Skip to main content
Compare a completed AI answer with the pages behind it. The result shows the searches the engine ran, the pages it retrieved and the ones it cited, the brands the answer names and the pages that name them, where your brand stands, and prioritized actions. It also maps the paragraphs of each cited page to the parts of the answer they correspond to, with an explanation for every link, and sums up what the cited pages have in common. This is post-hoc correspondence analysis of an existing answer — not proof that a source caused the model to generate a sentence, and not a score of a page’s quality. Treat it as evidence to inspect, not a verdict.
Poll GET /v1/async/task/:id, or supply webhook.url when submitting; the task reports taskType: "CITATION_ANALYSIS". Do not submit this analysis through POST /v1/async/task; use the dedicated endpoint. Earlier names, including GEO Audit’s, keep working: POST /v1/geo-audit, POST /v1/source-influence and POST /v1/research, the GEO_AUDIT, SOURCE_INFLUENCE and CITATION_ATTRIBUTION task types, and include.geoAudit and include.sourceInfluence; responses always use the new names.

Analyze a task you already ran

If the answer came from this API, send its task id instead of copying the answer and citations out of the result:
The task must be one of yours, COMPLETED, and still readable through GET /v1/async/task/:id. Its answer Markdown (text when the engine returned no Markdown) becomes answer, and its cited URLs become citations in the engine’s order: the first 100, with at most 10 discussion threads. Product and video card placeholders (googleusercontent.com links) and Google search links are skipped, since they have no page to read. The task’s prompt and country and its engine are used unless you send them. Sending taskId together with answer or citations is rejected with 400, and so is a task that is unfinished or has no answer text or cited URL. An unknown or expired task id returns 404.

Analyze every answer as you collect it

Add include.citationAnalysis: true to a collection request, and the analysis runs on the answer as soon as it is collected. It works with the answer engines (CHATGPT, GEMINI, PERPLEXITY, GOOGLE_AIO, GOOGLE_AIMODE, NAVER_AI_BRIEF, NAVER_AI_TAB, BING_SEARCH, BING_COPILOT), in single tasks, task batches, grids and POST /v1/batches.
The analysis reads the answer and its cited URLs the same way taskId does above. The answer’s result then carries response.citationAnalysis: the analysis task exactly as GET /v1/async/task/:id returns it, with task, credits and response or error.
  • Webhook: sent once, after the analysis finishes, with the answer and the analysis together.
  • Polling: the answer task is COMPLETED as soon as the answer is ready. Keep polling until response.citationAnalysis.task.status is COMPLETED or FAILED.
  • Batches complete when every answer and its analysis have finished.
  • No analysis: when the answer cites no URL to read, or your credits or the key’s daily limit cannot cover the analysis hold, response.citationAnalysis is { "error": "…" } and the answer is delivered right away.
  • Billing: the analysis is charged on its own, by usage like any Citation Analysis task. The answer’s charge does not change. A key limited to certain task types must also allow CITATION_ANALYSIS.

Payload

With taskId or include.citationAnalysis, these fields are filled from the answer’s raw data. For CHATGPT, the engine’s searches become queries and every page it retrieved becomes a citation with cited, position, citedText, publishedAt and query; the same URL with different tracking parameters is merged. For Google AI Overview, the overview’s sources are cited and organic results outside them are non-cited, with position set to their organic rank. Other engines provide cited sources only.
The request rejects analysis with 400. Insights are included in every result.

Result

Results stored before v4 keep their earlier shape, without a version field.

version

Always 4 for this shape.

answer

The answer you submitted, unchanged. answerRanges are offsets into this exact string: JavaScript UTF-16 code units, start inclusive, end exclusive. Slice it as-is — answer.slice(start, end) — instead of re-splitting the text. Each links[] range identifies a whole semantic unit of your answer: a sentence, list item or table cell. Each brands[] range is the exact name as written. Boundaries refer to the original text.

queries[]

The searches the engine ran for this answer. Each has an id, its text, and sourceIds: the sources that search found. Empty when the engine exposed no searches and you sent none.

sources[] — one entry per page

  • id — opaque identifier, unique within the result.
  • url — the page URL; domain — its host.
  • channel — who controls the page, judged from its host:
    • own — your brand’s own site (exactly when own is true).
    • brand — the own site of another brand the result names.
    • editorial — a publisher; only the publisher can change the page. Hosts that are not recognized are reported as editorial.
    • retailer — an online store or marketplace.
    • review_platform — a customer or business review site.
    • community — a forum or Q&A site.
    • video — a video platform.
    • social — a social network.
    • blog_platform — a hosted blog service.
    • reference — an encyclopedia or wiki; never the target of an action.
  • cited — true when the answer cites the page, false when the engine only retrieved it.
  • position — the engine’s own rank for the page, or null when it gave none.
  • queryIds[] — the searches that found the page.
  • publishedAt — the publication date as the engine reported it, or null.
  • citedText[] — passages the engine itself reported citing; [] when none.
  • brandIds[] — brands named on the readable page, its title or citedText.
  • own — true when the page is on your brand’s own site.
  • paragraphs[] — the paragraphs of a cited page included in the result. Each has an id (globally unique across the whole result) and the text of the paragraph. A paragraph can be included with no link at all. Always empty for non-cited pages.
  • error — present only when the page could not be read. paragraphs is then empty, and the string is a short reason for that source.

