Merge pull request #27 from serpapi/implement-image-api · serpapi/serpapi-ruby@2d75c5c · GitHub
Skip to content

Commit 2d75c5c

Browse files
authored
Merge pull request #27 from serpapi/implement-image-api
Add image upload support (`/image` API)
2 parents bfc0f8b + c7ab992 commit 2d75c5c

5 files changed

Lines changed: 138 additions & 12 deletions

File tree

‎CHANGELOG.md‎

Lines changed: 1 addition & 0 deletions

‎README.md‎

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ client.close
5656
- [Asynchronous searches](./demo/demo_async.rb) for submitting non-blocking jobs and retrieving completed results from the Search Archive API.
5757
- [Persistent connections and connection pooling](./demo/demo_thread_pool.rb) for reusing HTTP connections across searches.
5858
- Search results as Ruby hashes with `search`, token-efficient Markdown with `md`, or raw search-engine HTML with `html`.
59-
- SDK methods for the [Location API](https://serpapi.com/locations-api), [Search Archive API](https://serpapi.com/search-archive-api), and [Account API](https://serpapi.com/account-api).
59+
- SDK methods for the [Image API](https://serpapi.com/image-api), [Location API](https://serpapi.com/locations-api), [Search Archive API](https://serpapi.com/search-archive-api), and [Account API](https://serpapi.com/account-api).
6060
- Configurable HTTP timeouts and symbolized or string JSON keys.
6161

6262
## Response formats
@@ -210,6 +210,21 @@ pp results[:images_results]
210210

211211
A [light variant](https://serpapi.com/google-images-light-api) engine called `google_images_light` is also available for faster, lower-cost image searches.
212212

213+
### Google Lens with File Upload
214+
215+
Upload an image with the Image API, then use its image ID in a Google Lens search. Uploaded image IDs expire after 10 minutes.
216+
217+
```ruby
218+
require 'serpapi'
219+
220+
client = SerpApi::Client.new(api_key: ENV['SERPAPI_KEY'])
221+
upload = client.upload_image('./image.jpg')
222+
results = client.search(engine: 'google_lens', image_id: upload[:image_id])
223+
pp results[:visual_matches]
224+
```
225+
226+
[See Image API documentation](https://serpapi.com/image-api) · [See Google Lens image upload documentation](https://serpapi.com/google-lens-upload-an-image)
227+
213228
### Google Trends
214229

215230
Track search interest over time and compare the popularity of search terms.
@@ -356,6 +371,6 @@ The older library (google-search-results-ruby) was performing at 55 req/s on Rub
356371

357372
## Contributing
358373

359-
Contributions are welcome. Make sure to read our [contributing guide](./CONTRIBUTING.md).
374+
Contributions are welcome. Make sure to read our [contributing guide](https://github.com/serpapi/serpapi-ruby/blob/master/CONTRIBUTING.md).
360375

361376
© 2026 [SerpApi](https://serpapi.com)

‎README.md.erb‎

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ client.close
5656
- [Asynchronous searches](./demo/demo_async.rb) for submitting non-blocking jobs and retrieving completed results from the Search Archive API.
5757
- [Persistent connections and connection pooling](./demo/demo_thread_pool.rb) for reusing HTTP connections across searches.
5858
- Search results as Ruby hashes with `search`, token-efficient Markdown with `md`, or raw search-engine HTML with `html`.
59-
- SDK methods for the [Location API](https://serpapi.com/locations-api), [Search Archive API](https://serpapi.com/search-archive-api), and [Account API](https://serpapi.com/account-api).
59+
- SDK methods for the [Image API](https://serpapi.com/image-api), [Location API](https://serpapi.com/locations-api), [Search Archive API](https://serpapi.com/search-archive-api), and [Account API](https://serpapi.com/account-api).
6060
- Configurable HTTP timeouts and symbolized or string JSON keys.
6161

6262
## Response formats
@@ -210,6 +210,21 @@ pp results[:images_results]
210210

211211
A [light variant](https://serpapi.com/google-images-light-api) engine called `google_images_light` is also available for faster, lower-cost image searches.
212212

213+
### Google Lens with File Upload
214+
215+
Upload an image with the Image API, then use its image ID in a Google Lens search. Uploaded image IDs expire after 10 minutes.
216+
217+
```ruby
218+
require 'serpapi'
219+
220+
client = SerpApi::Client.new(api_key: ENV['SERPAPI_KEY'])
221+
upload = client.upload_image('./image.jpg')
222+
results = client.search(engine: 'google_lens', image_id: upload[:image_id])
223+
pp results[:visual_matches]
224+
```
225+
226+
[See Image API documentation](https://serpapi.com/image-api) · [See Google Lens image upload documentation](https://serpapi.com/google-lens-upload-an-image)
227+
213228
### Google Trends
214229

215230
Track search interest over time and compare the popularity of search terms.

‎lib/serpapi/client.rb‎

Lines changed: 38 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ module SerpApi
88
# * async non-block search
99
# * persistent HTTP connection
1010
# * search API with JSON, HTML, and Markdown output
11+
# * image API
1112
# * location API
1213
# * account API
1314
# * search archive API
@@ -146,6 +147,25 @@ def location(params = {})
146147
get('/locations.json', :json, params)
147148
end
148149

150+
# Upload an image using the Image API.
151+
# The returned image ID can be supplied to Search API engines that support
152+
# uploaded images, such as Google Lens. Image IDs expire after 10 minutes.
153+
#
154+
# doc: https://serpapi.com/image-api
155+
#
156+
# @param [String, Pathname, IO] image path or readable image stream
157+
# @param [Hash] params request parameters, such as an API key overriding the client default
158+
# @return [Hash] upload result containing the image ID
159+
def upload_image(image, params = {})
160+
if image.is_a?(String) || (defined?(Pathname) && image.is_a?(Pathname))
161+
::File.open(image, 'rb') do |file|
162+
post('/image', :json, params, image: HTTP::FormData::File.new(file))
163+
end
164+
else
165+
post('/image', :json, params, image: HTTP::FormData::File.new(image).tap(&:rewind))
166+
end
167+
end
168+
149169
# Retrieve search result from the Search Archive API
150170
#
151171
# ```ruby
@@ -235,19 +255,28 @@ def persistent?
235255
# @param [Hash] params custom search inputs
236256
# @return [String|Hash] raw text or decoded response as JSON / Hash
237257
def get(endpoint, decoder = :json, params = {})
238-
response = execute_request(endpoint, params)
258+
response = execute_request(:get, endpoint, params: query(params))
239259
handle_response(response, response_decoder(response, decoder), endpoint, params)
240260
ensure
241261
response&.flush if persistent?
242262
end
243263

244-
def execute_request(endpoint, params)
245-
if persistent?
246-
@socket.get(endpoint, params: query(params))
247-
else
248-
url = "https://#{BACKEND}#{endpoint}"
249-
HTTP.timeout(timeout).get(url, params: query(params))
250-
end
264+
# Perform an HTTP POST request with a multipart/form-data body
265+
#
266+
# @param [String] endpoint relative SerpApi endpoint, such as "/image"
267+
# @param [Symbol] decoder response decoder, either :json or :html
268+
# @param [Hash] params request parameters
269+
# @param [Hash] form multipart-specific fields, including uploaded files
270+
# @return [String|Hash] raw HTML or decoded response as JSON / Hash
271+
def post(endpoint, decoder = :json, params = {}, form = {})
272+
response = execute_request(:post, endpoint, form: query(params).merge(form))
273+
handle_response(response, decoder, endpoint, params)
274+
end
275+
276+
def execute_request(method, endpoint, options)
277+
client = persistent? ? @socket : HTTP.timeout(timeout)
278+
url = persistent? ? endpoint : "https://#{BACKEND}#{endpoint}"
279+
client.public_send(method, url, **options)
251280
end
252281

253282
def response_decoder(response, default)
@@ -310,7 +339,7 @@ def raise_http_error(response, data, endpoint, params, explicit_error: nil, deco
310339

311340
def raise_parser_error(response, endpoint, params)
312341
raise SerpApiError.new(
313-
"JSON parse error: #{response.body} on get url: https://#{BACKEND}#{endpoint}",
342+
"JSON parse error: #{response.body} from url: https://#{BACKEND}#{endpoint}",
314343
search_params: params,
315344
response_status: response.status,
316345
decoder: :json
Lines changed: 66 additions & 0 deletions

0 commit comments

Comments
 (0)