AnswerLine Start free

, Tutorials · Gemini

Monitor Google Gemini answers with an API: fields and code

Monitoring Gemini means running a fixed prompt set against Google’s Gemini on a schedule, per market, and storing the answer, its sources and its inline citations as structured data you can compare over time. Below: the request, the response fields, costs, a scheduled batch and the mistakes that distort the numbers.

Terms

A source is an entry in result.sources, the list of pages Gemini attached to the answer. A citation pill is an inline chip in the answer text that points at one or more sources, returned as result.citationPills. A run is one execution of the whole prompt set across its markets.

Google’s help page for Gemini Apps notes that sources appear in or below a response when available, and that “not all responses include related links or sources” (View related sources in Gemini Apps, checked 2026-09-17). An answer without sources is a normal outcome, and your metrics need to allow for it.

The endpoint and request fields

POST https://api.answerline.dev/v1/monitor/gemini, with Authorization: Bearer <key> as described in authentication.

Field Required Notes
prompt yes The prompt sent to Gemini (up to 2,000 characters).
country yes Country code; GET /v1/countries?model=gemini lists supported ones. Gemini answers from 213 countries.
state no Two-letter US state code, only with country: "US"; DC included.
include.markdown no Adds result.markdown.
include.html no Adds result.html, a link to a copy of the Gemini page as it was when the answer was captured. No extra cost.
include.rawResponse no Adds result.rawResponse, the answer as normalized stream events; include.rawWire returns the answer stream as Gemini sent it, when it was captured.

The body accepts no other fields; unknown keys fail validation.

Step 1: send a prompt

curl

curl -X POST https://api.answerline.dev/v1/monitor/gemini \
  -H "Authorization: Bearer $ANSWERLINE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "prompt": "which email platform is best for newsletters?",
    "country": "US",
    "include": { "markdown": true }
  }'

Python (requests)

import os
import requests

API = "https://api.answerline.dev"
HEADERS = {"Authorization": f"Bearer {os.environ['ANSWERLINE_API_KEY']}"}

resp = requests.post(
    f"{API}/v1/monitor/gemini",
    headers=HEADERS,
    json={"prompt": "which email platform is best for newsletters?", "country": "US", "include": {"markdown": True}},
    timeout=360,
)
resp.raise_for_status()
result = resp.json()["result"]

print(result.get("markdown") or result.get("text"))
for source in result.get("sources", []):
    print(source["position"], source["url"], source.get("label"))
print("charged (USD):", resp.headers.get("X-Credits-Charged"))

TypeScript (@answerline/sdk)

import { Client } from "@answerline/sdk";

interface GeminiResult {
  text?: string;
  markdown?: string;
  sources?: { position: number; label?: string; url: string; description?: string }[];
  citationPills?: { citationPillId: number; position: number; url: string; domain: string; label: string; description?: string }[];
  places?: { index: number; title: string; link: string; rating?: number; reviews?: number; type?: string; price?: string }[];
}

const client = new Client({ apiKey: process.env.ANSWERLINE_API_KEY! });
const { result } = await client.monitor.gemini<GeminiResult>({
  prompt: "which email platform is best for newsletters?",
  country: "US",
});
console.log((result.sources ?? []).map((s) => s.url));

The TypeScript SDK is on npm as @answerline/sdk; the Python examples call the HTTP API with requests.

Step 2: read the response fields

A successful call returns { "success": true, "result": { ... } }.

Field Type Meaning
text string Gemini’s answer as plain text.
markdown string The answer in Markdown, when include.markdown is true.
sources[] array position, label, url, description for each source.
citationPills[] array One entry per pill-and-source pair: citationPillId, position, url, domain, label, optional description. Omitted when the answer has no pills.
places[] array Place cards for lodging and local answers.
rawResponse[] array The answer as normalized stream events, when include.rawResponse is true. result.rawWire (with include.rawWire) is the answer stream as Gemini sent it, when it was captured.

Sources versus citation pills

sources is the list; citationPills tells you where in the answer those sources are used. When one chip cites several sources, the array holds one entry per source, all sharing the same 1-based citationPillId. Each entry’s position is the 1-based index of that source in result.sources, so you can join the two. label is always present but can be an empty string when Gemini showed no title; use domain or url for identity in that case.

Rebuilding the chips:

from collections import defaultdict

def chips(result: dict) -> dict[int, list[str]]:
    grouped = defaultdict(list)
    for pill in result.get("citationPills", []):
        grouped[pill["citationPillId"]].append(pill["domain"])
    return dict(grouped)

# e.g. {1: ["example.com"], 2: ["example.org", "example.net"]}

A source that is listed but never cited inline is less visible than one inside a chip, so track “in sources” and “in a chip” separately.

Place cards

For lodging and local prompts (“quiet hotels near Kings Cross”), places[] returns index, title, link (a Google URL for the place) and, when Gemini shows them, thumbnail, rating (0 to 5), reviews, type, description and price as Gemini displays it. For hospitality and local businesses this block is the recommendation list; measure presence and index, and combine with state targeting (local AI answers).

