# Google results (SERP)

`GET https://api.crawl4ai.com/serp`

Google's results page as structured data: true ranks, every block Google showed, and the place the page was localised to. Built for rank tracking and SEO tools. Not the same as [/search](https://crawl4ai.com/docs/search/), which is Crawl4AI's own web search for reading results. In beta since 8 October 2026: the shape is settled, the service is new; it becomes stable in a few weeks. `POST /serp` takes the same fields as a JSON body.


> **Note: Status**
>
> beta · added 8 Oct 2026

## Request

curl:

```bash
curl "https://api.crawl4ai.com/serp?q=best+crm+software&country=us" \
  -H "Authorization: Bearer sk_live_..."
```

## Query

| Name | Type | Default | Description |
|---|---|---|---|
| q* | string | - | The search query (max 512 chars). |
| country | string | us | Default `us`. Two letters: the country Google serves the page for. |
| language | string | en | Default `en`. The language of Google's page. |
| location | string | - | Optional. A city or region, for example `Austin, TX`. The answer's `search.location` shows the full name we used (`Austin,Texas,United States`). An unknown name answers `400` with the closest names in `closest`. |
| num | 10, 20, 30 … 100 | 10 | Default 10, any multiple of 10. Results per page: `num=30` fetches Google pages 1 to 3 at the same time and returns every organic result on them. |
| page | int | 1 | Default 1. Counts in steps of `num`: `num=20&page=2` is Google pages 3 and 4. No request reaches past Google page 30. |
| device | string | desktop | Default `desktop`. `desktop` is the only value today. |
| time | string | - | Optional. `hour`, `day`, `week`, `month` or `year`: only results from that past period. |
| fresh | 0 \| 1 | 0 | Default 0. `1` skips the cache and fetches the page live, at the live price. |
| source | auto \| google | auto | Default `auto`: a live Google result by either road (`gg` or `gs` in the answer's `source`). `google`: only `gg`, the road that also returns the AI Overview when Google shows one. When it cannot answer, the call ends with a `503`, and a failed call costs nothing. |
| engine | string | google | Default `google`. `google` is the only value today. |
| type | string | organic | Default `organic`. `organic` is the only value today. |

## Response

Shortened, for `q=car insurance quotes`:

response:

```json
{
  "search": { "q": "car insurance quotes", "engine": "google", "type": "organic", "country": "us", "language": "en",
              "location": null, "device": "desktop", "page": 1, "num": 10, "time": null },
  "cached": false, "age_s": 0, "source": "gg",
  "pages": { "asked": 1, "got": 1, "missing": [] },
  "blocks": [ "ai_overview", "organic", "people_also_ask", "related_searches" ],
  "total_results": 238000000,
  "results": [
    { "position": 1, "position_absolute": 2, "title": "…", "url": "https://www.libertymutual.com/…",
      "domain": "libertymutual.com", "displayed_url": "https://www.libertymutual.com › auto-insurance › state",
      "snippet": "…" },
    …
  ],
  "ai_overview": { "text": "You can compare rates…", "sources": [ { "title": "The Zebra", "url": "https://…" } ] },
  "people_also_ask": [ "…", "…", "…", "…" ],
  "related_searches": [ "…" ],
  "ms": 1240
}
```

The top-level keys:

| Name | Type | Default | Description |
|---|---|---|---|
| search | object | - | The settings the page was fetched with: `q`, `engine`, `type`, `country`, `language`, `location`, `device`, `page`, `num`, `time`. |
| results | array | - | The organic results, in Google's order (below). No results is a `200` with an empty list. |
| blocks | array | - | The names of the blocks that were on the page, in page order. |
| total_results | int | - | Google's own estimate, as printed on the page ("About 116 results"). With a place attached it is often a small number. Absent when Google did not show one. |
| source | gg \| gs \| mixed | - | Where the pages came from: `gg` and `gs` are both live Google results, `mixed` means some pages of each. A `gs` page has fewer blocks and no `position_absolute`, and `blocks` names only what it has. |
| empty_reason | string | - | Only when `results` is empty. `google_no_results`: Google's own "did not match any documents" page. `rescue_no_results`: Google refused the query to us and our fallback had nothing; another provider may still have results, so fall back on this one only. |
| cached | bool | - | `true` when every page came from our cache; `age_s` is then the age of the oldest page, in seconds. `fresh=1` skips the cache. |
| pages | object | - | `{asked, got, missing}`: the number of Google pages asked for, the number we got, and the page numbers we could not get. |
| ms | int | - | The time the answer took, in milliseconds. |

Each result:

| Name | Type | Default | Description |
|---|---|---|---|
| position | int | - | The rank among the organic results only, from 1. |
| position_absolute | int | - | The rank among every block on the page, in page order: an ad, an AI Overview or a places block each take a number too. |
| title | string | - | The result's title. |
| url | string | - | The page itself, not a redirect. |
| domain | string | - | The host of `url`, lower case, without `www.`. |
| displayed_url | string | - | The address Google prints under the title, with its `›` parts; empty when Google prints none. |
| snippet | string | - | The text Google shows, without the date at its start. |
| date | string | - | Optional. The date Google shows, as shown (`Jul 20, 2026`, `3 years ago`). |
| sitelinks | array | - | Optional. The small links under the result: `{title, url}`. |

## The blocks

A block Google did not show is absent, never `null`. Each block's key and its item fields:

| Key | Fields |
|---|---|
| `ai_overview` | `{text, sources: [{title, url}]}` |
| `people_also_ask` | a list of questions (strings) |
| `related_searches` | a list of searches (strings) |
| `knowledge_panel` | `{title, subtitle, description, facts: [{k, v}]}` |
| `top_stories` | `[{title, url}]` |
| `local_pack` | `[{position, name, rating, reviews_count, address}]` |
| `ads_top` · `ads_bottom` | `[{position_absolute, title, url, displayed_url, snippet}]` |
| `short_videos` | `[{title, url, source, duration}]` |
| `images` | `[{title, url, source}]`; `url` is the page the image is on |
| `latest_from_site` | `[{title, url, date}]`; `latest_from_site_domain` names the site |
| `perspectives` | `[{title, url, source, author, date}]` |
| `apps` | `[{title, url, store}]` |

`blocks` can also name `videos` and `discussions`: they were on the page and take a number in `position_absolute`, but they have no list of their own. A block can be named in `blocks` with no list when we could not read its links.

## Depth and paging

For exact ranks past the first 10, ask a bigger `num`: its Google pages are fetched at the same time, and `position` runs on across them. With `page` above 1, numbers start at `(page-1)×num+1`; this is an estimate, because a Google page holds 8 to 10 organic results. If a later page fails, the answer is still a `200` with the pages we got, and `pages.missing` names the failed ones.

## Credits and errors

1 credit per Google page fetched live; a page from our cache costs half. The `x-c4-cost` header carries the real cost of the call. A failed first page or a blocked fetch is a `5xx` and is not charged. A bad setting answers `400` with the reason in `error`.

**Try it with your key**

Run this call in the playground and see the real answer, the time and the units.
[Open the playground](https://crawl4ai.com/#playground)



---
Source: https://crawl4ai.com/docs/serp/
