google-search-api
Google Search API
One request returns a live Google results page as structured JSON: what ranked, in what order, from where. One credit per page.
Endpoint
curl "https://api.serpix.io/v1/search?q=best+espresso+machine&hl=en" \
-H "Authorization: Bearer $SERPIX_API_KEY"The same parameters are accepted as a JSON body on POST /v1/search:
curl -X POST "https://api.serpix.io/v1/search" \
-H "Authorization: Bearer $SERPIX_API_KEY" \
-H "Content-Type: application/json" \
-d '{"q": "best espresso machine", "hl": "en"}'Parameters
| field | type | default | description |
|---|---|---|---|
| qrequired | string | – | The search query. |
| gl | string | – | Country to search as, two letters. Defaults to the country of location when one is set, otherwise us. |
| hl | string | en | Interface language, two letters. Changes the labels Google renders. |
| device | desktop | mobile | desktop | Which layout Google serves. Mobile results differ from desktop. |
| cr | string | – | Restrict results to pages from these countries. Comma-separated two-letter codes, like fr,de. |
| lr | string | – | Restrict results to pages in these languages. Comma-separated two-letter codes, like fr,de. |
Search origin
Where the search is made from. A place name covers most work; coordinates are there for when a city is too coarse, and Google measures distance from them. They are two ways to write one thing, so a request carries one or the other.
| field | type | default | description |
|---|---|---|---|
| location | string | – | City-level place the search is made from, like Austin or Berlin, Germany. The canonical name it resolves to is echoed back as search_parameters.location_used. |
| lat | number | – | Latitude of the search origin, for when a city is too coarse. Send with lon, and not with location. |
| lon | number | – | Longitude of the search origin. Send with lat. |
| radius | integer | – | Distance bias in metres around the origin. A bias, not a fence: prominent results outside it still rank. |
Results and filtering
| field | type | default | description |
|---|---|---|---|
| page | integer | 1 | 1-based page number. Page 2 is the next ten results. |
| time_range | h | d | w | m | y | – | Only pages from the past hour, day, week, month, or year. Mutually exclusive with after/before. |
| after | string | – | Only pages published on or after this date, ISO YYYY-MM-DD. |
| before | string | – | Only pages published on or before this date, ISO YYYY-MM-DD. |
| sort | relevance | date | relevance | date orders by recency instead of relevance. |
| verbatim | boolean | false | Search the words exactly: no synonyms, no stemming, no query expansion. |
| autocorrect | boolean | true | false keeps results for the query as typed, even when Google would correct it. See spelling_fix in the response. |
| safe | off | active | off | Turn Google's explicit-results filter on. |
| filter | boolean | true | false disables Google's similar-results dedup, so near-duplicate pages that are normally folded away still rank. |
Rejected parameters
Unknown parameters are never silently ignored. A request carrying one is rejected with 422 before any search runs, so a typo can't bill you for results you didn't ask for. Common parameters from raw Google URLs and other SERP APIs get a pointed message naming the equivalent here:
| field | type | default | description |
|---|---|---|---|
| num | 422 | – | One page of about ten results per request. Paginate with page. |
| start | 422 | – | Use page, a 1-based page number, instead of a result offset. |
| tbs | 422 | – | Use time_range, after/before, sort, or verbatim. |
| nfpr | 422 | – | Use autocorrect, a boolean. |
| uule | 422 | – | Use location with a city name, or lat/lon. The encoding is handled for you. |
| google_domain | 422 | – | Searches run on google.com. Set the country and language with gl and hl, and the origin with location or lat/lon. |
| api_key | 422 | – | Authenticate with the Authorization: Bearer header. |
| engine | 422 | – | Not needed. This endpoint is Google web search. |
HTTP/1.1 422 Unprocessable Entity
{
"detail": "`num` is not supported: one page of ~10 results per request; paginate with `page`"
}Response
A search returns one JSON object: three envelope objects that are always present, then the results themselves. A field Google didn't serve is omitted entirely, never null, never [].
| field | type | default | description |
|---|---|---|---|
| search_metadata | object | – | Request id, status, timing, parser version, and the exact Google URL fetched. |
| search_parameters | object | – | The vantage point this capture was taken from, so it can be run again the same way. |
| search_information | object | – | The query as Google echoed it, and how it handled a misspelling. |
| organic_results | array | – | The ranked web results, in the order Google served them. |
| people_also_ask | string[] | – | The follow-up questions Google suggests, as plain strings. |
| related_searches | string[] | – | The refined queries at the bottom of the page, as plain strings. |
| pagination | object | – | The current page and the URL of the next one. |
{
"search_metadata": {
"id": "srx_6-Z531iRST1L67tZ",
"status": "success",
"created_at": "2026-08-13T14:22:07Z",
"took_ms": 812,
"parser_version": "2026.08.18",
"google_url": "https://www.google.com/search?q=best+espresso+machine&..."
},
"search_parameters": {
"q": "best espresso machine",
"gl": "us",
"hl": "en",
"device": "desktop",
"page": 1
},
"search_information": {
"query_displayed": "best espresso machine"
},
"organic_results": [
{
"position": 1,
"title": "The 5 Best Espresso Machines [Tested 2026]",
"link": "https://coffeechronicler.com/gear/espresso-machines/best/",
"snippet": "We tested 14 machines. The Gaggia Classic Pro wins on build quality, while the Breville Bambino Plus is the better pick for beginners.",
"date": "Jul 3, 2026"
},
{
"position": 2,
"title": "Best Espresso Machine: Our Top 5 Picks for 2026",
"link": "https://www.coffeeness.de/en/best-espresso-machine/",
"snippet": "Our top pick is the Gaggia Classic Evo, with a commercial steam wand and 58mm portafilter.",
"sitelinks": [
{
"title": "Gaggia Classic Evo Review",
"link": "https://www.coffeeness.de/en/gaggia-classic-evo-review/"
}
]
}
],
"people_also_ask": [
"Is a $500 espresso machine worth it?",
"What is the best espresso machine for a beginner?",
"Do I need a separate grinder?"
],
"related_searches": [
"best espresso machine under 500",
"gaggia classic pro review",
"best espresso machine with grinder"
],
"pagination": {
"current": 1,
"next": "https://www.google.com/search?q=best+espresso+machine&start=10&..."
}
}Organic results
One entry per ranked page, in served order. position is the rank a rank tracker stores; date is split off the front of the snippet rather than left glued to it.
| field | type | default | description |
|---|---|---|---|
| position | integer | – | Rank on this page, starting at 1. |
| title | string | – | The result's headline, as displayed. |
| link | string | – | The destination URL. Google's opaque redirect is resolved, so this is the real page, never a /goto token. |
| snippet | string | – | The description text Google quoted from the page. |
| date | string | – | Publication date as displayed, when Google shows one. Split off the snippet so the prose stays prose. |
| sitelinks | list | – | Extra links Google nested under this result. |
| field | type | default | description |
|---|---|---|---|
| title | string | – | The sitelink's label. |
| link | string | – | Where it points. |
Search information
Google silently rewrites misspelled queries. When it does, the ranks you get belong to a different search than the one you sent — so it is stated explicitly rather than left for you to notice.
| field | type | default | description |
|---|---|---|---|
| query_displayed | string | – | The query as Google echoed it in the search box. |
| spelling_fix | object | – | Present only when Google corrected or suggested a spelling. |
| field | type | default | description |
|---|---|---|---|
| type | string | – | corrected means Google searched something else and these ranks are for that query. suggested means it only offered, and the ranks are for the query you sent. |
| original | string | – | The query as typed, when Google replaced it. |
| corrected_to | string | – | The query Google corrected or suggested. |
{
"search_information": {
"query_displayed": "portugual",
"spelling_fix": {
"type": "corrected",
"original": "portugual",
"corrected_to": "portugal"
}
}
}Pagination
Ask for page 2 with page=2. next is absent on the last page, which is how you know to stop.
| field | type | default | description |
|---|---|---|---|
| current | integer | – | The page this response is for. |
| next | string | – | Google's URL for the following page. Absent on the last page. |
Envelope
| field | type | default | description |
|---|---|---|---|
| id | string | – | Request id. Quote it in support. |
| status | string | – | Always success on a 200. |
| created_at | string | – | When the search ran, ISO 8601 UTC. |
| took_ms | integer | – | Total time for the fetch and parse. |
| parser_version | string | – | The parser build that produced this response. |
| google_url | string | – | The exact URL fetched, including the encoded origin. |
| html_url | string | – | Archived raw HTML for this search, when archiving is on. |
| json_url | string | – | Archived JSON for this search, when archiving is on. |
| field | type | default | description |
|---|---|---|---|
| q | string | – | The query that was searched. |
| gl | string | – | The country actually used, after defaults were applied. |
| hl | string | – | The interface language used. |
| device | string | – | The layout requested. |
| page | integer | – | The page requested. |
| location_requested | string | – | The place name as you sent it. |
| location_used | string | – | The canonical place it resolved to. |
| lat | number | – | Latitude of the origin, when coordinates were sent. |
| lon | number | – | Longitude of the origin, when coordinates were sent. |
| radius | integer | – | Distance bias in metres, when one was sent. |
Errors
You are charged for results, not for failures. A blocked page, a transport error, or a page we captured but could not read all cost nothing — the credit header on the response says so either way.
| field | type | default | description |
|---|---|---|---|
| 200 | charged | – | Results returned. |
| 402 | free | – | Not enough credits. Nothing was fetched. |
| 422 | free | – | The request was rejected before any search ran. |
| 502 | free | – | The page was captured but could not be read. This is our problem, not yours, and it is logged as one. |
| 503 | free | – | Google blocked the capture. Retry — a different session is used. |
HTTP/1.1 502 Bad Gateway
X-Serpix-Credits-Charged: 0
{
"error": { "code": "search_failed", "message": "the page could not be parsed" }
}