POST /api/v1/search/serp/google opens one Google search URL the way the client’s own browser would
and returns the HTML of the results page. You send no code: the task is dat.ai’s own SERP
script, run the same way as a Browsing API task, with the same key, the
same billing and the same task_id in GET /api/v1/browsing/status.
A captcha, “unusual traffic”, “verifying your request”, a walk off Google, an empty page:
none of these is an answer. Such an attempt counts as a failure and the server tries
again from elsewhere: first within fanout (the first browser to return a full results page
wins), then with the next round of attempts from browsers and IP addresses not yet tried.
A page with only a few results or “did not match any documents”, which Google also shows a
browser it is reluctant to serve, does not win at once: the other browsers of the same
fanout get up to 20 seconds to answer, and the page with the most results is returned.
By default the countries where Google refuses more often than it answers are excluded (RU
always is). You pay only for the task that returned the page, once, however many browsers
of its fanout answered.
Request
POST /api/v1/search/serp/google, JSON body:
| Field | Type | Description |
|---|---|---|
url | string | Required. A https://www.google.<tld>/search?q=... URL with all its parameters (hl, gl, uule, udm, …); it goes to the browser as is. Google no longer honours num: a results page holds about 10 results whatever it says. udm=50 asks for AI Mode. Other hosts and paths are rejected |
locale | string | A language such as en-GB. It becomes the Accept-Language header, shaped the way Chrome shapes it, and navigator.languages: en-GB → en-GB,en-US;q=0.9,en;q=0.8, de-DE → de-DE,de;q=0.9,en-US;q=0.8,en;q=0.7, fr → fr,en-US;q=0.9,en;q=0.8. Without it the browser speaks its own language |
cookies | object | Cookies placed on the Google domain before the first request: {"NID": "..."}. A value is a string or a list of strings; a list is set in order and the browser keeps the last. The cookie consent (SOCS) is set by the server itself, so the consent wall is not your problem; a SOCS or CONSENT of your own replaces ours. On an AI Mode page the server sets none and clicks the wall through instead |
headers | object | Extra headers on every request the page makes: {"Cache-Control": "max-age=0"}. A value is a string or a list (a list is joined with , ). Not allowed: User-Agent (a foreign UA is the surest way to a captcha), Cookie, Host, Connection, Content-Length, Sec-*, Proxy-*; Accept-Language only when there is no locale |
timeout_sec | number | The budget of one attempt, default 90, maximum 1200 |
fanout | number | How many browsers run an attempt at once, 1–10, default 3. Only the browser that returns the page is billed |
attempts | number | How many rounds are run in turn when a round produced no page, 1–5, default 3. Every round is a new task from browsers and IP addresses not used before |
resolve_links | bool | Default false: the page comes back as Google rendered it. With true, Google’s redirect links in the page (/goto?url=..., /url?...&url=...) are replaced with the addresses they lead to. For these sessions Google renders every outbound link, organic results and AI Overview citations included, only as such tokens; the real URLs are not in the HTML at all. Resolution runs in the same session, one small request to google.com per link, in parallel; typically +3–8 s per task. Ask for it when you need the addresses rather than the text and positions |
session_key | string | As in browsing: the rounds of one request never repeat a browser anyway; the key is for fitting the SERP into an existing session |
filter.country_iso_included | string[] | The countries the search may run from, as in browsing. A country named here is not taken away by the default exclusions |
filter.country_iso_excluded | string[] | The countries it must not run from, added to the default exclusions. Not together with country_iso_included |
ttl_days | number | How long the task body is kept, as in browsing |
Unknown fields are rejected with 400.
Response
200: the body is the page’s HTML, Content-Type: text/html; charset=utf-8.
| Header | Meaning |
|---|---|
X-Task-ID | The task that returned the page (visible in GET /api/v1/browsing/status) |
X-Serp-Final-URL | Where the browser ended up |
X-Serp-Links | How many redirect links now carry their real address out of how many were found (38/40); absent when the page had none or resolve_links was not set |
X-Serp-AI-Overview | expanded when the page had an AI Overview and it was opened (the HTML holds the full text rather than the collapsed third), present when it would not open; absent when the page had no such block |
X-Serp-AI-Mode | On an AI Mode page: complete when the answer finished streaming before the page was read, partial when time ran out with some of it there |
Errors: 400 (invalid request), 401, 402 (subscription or balance), 502 when no results
page could be obtained (Google refused, a captcha and the like), 504 when no answer came
in time. The error body is a JSON object with one field, error, as the server gives it:
// 400 — not a Google search URL, a header the browser must keep its own, a field the endpoint does not know
{ "error": "url must be a Google search page, e.g. https://www.google.com/search?q=..." }
{ "error": "headers: User-Agent cannot be set: the node's own browser identifies itself; a different User-Agent is what gets a node refused" }
{ "error": "Invalid input data" }
// 401 — no key, or a wrong one
{ "error": "Authentication required: missing Authorization header. Please provide Authorization header with Bearer token format: 'Authorization: Bearer <your-api-key>'" }
// 402 — a quota at zero and no balance to cover it (see Account & Balance)
{ "error": "Insufficient balance: the browser_seconds quota is used up and there is no USD balance to cover overage; top up in the dashboard (https://dash.dat.ai) or check GET /api/v1/account/balance", "code": "insufficient_balance", "exhausted": "browser_seconds" }
// 502 — no browser tried returned a results page
{ "error": "no results page: Google refused the search (captcha)" }
{ "error": "no results page: Google's consent page could not be passed" }
{ "error": "no results page: Google returned an empty page" }
{ "error": "no results page: the search could not be completed" }
// 504 — no answer within the attempts' time
{ "error": "no results page: no answer in time" }The whole request takes up to attempts × (timeout_sec + ~45 s); with long budgets keep
attempts small.
AI Mode
Google’s AI Mode is a search page of its own: one answer that streams in, instead of a list
of results. Ask for it with udm=50 in the URL, everything else unchanged:
https://www.google.com/search?q=how+does+photosynthesis+work&udm=50&hl=en&gl=usThe task waits for the answer to finish streaming before it reads the page, and
X-Serp-AI-Mode says whether it did. The answer is in the [data-subtree="aimc"] element
of the HTML. A browser Google will not give AI Mode to (“AI Mode is not currently
available”) counts as a refusal like a captcha does, and the request moves on to another
one within fanout and attempts. Google refuses AI Mode far more readily than a results
page, so the default fanout of 3 matters here: keep it, or raise it. On this page a
locale goes as the Accept-Language header only; navigator.languages stays the
browser’s own.
Example
curl -sS -X POST "https://llm.dat.ai/api/v1/search/serp/google" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-o serp.html -D - \
-d '{
"url": "https://www.google.com/search?q=kids+soccer+pants&ie=utf-8&oe=utf-8&hl=en&gl=us&uule=w+CAIQIFISCQkhfJQWRMKJEYZ4AAR0XHaC&glp=1&gws_rd=cr&fg=1",
"locale": "en-GB",
"headers": {"Cache-Control": "max-age=0"},
"fanout": 2,
"attempts": 3
}'Response (200): the headers say which task answered and what the page holds, the body
is the page (about 3 MB for a results page with an AI Overview), saved to serp.html here:
HTTP/2 200
content-type: text/html; charset=utf-8
x-task-id: web:f6fba073-a71e-4e74-87bf-d0c39c71dcf0
x-serp-final-url: https://www.google.com/search?q=kids+soccer+pants&hl=en&gl=us&sei=0QataoTVEbGN9u8PmLSz6Qg
x-serp-ai-overview: expanded
x-serp-links: 21/21
<!DOCTYPE html>
<html itemscope="" itemtype="http://schema.org/SearchResultsPage" lang="en" ...><head><meta charset="UTF-8"> ...x-serp-links is there because the request asked for resolve_links; on a page without
an AI Overview x-serp-ai-overview is absent. A request that got no page answers with a
JSON error instead, see Response.