Step 3: compute visibility metrics

from urllib.parse import urlsplit

def on_domain(url: str, domain: str) -> bool:
    host = (urlsplit(url).hostname or "").lower()
    return host == domain or host.endswith("." + domain)

def gemini_metrics(result: dict, brand: str, domain: str) -> dict:
    sources = result.get("sources", [])
    pills = result.get("citationPills", [])
    text = result.get("text", "")
    return {
        "has_sources": bool(sources),
        "mentioned": brand.lower() in text.lower(),
        "source_positions": [s["position"] for s in sources if on_domain(s["url"], domain)],
        "in_chip": any(on_domain(p["url"], domain) for p in pills),
        "place_rank": next((p["index"] for p in result.get("places", []) if brand.lower() in p["title"].lower()), None),
    }

Aggregate per prompt, market and run:

  1. Sourced-answer rate: share of answers with has_sources, which tells you how much of your prompt set can produce a citation on Gemini at all.
  2. Citation rate: among sourced answers, share where your domain appears in sources.
  3. Chip rate: share where your domain is inside a citation pill.
  4. Mention rate: share of all answers naming the brand in text, cited or not.

The difference between mention and citation is covered in mentions vs citations; for a full scoring model see AI share of voice.

Step 4: run it on a schedule

Scheduled monitoring should use async tasks: taskType: "GEMINI" with the same body as payload.

def gemini_tasks(prompts: dict[str, str], countries: list[str], day: str, hook: str) -> list[dict]:
    # prompts maps a stable prompt id to its text
    return [
        {
            "taskType": "GEMINI",
            "payload": {"prompt": text, "country": country},
            "idempotencyKey": f"gemini-{pid}-{country}-{day}",
            "webhook": {"url": hook},
        }
        for pid, text in prompts.items()
        for country in countries
    ]

tasks = gemini_tasks({"newsletter-platform": "which email platform is best for newsletters?",
                      "crm-small-team": "which CRM suits a five-person sales team?"},
                     ["US", "GB"], "2026-09-17", "https://example.com/hooks/answers")
batch = requests.post(f"{API}/v1/async/task/batch", headers=HEADERS, json=tasks, timeout=360).json()
print(batch["summary"])  # {"total": 4, "succeeded": ..., "failed": ...}

The same with the SDK, for one country:

const prompts: Record<string, string> = {
  "newsletter-platform": "which email platform is best for newsletters?",
  "crm-small-team": "which CRM suits a five-person sales team?",
};
const tasks = Object.entries(prompts).map(([pid, prompt]) => ({
  taskType: "GEMINI" as const,
  payload: { prompt, country: "US" },
  idempotencyKey: `gemini-${pid}-US-2026-09-17`,
  webhook: { url: "https://example.com/hooks/answers" },
}));
const batch = await client.createBatch(tasks);
for (const item of batch.results) if (!item.success) console.warn(tasks[item.index].idempotencyKey, item.error.code);

Each finished task is POSTed to the webhook as the GET /v1/async/task/{id} JSON; the Gemini result is under response.result. Verify the signature first (verify webhook signatures), dedupe by task.id, and return a 2xx within 15 seconds. The full pipeline design is in webhook reliability.

What it costs

Per billing, at the Starter tier:

Request Price
Gemini request $0.0006
include.markdown or include.rawResponse +0

Worked example: 100 prompts with 3 runs each is 100 x 3 x $0.0006 = $0.18. A new account’s 100 free requests cover 100 Gemini tasks, enough for 33 prompts at 3 runs each. Size larger programs with cost planning and compare tiers on pricing.

Sync or async

More on the trade-off in sync calls, async tasks or webhooks.

Pitfalls

Gemini next to the other engines

Gemini is one engine in a cross-engine program. Run the same prompt set on ChatGPT, then compare where your domain is cited on one and missing on the other. How engines pick citations in general is covered in how AI engines choose citations; reporting is covered on the AI visibility tracking page.

The request example and field summary live on the Gemini engine page; to make your first call, start with the quickstart.

Questions

Why is a Gemini source listed but never cited in the answer?

sources is the full list of what Gemini drew on; citationPills records where those sources are used inline, one entry per pill and source pair sharing a citationPillId, with position pointing back into sources. A source that never appears in a pill is less visible than one inside a chip, so track the two separately.

Why is result.sources empty for some Gemini answers?

Gemini does not attach sources to every answer, so an empty sources array is a valid result. Record it as an answer without citations rather than as an error.

Can a Gemini source label be empty?

Yes. label is always present but can be an empty string when Gemini showed no title, so identify a source by url or domain and keep label for display only.

What are Gemini place cards in the response?

result.places lists the place cards Gemini returns for lodging and local answers, with index, title, link and, when shown, thumbnail, rating, reviews, type, description and price.

Can I monitor Gemini in a specific US state?

Yes. Send state, for example CA (DC included), together with country US.

Try it on your own prompts

100 free requests on any engine, no card. One POST returns the answer, sources and citations as JSON.

Keep reading