brands[]

Every brand the result tracks: your brand (role: "target"), your competitors ("competitor"), and other brands the answer names ("answer").
  • rank — the order of the brand’s first name span in the answer, starting at 1; null when the answer does not name the brand.
  • answerRanges[] — the exact spans where the answer names the brand or one of its product names, in the same UTF-16 offsets, so answer.slice(start, end) returns the name as written. Matching ignores case and takes whole words (except in scripts written without spaces); it covers headings and table cells, never link URLs or image markup. Unlike links[] ranges, these are not whole answer units.
  • citedSourceIds[] — cited sources whose page names it.
  • retrievedSourceIds[] — non-cited sources whose page names it.
  • ownSourceIds[] — sources on the brand’s own site; filled for the target only.
  • paragraphId — the paragraph this link is about. It always points at a paragraph that exists in sources[].
  • answerRanges[] — the parts of the answer this paragraph corresponds to, as start/end pairs in the same UTF-16 offsets as above. A link may carry several ranges; the same answer text is not repeated across them. Each range is a whole answer unit (sentence, list item or table cell).
  • explanation — a short sentence describing the correspondence: what the paragraph states about that part of the answer.

visibility

Where your brand stands in this answer; null when no brand was sent.
  • brandId — your brand in brands[].
  • stage — one of:
    • mentioned — the answer names your brand.
    • on_cited_page — the answer does not name it, but a cited page does.
    • on_retrieved_page — only pages the engine retrieved without citing name it.
    • not_found — no page that was read names it.
  • rank — your brand’s rank in brands[].
  • answerBrands — how many brands the answer names.

actions[]

Prioritized next steps for your brand; [] when no brand was sent. priority starts at 1 (do first) and has no gaps. Actions are ordered by control: what your brand controls first (owned), then platforms it can post on (platform), then outreach (outreach). summary is one sentence and steps[] holds one to five concrete steps, both in the answer’s language. sourceIds, brandIds, queryIds and linkIds point at the evidence in this result. brandIds names your brand, or the rival brands those pages name. Actions never suggest fake or undisclosed reviews, posing as an independent reviewer, buying links, or editing pages your brand does not own. Third-party pages that name your brand but were not cited are listed in brands[].retrievedSourceIds, not as actions.

insights[]

Up to four short summaries of what the pages add up to: which channels were cited, the format, freshness or criteria the cited pages share, how the answer’s brand order compares with what the cited pages cover, and where your brand sits. sourceIds names the pages and linkIds the links each summary rests on; at least one of them is non-empty, and they only name entries in this result. Insights describe correspondence; they are not advice.

Reading the result correctly

  • No link is not a verdict. A paragraph without a link simply did not match an answer range, and an answer part with no range means no paragraph was linked to it. Neither says the page is irrelevant, unused or untrustworthy.
  • An unread page stays uncertain. When sources[].error is set, that citation was excluded from the analysis; do not treat its lack of links as a negative finding. Retry it or supply its body if it matters.
  • Correspondence, not causation. The result describes how an existing answer and existing page text line up after the fact, and which brands those pages name. It does not measure generation influence, score the page, or report how the answer was produced.
  • not_found means not on any page read. It does not mean your brand is absent from the web.
  • Retrieval data depends on the engine. Non-cited pages appear only when the engine exposes them (ChatGPT, Google AI Overview) or you send cited: false pages. Otherwise every source is cited, and your own page can only appear as cited.
  • Channels are judged from the host. channel is inferred from the page’s host, not from reading who wrote it.
  • Actions are suggestions, not guarantees. They are prioritized from the pages listed in this result; acting on one does not ensure the answer changes.
  • Ids are internal. Do not persist s…/q…/b…/l…/a… identifiers as stable keys across runs; they are scoped to a single result.

Execution and limits

  • Size is not a failure mode. Anything the request contract accepts — an answer up to 200,000 characters with up to 100 citations — is analyzed. Long answers, large source sets and very long pages are handled by splitting the work, not by rejecting the task.
  • Depth scales with the answer, not the page. A long page does not produce more links than the answer can support: the analysis concentrates on the passages most likely to correspond to the answer, and every source that shows a correspondence is represented. On a large page, or on a page that repeats the same statement in navigation, listings and reviews, expect the strongest passages rather than every occurrence.
  • Markup is not evidence. Sitemaps, link indexes and image galleries contribute no links; a page made only of those is reported with no paragraphs rather than with invented correspondence.
  • A source that could not be read is reported in sources[].error without failing the task if another source is readable. The task fails when no source could be read, on an analysis failure, on a timeout, or on a service configuration error.
  • Requests are metered by usage, like the rest of the API. The submit response shows the temporary credit hold; the final charge is settled when the task reaches a terminal state.