AnswerLine Start free

Engines

Google AI Mode API

Google's conversational AI Mode answer with citation pills, places, shopping cards and videos.

Price

$2.06 per 1,000 requests on every plan, deducted from your prepaid USD balance.

Endpoint

POST /v1/monitor/aimode

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

Countries

213 countries, down to the city.

Coverage

Request

curl -X POST "https://api.answerline.dev/v1/monitor/aimode" \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"prompt":"Family-friendly hotels in Lisbon","country":"PT","hl":"en"}'

Request options

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

FieldTypeDescription
prompt * string Prompt submitted to Google AI Mode, up to 2,000 characters. A longer prompt is refused with a 400 that states its length.
country string Code of the country or region used to localize the response
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 form, used for geo-targeted results, written comma-separated as 'City,Region,Country' (https://developers.google.com/google-ads/api/reference/data/geotargets lists every supported location, ~100,000 in all). Combine it with 'country' to target a specific city. Cannot be combined with 'uule'; send only one of the two.
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.
device "desktop" | "mobile" Type of device the results are fetched for
include object Flags that add extra output formats to the response (optional)
include.html boolean A copy of the AI Mode 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.markdown boolean Add a markdown version of the response to the result
include.expandProducts boolean Not available yet: a request that sets it is refused with 400 before anything runs or is charged. It is meant to look up merchant offers for the response's product clusters and return them in 'result.productResults'.
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
textstringText of the AI Mode answer
markdownstringThe AI Mode answer rendered as Markdown (present when include.markdown is true)
sourcesobject[]Sources the response cites
citationPillsobject[]Citation pills that appear inline in the AI Mode answer. Every element pairs one pill with one source: a pill backed by N sources (Google's `[Label +N]` chips) yields N elements with the same `citationPillId`, each holding that source's own `label`, `url` and `domain`. Grouping by `citationPillId` rebuilds the pills. Not present in `result` if the answer has no pills.
placesobject[]Place cards embedded inline in the response
shoppingCardsobject[]Shopping-related product details parsed from the response
inlineProductsobject[]Product cards that appear inside the AI answer text Empty on the answers served today.
videosobject[]Video cards shown inline within the AI response (YouTube for now)
mapobject[]Locations pulled from the response, enriched with GPS coordinates
adsobjectSection of sponsored ads parsed from the response

Full field definitions are in the API reference.

AI Mode answers versus Google’s developer APIs

Google AI Mode is Google Search’s conversational answer surface. This endpoint returns that answer for a prompt, market, city and language, with citation pills, places, map pins, shopping cards, videos and, when Google shows sponsored products, ads as structured fields. inlineProducts is in the schema and comes back empty: Google shows an inline product mention without a name or price.

Google’s long-standing developer route to search results is the Custom Search JSON API. Its overview (checked 2026-09-17, developers.google.com) says it returns web and image results from a Programmable Search Engine, offers 100 free queries per day and $5 per 1,000 beyond that up to 10,000 per day, is closed to new customers, and gives existing customers until January 1, 2027 to transition. Its cse.list reference (checked 2026-09-17, developers.google.com) documents gl, hl, cr and lr parameters and does not mention AI Overviews, ads, local packs or knowledge panels.

In AI Mode users read a composed response instead of scanning ten links, so whether your brand is named, and in which sentence, is the visibility metric. Citations appear as [Label +N] chips that bundle several sources; citationPills records every source behind every chip, so you can measure both “cited at all” and “cited first in a chip”. Products (shoppingCards), sponsored products (ads), local businesses (places, map) and videos sit inside the answer and vary by location and device.

An annotated response

The excerpt follows the result schema for POST /v1/monitor/aimode. Values are illustrative.

{
  "success": true,
  "result": {
    "text": "For families, look for hotels near the riverfront with interconnecting rooms...",
    "sources": [
      {
        "position": 1,
        "url": "https://www.example-travel.com/lisbon/family-hotels",
        "label": "Best family hotels in Lisbon",
        "description": "Rooms for four, pools and quiet streets..."
      }
    ],
    // One entry per (chip, source). citationPillId is 1-based; position points
    // into result.sources. redirectLink appears only when Google served a /goto link.
    "citationPills": [
      {
        "label": "Best family hotels in Lisbon",
        "citationPillId": 1,
        "url": "https://www.example-travel.com/lisbon/family-hotels",
        "domain": "example-travel.com",
        "position": 1
      }
    ],
    "places": [
      {
        "title": "Example Riverside Hotel",
        "link": "https://www.google.com/viewer/place?example",
        "placeId": "ChIJexampleLisbon",
        "index": 0,
        "rating": 4.6,
        "reviews": 2140,
        "type": "Hotel",
        "priceLevel": "€€€",
        "address": "Rua Exemplo 10, Lisboa"
      }
    ],
    "map": [
      {
        "title": "Example Riverside Hotel",
        "placeId": "ChIJexampleLisbon",
        "gps_coordinates": { "latitude": 38.707, "longitude": -9.136 },
        "rating": 4.6
      }
    ],
    // position is 0-indexed within the shopping carousel.
    "shoppingCards": [],
    // ads is an object: a section title plus the ads in it.
    "ads": { "title": "Sponsored", "ads": [] },
    "videos": [
      {
        "title": "Lisbon with kids: where to stay",
        "link": "https://www.youtube.com/watch?v=example",
        "platform": "YouTube",
        "channel": "Example Family Travel",
        "duration": "12:04"
      }
    ]
  }
}

Shape details to code for:

Synchronous calls

A synchronous call waits up to five minutes. It costs $0.00206 on every tier, the same as a task.

Python (httpx)

import os
import httpx

API = "https://api.answerline.dev"

with httpx.Client(base_url=API, headers={"Authorization": f"Bearer {os.environ['API_KEY']}"}, timeout=330) as client:
    resp = client.post("/v1/monitor/aimode", json={
        "prompt": "Family-friendly hotels in Lisbon",
        "country": "PT",
        "hl": "en",
    })
if resp.status_code != 200:
    raise RuntimeError(f"{resp.status_code} {resp.headers.get('X-Request-Id')} {resp.text}")

result = resp.json()["result"]
chips = {}
for pill in result.get("citationPills", []):
    chips.setdefault(pill["citationPillId"], []).append(pill["domain"])
print("chips:", chips)
print("places:", [(p.get("title"), p.get("rating")) for p in result.get("places", [])])
print("credits charged:", resp.headers.get("X-Credits-Charged"))

TypeScript (fetch)

const API = "https://api.answerline.dev";

const res = await fetch(`${API}/v1/monitor/aimode`, {
  method: "POST",
  headers: { Authorization: `Bearer ${process.env.API_KEY}`, "Content-Type": "application/json" },
  body: JSON.stringify({
    prompt: "best standing desk for a small apartment",
    gl: "us",
    location: "Austin,Texas,United States",
    include: { markdown: true },
  }),
  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 card of result.shoppingCards ?? []) {
  console.log(card.position, card.title, card.price?.raw ?? card.price?.value, card.store);
}
for (const ad of result.ads?.ads ?? []) console.log("ad:", ad.position, ad.title, ad.store);

Batches: async tasks and a webhook

Tasks cost $0.00206 on every tier, wait in your queue until they start, and post to your webhook. POST /v1/async/task/batch takes 1 to 500 tasks, each admitted on its own and answered by index.

Python: a city grid

import datetime
import os
import requests

API = "https://api.answerline.dev"
HEADERS = {"Authorization": f"Bearer {os.environ['API_KEY']}"}
day = datetime.date.today().isoformat()

cities = ["Austin,Texas,United States", "Denver,Colorado,United States", "Seattle,Washington,United States"]
prompts = ["emergency plumber near me", "best pizza for delivery"]
tasks = [
    {
        "taskType": "AIMODE",
        "payload": {"prompt": prompt, "country": "US", "location": city},
        "idempotencyKey": f"aim-{i}-{city.split(',')[0].lower()}-{day}",
        "webhook": {"url": "https://your-app.example/hooks/answers"},
    }
    for i, prompt in enumerate(prompts)
    for city in cities
]

resp = requests.post(f"{API}/v1/async/task/batch", headers=HEADERS, json=tasks, timeout=60)
resp.raise_for_status()
body = resp.json()
print(body["summary"], "reserved:", sum(r["credits"]["creditsToCharge"] for r in body["results"] if r["success"]))

TypeScript: receive deliveries and check your listing (Express)

Each delivery is the GET /v1/async/task/{taskId} body. Verify Webhook-Signature over the raw body before parsing.

import express from "express";
import { createHmac, timingSafeEqual } from "node:crypto";

const MY_PLACE_IDS = new Set(["ChIJexampleLisbon"]);

function verify(raw: Buffer, header: string | undefined, secret: string, toleranceSeconds = 300): boolean {
  const pairs = (header ?? "").split(",").map((part) => part.split("="));
  const stamps = pairs.filter(([k]) => k === "t").map(([, v]) => v);
  const sigs = pairs.filter(([k]) => k === "v1").map(([, v]) => v);
  if (stamps.length !== 1 || !/^\d+$/.test(stamps[0]) || sigs.length === 0) return false;
  if (Math.abs(Date.now() / 1000 - Number(stamps[0])) > toleranceSeconds) return false;
  const expected = createHmac("sha256", secret).update(`${stamps[0]}.`).update(raw).digest();
  return sigs.some((sig) => {
    const given = Buffer.from(sig, "hex");
    return given.length === expected.length && timingSafeEqual(given, expected);
  });
}

const app = express();
app.post("/hooks/answers", express.raw({ type: "application/json" }), (req, res) => {
  if (!verify(req.body, req.get("webhook-signature"), process.env.WEBHOOK_SECRET!)) return res.sendStatus(400);
  const delivery = JSON.parse(req.body.toString("utf8"));
  if (delivery.test) return res.sendStatus(204);
  const { task, response } = delivery;
  if (task.status !== "COMPLETED") {
    console.warn(task.idempotencyKey, "failed:", response.error.code);
    return res.sendStatus(204);
  }
  const ranks = (response.result.places ?? []).filter((p: any) => MY_PLACE_IDS.has(p.placeId)).map((p: any) => p.index);
  console.log(task.idempotencyKey, ranks.length ? "listed at" : "not listed", ranks);
  res.sendStatus(204);
});
app.listen(3000);

Deduplicate on task.id; see webhooks.

Geo and language targeting

Field Values Notes
country or gl ISO 3166-1 alpha-2, either case for gl One is required; sending both with different values is a 400. GET /v1/countries?model=aimode lists codes.
hl Google interface language, e.g. en, de, pt-BR Overrides the language derived from the country.
location Google canonical name, City,Region,Country City-level precision; the names come from Google Ads geotargets. Not with uule. Without it, Google is localized to the requested country.
uule Pre-encoded UULE string Alternative to location.
device desktop, mobile Default desktop.

For a region or city below the country, use location (for example Texas,United States).

Include flags and what they cost

An AI Mode task is $2.06 per 1,000 on every tier.

Flag Effect Price
include.markdown Adds markdown 0
include.html Adds html: a link to a copy of the AI Mode page (HTML snapshots) 0
include.expandProducts Not available yet: a request that sets it is refused with a 400 Not charged
include.googleGoto Deprecated, ignored 0
include.rawWire Adds rawWire: the AI Mode page as rendered, when it was captured (what it holds) 0
include.screenshot Adds screenshot: a link to a full-page PNG of the AI Mode page once the answer finished (details) 0

Common workloads

Pitfalls

location expects Google’s canonical City,Region,Country names from the Google Ads geotargets list. Copy names from that list instead of typing them, and keep a vetted list per client.

hl overrides the language derived from the country. When comparing markets, set hl explicitly; see multilingual AI answers.

shoppingCards[].position starts at 0 and inlineProducts[].position at 1. Normalize before storing ranks.

A city grid grows fast: 2 prompts × 3 cities is 6 tasks per day before repeated samples, and answers vary enough between runs that you want several per combination (AI answer volatility). There is no concurrency cap and no request rate limit; tasks queue (about 100,000 tasks of a typical prompt at a time). See rate limits.

How failures are billed

Accepted requests reserve their maximum cost (credits.creditsToCharge) and are charged the actual cost on success (credits.creditsCharged, or X-Credits-Charged on a synchronous call), never more than reserved. A failed request is charged nothing and its reservation is released, including tasks that never start within 4 hours (QUEUE_WAIT_EXCEEDED). Synchronous calls may complete and be charged after a dropped connection, so resend only if the request never reached the server. See credits.

Related: getting AI Mode answers as JSON and monitoring Google AI Overviews.

Questions

How much does a Google AI Mode request cost?

A request is $2.06 per 1,000 on every tier.

Can I target a city in AI Mode?

Yes. Send country (or gl) plus location as a Google canonical location name such as Austin,Texas,United States, or a pre-encoded uule instead of location. location and uule are mutually exclusive.

What is redirectLink in AI Mode results?

When Google serves a link as a /goto redirect, the link field holds the resolved destination and redirectLink holds Google's redirect. POST /v1/monitor/google/goto resolves a redirect link on its own and is free.

Can I set the interface language separately from the country?

Yes. hl sets Google's interface language, for example en or pt-BR, and overrides the language derived from the country, so language and result geography can differ.

Is a failed AI Mode request charged?

No. A failed request is charged nothing and its reservation is released. A completed request is charged the base price.

Other engines