Google Search API
Structured Google results: organic listings, ads, People Also Ask, knowledge graph, local pack and the AI Overview.
Price
$1.03 per 1,000 requests on every plan, deducted from your prepaid USD balance.
Endpoint
POST /v1/monitor/google
Countries
213 countries, down to the city.
Request
curl -X POST "https://api.answerline.dev/v1/monitor/google" \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{"query":"project management software","country":"US","include":{"aioverview":{"markdown":true}}}'
Request options
| Field | Type | Description |
|---|---|---|
query |
string |
Query to run on Google. Mandatory when 'url' is not given; send one or the other, never both. |
url |
string |
A full URL of a Google web search, executed as-is in place of a request assembled from 'query' plus the targeting fields. It has to be absolute and http(s), hosted on one of Google's web search domains (google.com, google.co.uk, www.google.de, ...), with '/search' as its path and a 'q' that is not empty. Cannot be combined with 'query', 'location', 'uule' or 'pages', since the URL already determines them. It may carry only these query parameters, which are kept: 'q', 'gl', 'num' (treated as result depth), 'hl' and 'uule'; a URL with any other (such as 'start', 'tbs' or 'safe') is refused with a 400. Without 'uule', the search is localized to the requested country. A URL containing 'tbm' is refused: it points at a Google vertical that has a separate endpoint and price (for tbm=nws, call POST /v1/monitor/google/news). |
country |
string |
Country for localized search results, as an ISO 3166-1 alpha-2 code. Mandatory in the standard request form. If you send 'url' instead, the country is taken from the URL's 'gl' parameter — set it yourself to override that value, or when the URL carries no 'gl'. |
gl |
string |
Google's own name for the result-geography parameter, usable in place of `country`. Takes ISO 3166-1 alpha-2 codes, as `country` does, in upper or lower case (`us` or `US`), and the country has to be one this endpoint supports (list them with `GET /v1/countries`; Google News has no `model` value there, and its edition countries are listed at https://answerline.dev/docs/coverage#countries). Send either `country` or `gl`; if both are sent with differing values, the request fails with `400`. |
hl |
string |
Interface language, in the format of Google's `hl` parameter (for example `en`, `de`, `pt-BR`). Case-insensitive free text; Google defines which codes exist. Takes precedence over the language Answerline infers from the country, which lets interface language and result geography differ. |
location |
string |
Location name in Google's canonical comma-separated form 'City,Region,Country' (roughly 100,000 accepted locations are listed at https://developers.google.com/google-ads/api/reference/data/geotargets). Combine with 'country' to target a specific city. Cannot be sent together with 'uule' — choose one of the two. Not allowed when 'url' is set. |
uule |
string |
A ready-made Google UULE string for exact geo-targeting, in the location-name form (starting w+CAIQICI); the coordinate form (a+) is refused with a 400. Supply it when you already have an encoded UULE rather than a location name. Cannot be sent together with 'location' — pass only one of the two. Not allowed together with 'url'; in that case add 'uule' to the URL's query string. |
device |
"desktop" | "mobile" | "ios" | "android" |
Device to run the search on. 'desktop' gives the desktop SERP, while 'ios' (Safari on an iPhone), 'android' (Chrome on an Android phone) and 'mobile' (same as 'android') each give the mobile SERP. |
pages |
integer |
How many pages of search results to retrieve (1-10). Not allowed together with 'url' — when 'url' is given, depth is taken from the URL's 'num' parameter (rounded up to full 10-result pages, at most 10 pages). |
include |
object |
Flags that add extra data to the response (optional) |
include.html |
boolean |
A copy of each Google results page as it was when the answer was captured, stored on our file host and linked in `html`, to check the answer against. No extra cost. |
include.aioverview |
object |
Add Google's AI Overview to the response |
include.paaAioverview |
boolean |
Fill People Also Ask items of the AI Overview type with markdown content and their sources. Billed as the AI Overview add-on, once per request, however many of include.aioverview and include.paaAioverview are set; every hydrated item needs one more fetch (usually 1-2 on a page), which makes responses slower. |
include.googleGoto |
boolean |
Has had no effect since 8th September 2026. Each redirect link is now always returned as `redirectLink` next to its target, so you get both without setting this flag. Requests that still include it are handled as though it were omitted. |
include.rawWire |
boolean |
Return the engine's own response as received in result.rawWire: the answer stream, the rendered results pages or the RSS feed, without Gemini's detected location or ChatGPT's session tokens; null where it was not captured. Free. See https://answerline.dev/docs/output-formats#raw-wire |
include.screenshot |
boolean |
Return a full-page PNG screenshot of the engine's page as it showed the finished answer, in result.screenshot: a link, or one link per results page on Google Search. On ChatGPT, Gemini, AI Mode, Google Search, Perplexity and Grok; other engines refuse it with a 400. Absent when the answer was read without a rendered page or its page was not available. Free. See https://answerline.dev/docs/output-formats#screenshots |
answerDepth |
"any" | "full" | "quick" |
How deep an answer the engine writes, where it offers a choice. any (the default): a full answer when the engine can give one now, else its short one. full: a full answer or a retryable error, never charged. quick: the short one, the fastest. Perplexity offers the choice; every other engine has one depth and ignores the field. provenance.answerDepth says what was served. Same price for every value. See https://answerline.dev/docs/engine-features/#answer-depth |
What you get back
Field in result | Type | Description |
|---|---|---|
organicResults | object[] | Google organic results |
ads | object[] | Sponsored results in Google search: text ads (`type: RESULT`), plus sponsored shopping cards (`type: SHOPPING_CARD`) drawn from the carousels at the top of the page and in the right-hand column. |
peopleAlsoAsk | object[] | Google's "People also ask" questions |
peopleAreSaying | object[] | Items from the Google results module titled "What people are saying" or "Trending posts and discussions". When the page has no such module, `result` leaves this field out instead of returning an empty array, so treat it as optional. |
relatedSearches | object[] | Suggested related searches |
knowledgeGraph | object | Knowledge Graph panel Google shows for the entity in the query. Not included in `result` if the results page has no such panel — handle the field as optional; it is not sent as null. |
localResults | object[] | Businesses in Google's local pack (the map-based "3-pack" of nearby businesses shown for local-intent searches, e.g. "best ramen in seattle"). If the SERP has no local pack, `result` does not contain this field at all, so treat it as optional instead of relying on an empty array. Applies to desktop only; neither the mobile layout nor the hotels/travel pack (a separate panel) is returned. |
shoppingCards | object[] | Product cards taken from the organic shopping grids on the results page ("Popular products" / "More products"). When the page has no shopping section, `result` does not include this field at all, so clients should handle it as optional instead of relying on an empty array. The shape reuses field names from `shopping_cards` in AI Mode for consistency across endpoints, but its keys are camelCase (`productLink`, `oldPrice`) in the Google response. |
recipes | object[] | The cards of Google's Recipes carousel, in the order shown. Absent when the page shows none. |
aioverview | object | Data from Google's AI Overview, when requested; null if no AI Overview could be obtained after retrying. |
The results page versus Google’s developer API
This endpoint returns Google’s results page for a query and market, as structured fields. Rank tracking and AI Overview monitoring measure that page, because it is what searchers see.
Google’s JSON API for web search results is the Custom Search JSON API. According to its overview (checked 2026-09-17, developers.google.com) it returns web and image results from a Programmable Search Engine, gives 100 free queries per day, costs $5 per 1,000 further queries up to 10,000 per day, is closed to new customers, and existing customers have until January 1, 2027 to move to an alternative. Its cse.list reference (checked 2026-09-17, developers.google.com) documents gl, hl, cr and lr and does not mention AI Overviews, People Also Ask, ads, local packs or knowledge panels.
Rank here is a page position: organicResults[].position and page describe where a listing appeared after ads, the AI Overview and other modules pushed it down. Those modules (aioverview, peopleAlsoAsk, peopleAreSaying, localResults, knowledgeGraph, shoppingCards, recipes, ads) take attention before the first organic link, and tracking them explains traffic changes that rank alone cannot. Being cited in aioverview.sources or aioverview.citationPills is a separate outcome from ranking organically and needs its own tracking.
An annotated response
The excerpt follows the result schema for POST /v1/monitor/google. Values are illustrative; the request set include.aioverview.markdown to true.
{
"success": true,
"result": {
"organicResults": [
{
"position": 1,
"title": "Project Management Software for Teams | ExampleBoard",
"link": "https://www.exampleboard.com/",
"displayedLink": "https://www.exampleboard.com",
"snippet": "Plan, track and deliver projects...",
"page": 1,
"sitelinks": { "inline": [{ "title": "Pricing", "link": "https://www.exampleboard.com/pricing" }] }
}
],
// blockPosition: top | bottom | middle | rhs. type: RESULT | SHOPPING_CARD.
"ads": [
{
"position": 1,
"blockPosition": "top",
"type": "RESULT",
"title": "SampleTask - Free for Small Teams",
"url": "https://www.sampletask.example/signup",
"page": 1,
"displayedUrl": "www.sampletask.example",
"domain": "sampletask.example",
"description": "Kanban, Gantt and time tracking in one tool..."
}
],
// type is AIOVERVIEW, LINK or UNKNOWN. markdown and sources on AIOVERVIEW
// items require include.paaAioverview.
"peopleAlsoAsk": [
{ "question": "What is the easiest project management software?", "type": "LINK", "title": "...", "link": "https://www.example-reviews.com/easy-pm", "snippet": "..." }
],
"relatedSearches": [{ "query": "free project management software", "link": "https://www.google.com/search?q=free+project+management+software" }],
// null when requested but Google showed no AI Overview.
"aioverview": {
"text": "Popular options include ExampleBoard and SampleTask...",
"markdown": "Popular options include **ExampleBoard** and **SampleTask**...",
"sources": [
{ "position": 1, "url": "https://www.example-reviews.com/best-pm-software", "label": "Best project management software", "description": "..." }
],
// citationPillId is 1-based; position points into aioverview.sources.
"citationPills": [
{ "label": "Best project management software", "citationPillId": 1, "url": "https://www.example-reviews.com/best-pm-software", "domain": "example-reviews.com", "position": 1 }
],
// Links under a chip's "related links" flyout that are not in sources. No position.
"relatedLinks": []
}
// localResults, peopleAreSaying, knowledgeGraph and shoppingCards are omitted
// when the page has no such module.
}
}
Details that matter in code:
localResults,peopleAreSaying,knowledgeGraph,shoppingCardsandrecipesare omitted when absent;aioverviewisnullwhen requested but not shown. Distinguish “not requested” (key missing) from “not shown” (null).localResults[].reviewsis Google’s display string (for example26K), not a number.peopleAreSaying[].dateis relative text such as5 days ago.ads[].priceon shopping-card ads carriesvalue,currencyandraw;rawdistinguishes a genuine price from labels such as installment offers.shoppingCards[].productLinkis usually an empty string because the card opens a Google overlay.recipesholds the cards of Google’s Recipes carousel in the order shown:title,linkto the recipe’s page,source(the site),ingredientsandtotalTimeas the card lists them,ratingout of 5 andreviews, the number of ratings.redirectLinkappears beside a link only when Google served it as a redirect;POST /v1/monitor/google/gotoresolves one free of charge.include.googleGotois deprecated and ignored.
Synchronous calls
A synchronous call returns the structured page and waits up to five minutes. One page costs $0.00103, the same as a task.
Python (requests)
import os
import requests
API = "https://api.answerline.dev"
HEADERS = {"Authorization": f"Bearer {os.environ['API_KEY']}"}
MY_DOMAIN = "exampleboard.com"
resp = requests.post(
f"{API}/v1/monitor/google",
headers=HEADERS,
json={
"query": "project management software",
"country": "US",
"include": {"aioverview": {"markdown": True}},
},
timeout=330,
)
if resp.status_code != 200:
raise RuntimeError(f"{resp.status_code} {resp.headers.get('X-Request-Id')} {resp.text}")
result = resp.json()["result"]
rank = next((r["position"] for r in result.get("organicResults", []) if MY_DOMAIN in r.get("link", "")), None)
aio = result.get("aioverview")
cited = aio is not None and any(MY_DOMAIN in s.get("url", "") for s in aio.get("sources", []))
print("organic rank:", rank)
print("AI Overview:", "absent" if aio is None else ("cites us" if cited else "shown, not citing us"))
print("charged (USD):", resp.headers.get("X-Credits-Charged"))
TypeScript (fetch)
const API = "https://api.answerline.dev";
const res = await fetch(`${API}/v1/monitor/google`, {
method: "POST",
headers: { Authorization: `Bearer ${process.env.API_KEY}`, "Content-Type": "application/json" },
body: JSON.stringify({ query: "emergency plumber denver", gl: "us", pages: 2 }),
signal: AbortSignal.timeout(330_000),
});
if (!res.ok) throw new Error(`${res.status} ${res.headers.get("X-Request-Id")} ${await res.text()}`);
const { result } = await res.json();
for (const place of result.localResults ?? []) {
console.log(place.position, place.title, place.rating, place.reviews, place.phone ?? "");
}
const topAds = (result.ads ?? []).filter((ad: any) => ad.blockPosition === "top");
console.log("top ads:", topAds.map((ad: any) => ad.domain));
console.log("charged (USD):", res.headers.get("X-Credits-Charged"));
Batches: async tasks and a webhook
Rank tracking runs as batches. Tasks queue and post to your webhook. POST /v1/async/task/batch takes 1 to 500 tasks; each is admitted independently and answered by index.
Python: a daily keyword batch
import datetime
import os
import requests
API = "https://api.answerline.dev"
HEADERS = {"Authorization": f"Bearer {os.environ['API_KEY']}"}
day = datetime.date.today().isoformat()
keywords = ["project management software", "kanban tool", "gantt chart software"]
countries = ["US", "GB"]
tasks = [
{
"taskType": "GOOGLE",
"payload": {"query": kw, "country": cc, "pages": 2, "include": {"aioverview": {}}},
"idempotencyKey": f"serp-{i}-{cc.lower()}-{day}",
"webhook": {"url": "https://your-app.example/hooks/serps"},
}
for i, kw in enumerate(keywords)
for cc in countries
]
# Chunk to the 500-task batch limit.
for start in range(0, len(tasks), 500):
chunk = tasks[start:start + 500]
resp = requests.post(f"{API}/v1/async/task/batch", headers=HEADERS, json=chunk, timeout=60)
resp.raise_for_status()
print(resp.json()["summary"])
Each task above reserves $0.00103 + $0.00071 (second page) + $0.00071 (AI Overview) = $0.00245 and is charged for the pages fetched and the overview if Google showed one.
Python: receive and verify deliveries (Flask)
Deliveries carry the GET /v1/async/task/{taskId} body. Verify Webhook-Signature over the raw bytes before parsing.
import hashlib
import hmac
import json
import os
import time
from flask import Flask, abort, request
app = Flask(__name__)
MY_DOMAIN = "exampleboard.com"
def verify(raw: bytes, header: str | None, secret: str, tolerance: int = 300) -> bool:
pairs = [part.split("=", 1) for part in (header or "").split(",") if "=" in part]
stamps = [v for k, v in pairs if k == "t"]
sigs = [v for k, v in pairs if k == "v1"]
if len(stamps) != 1 or not stamps[0].isdigit() or not sigs:
return False
if abs(time.time() - int(stamps[0])) > tolerance:
return False
expected = hmac.new(secret.encode(), stamps[0].encode() + b"." + raw, hashlib.sha256).hexdigest()
return any(hmac.compare_digest(expected, s) for s in sigs)
@app.post("/hooks/serps")
def serps():
raw = request.get_data()
if not verify(raw, request.headers.get("Webhook-Signature"), os.environ["WEBHOOK_SECRET"]):
abort(400)
delivery = json.loads(raw)
if delivery.get("test"):
return "", 204
task = delivery["task"]
if task["status"] != "COMPLETED":
print(task["idempotencyKey"], "failed:", delivery["response"]["error"]["code"])
return "", 204
result = delivery["response"]["result"]
ranks = [(r.get("page"), r.get("position")) for r in result.get("organicResults", []) if MY_DOMAIN in r.get("link", "")]
aio = result.get("aioverview")
print(task["idempotencyKey"], ranks, "aio:", None if aio is None else len(aio.get("sources", [])),
"charged:", delivery["credits"]["creditsCharged"])
return "", 204
Deliveries can repeat; deduplicate on task.id. See webhooks and batching a rank tracking pipeline.
Market targeting
| Field | Values | Notes |
|---|---|---|
country or gl |
ISO 3166-1 alpha-2 | One is required with query; different values in both is a 400. GET /v1/countries?model=google lists codes (model=aioverview for AI Overview markets). |
pages |
1 to 10 | Results pages to fetch. |
location or uule |
Google canonical City,Region,Country name, e.g. Austin,Texas,United States, or a pre-encoded UULE |
City-level targeting; mutually exclusive, and neither goes with url. Without either, Google is localized to the requested country, whatever the IP. |
hl |
Interface language, e.g. en, pt-BR |
Without it, the country’s own language (de for DE, pt-BR for BR). |
device |
desktop, mobile, ios, android |
Default desktop. |
With url, the query and gl are read from the URL, which may carry only q, gl, num, hl and uule; any other parameter (start, tbs, safe, tbm and the rest) is refused with a 400. Date and SafeSearch filtering through the URL are coming; use Google News for news results.
Include flags and what they cost
Google Search costs $1.03 per 1,000 one-page tasks on every tier, deducted from your balance.
| Option | Adds | Credits |
|---|---|---|
| one page | organicResults, ads, peopleAlsoAsk, other modules |
$0.00103 |
pages 2 to 10 |
results from further pages | +$0.00071 per extra page fetched |
include.aioverview (object, e.g. {} or {"markdown": true}) |
aioverview |
+$0.00071, when Google shows one |
include.paaAioverview: true |
markdown and sources on AI-Overview-type PAA items |
0 |
include.html |
html[]: a link to a copy of each results page (HTML snapshots) |
0 |
include.rawWire |
rawWire: the results pages as rendered, when they were captured (what it holds) |
0 |
include.screenshot |
screenshot: links to a full-page PNG of each results page, in page order (details) |
0 |
The AI Overview costs +$0.00071 only when Google shows one; paaAioverview is free. include.aioverview is enabled by the key being present, so {"markdown": false} still turns it on. The maximum is $0.00103 + 9 × $0.00071 + $0.00071 = $0.00813 for a task; a request reserves its maximum and is charged for the pages fetched. Responses with paaAioverview take longer.
Common workloads
- Rank tracking by keyword and market. See rank tracking and local rank tracking.
- AI Overview monitoring: presence, cited domains and chip grouping. See monitoring Google AI Overviews and AI Overviews with async batches and webhooks.
- Keyword research from
peopleAlsoAskandrelatedSearches. See keyword research and People Also Ask keyword research. - Competitor and ad intelligence: advertisers per keyword and block position. See competitor analysis.
- Search grounding for agents: structured results as a tool call. See web search API.
- Brand SERP monitoring: knowledge panel fields and
peopleAreSayingthreads. See brand protection and tracking SERP feature changes.
Pitfalls
Whether an AI Overview appears can change between runs of the same query. Track presence as a rate over several runs, and store null as an observation.
Results differ by market and time of day. Keep the market fixed within a series and change one dimension at a time.
Many modules are omitted rather than returned empty, so default every module read (result.get("localResults", [])).
Ten pages per keyword is $0.00742 per task, $0.00813 with the AI Overview. Most rank tracking needs the first page or two; fetch deeper pages only for keywords where you rank there.
Large keyword sets belong in tasks, which queue. Watch for 429 QUEUE_LIMIT_EXCEEDED on very large submissions. See rate limits.
How failures are billed
A request reserves its maximum possible cost and is charged the actual cost on success: base, AI Overview add-on if requested, and the pages fetched. A failed request is charged nothing and releases its reservation. A completed request with aioverview: null is a success and is charged, including the add-on. Tasks that never start within 4 hours fail uncharged (QUEUE_WAIT_EXCEEDED). See credits.
Questions
How much does a Google Search request cost?
A one-page request costs $0.00103 ($1.03 per 1,000) on every tier. Each extra page adds $0.00071, charged for the pages fetched; include.aioverview adds $0.00071 once when Google shows an overview; include.paaAioverview is free.
What happens when Google shows no AI Overview for my query?
If you requested it, result.aioverview is null. The +$0.00071 add-on is charged only when the response holds an overview, so a null overview costs nothing extra.
Can I send a Google search URL instead of a query?
Yes. Send url with a full Google web search URL on /search with a non-empty q; the query and gl are read from it. The URL may carry only q, gl, num, hl and uule; any other parameter, such as start, tbs, safe or tbm, is refused with a 400.
Can I target a city, a language or mobile results?
Yes. location (City,Region,Country, e.g. Austin,Texas,United States) or uule targets a city, hl sets the interface language, and device takes desktop, mobile, ios or android. localResults covers the desktop local pack.
How is this different from Google's Custom Search JSON API?
The Custom Search JSON API returns results from a Programmable Search Engine and, according to Google, is closed to new customers with existing customers transitioning by January 1, 2027. This endpoint returns the Google results page itself as structured data: organic results, ads, People Also Ask, local pack, knowledge graph, shopping cards and the AI Overview.