AnswerLine Start free

Engines

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.

AI Overview, charged only when the response holds one: +$0.71 per 1,000

Each page after the first (up to 10): +$0.71 per 1,000

Each page with a search operator (site:, intitle:…): +$2.84 per 1,000

Endpoint

POST /v1/monitor/google

Or queue it with POST /v1/async/task and taskType: "GOOGLE".

Countries

213 countries, down to the city.

Coverage

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

* required. Nested fields are written with a dot, e.g. include.markdown.

FieldTypeDescription
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 resultTypeDescription
organicResultsobject[]Google organic results
adsobject[]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.
peopleAlsoAskobject[]Google's "People also ask" questions
peopleAreSayingobject[]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.
relatedSearchesobject[]Suggested related searches
knowledgeGraphobjectKnowledge 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.
localResultsobject[]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.
shoppingCardsobject[]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.
recipesobject[]The cards of Google's Recipes carousel, in the order shown. Absent when the page shows none.
aioverviewobjectData from Google's AI Overview, when requested; null if no AI Overview could be obtained after retrying.

Full field definitions are in the API reference.

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:

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

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.

Other engines