October 6, 2026

What Are LLM Citations? How They Are Produced and What They Prove

What Are LLM Citations? How They Are Produced and What They Prove

TLDR: LLM citations are pointers from a claim in a generated answer to the evidence behind it. Models produce them three ways: prompted markers you parse, pointers an API creates for you, and attribution added after generation. The method sets the guarantee. Prompting guarantees nothing, platform features can guarantee valid pointers, and retrofitting shows only that support exists. None proves the model relied on the source, so check the page itself.

This guide starts at how the citation gets into the response, because that decides which failures to expect and which checks can catch them. For the discipline behind it, see our AI grounding guide. For coverage and support metrics, see the Grounding API guide. For one provider's response contract in depth, see the AI answer API guide. You.com sells the Web Search, Contents, Answer, and Research APIs discussed below, so check those details against the docs we link.

What are LLM citations?

An LLM citation is a pointer from a span of generated text to the evidence behind it. A usable one has three parts: the claim, a source your code can resolve (a URL or document ID), and the passage in that source that backs the claim. A bare URL sends the reader back to re-read a page. A verbatim passage with a location lets a person or a script compare claim and evidence in seconds.

Citations are not grounding. Grounding means the model generated from retrieved evidence, and a citation is the visible pointer back to it. A response can be grounded and carry no usable citations, or carry citations attached to text the model wrote from memory, so this guide treats the citation itself as something to examine.

How often cited answers fail to support their claims is measurable. A 2023 human audit by Liu et al. of Bing Chat, NeevaAI, perplexity.ai, and YouChat defined citation recall (all statements are fully supported by citations) and citation precision (every cite supports its associated statement). On average, only 51.5% of generated sentences were fully supported by citations and only 74.5% of citations supported their sentence. Those systems predate today's, so read the numbers as proof that fluent, cited answers can hide unsupported claims, not as a current benchmark. The ALCE benchmark, also from 2023, found on its ELI5 set that even top-performing models lacked complete citation support 50% of the time.

What does a citation contain?

Two properties decide how cheaply a citation can be checked: how precisely it anchors the claim, and how much evidence it carries.

Anchoring runs from coarse to fine: a source list under the answer, a numbered marker in the text, then a span. The You.com Answer API returns an answer string with markers such as [[1, 2]] and a citations array in citation order, and the Research API does the same with output.content and output.sources. OpenAI's web search tool and Gemini's Grounding with Google Search return url_citation annotations with start_index and end_index offsets. Anthropic attaches a citations list to each text block, so the block is the claim.

Evidence runs from a bare URL to a snippet to a verbatim passage with a location. The OpenAI and Gemini annotations carry a URL and a title but no passage, and the Research API's sources carry snippets. Anthropic's citations feature for documents you supply returns cited_text plus character offsets, page numbers, or block indices, and its web search tool returns cited_text of up to 150 characters. The Answer API's excerpts are, per its docs, the verbatim passages the model used. A span plus a verbatim passage lets a script test one claim against one quote. A bare URL leaves you to fetch the page and guess which claim it supports. OpenAI also exposes a sources field listing every URL the search consulted, which its docs say is often longer than the citation list, so keep the cited set and the consulted set apart.

How do models produce citations?

Three mechanisms produce citations, and they differ in who writes the pointer. That decides what can go wrong.

MechanismWho writes the pointerWhat it can guaranteeTypical failure
PromptedThe model, as plain text, because you askedNothingA marker with no matching source, or a real source that does not support the claim
Platform-madeThe API, from structured output or a verified pipelineValid pointers (Anthropic) or verified citations (You.com Answer API docs)A valid pointer to a passage that does not support the claim
RetrofittedA later step that searches for evidence after the text existsThat support exists somewhereEvidence found for a claim the model made from memory

Prompted citations. You number the retrieved sources, tell the model to cite each claim by number, and parse the markers from the answer. The You.com guide to grounding with citations describes this loop and the rules that hold up: number sources instead of naming them, tell the model to use only the sources and to say so when they do not contain the answer, and check afterward that every [N] refers to a source you sent. Ask for a marker on every claim, so an uncited sentence is visible. Perplexity's Agent API docs make the same point from the other side: markers depend on the prompt, so treat each search result's id and url as the source of truth. Because the model writes the pointer as plain text, nothing forces it to use a real index.

Platform-made citations. Here the API produces the pointer. Anthropic's docs say that with citations enabled the model outputs a standardized internal format that is parsed into cited text and document location indices, and that cited_text is extracted directly. On that basis they say citations are guaranteed to contain valid pointers to the provided documents. Chunking sets how fine a citation can be: plain text and PDFs are split into sentences, and custom content documents are used as given. The You.com Answer API runs retrieval, extraction, and synthesis in one call and, per its docs, verifies that every citation exists in the source text and supports the answer before returning it. As of October 2026, OpenAI's and Gemini's docs describe their annotation fields without a per-citation support guarantee.

Retrofitted citations. You generate first and find evidence afterward. RARR (Retrofit Attribution using Research and Revision) is the research version: it finds attribution for the output of any text generation model and post-edits unsupported content. The simple production form is an aligner: split the finished answer into claims, match each to the closest retrieved passage, attach it if the match is strong enough, and abstain if not. It works with any model, even one that never saw your sources, which is why teams use it and why it proves less.

Does a citation prove the model used the source?

No, and the distinction has a name. Wallat et al. separate citation correctness (do the cited documents support the statement?) from citation faithfulness (is the model's reliance on them genuine?). A model can write a claim from prior belief and end up with a citation that happens to agree. The authors call this post-rationalization, and their findings suggest that up to 57% of citations in current attributed answers lack faithfulness. That is one 2024 study, so treat the number as a warning, not a rate for your system. The paper also notes that earlier studies applied the two terms inconsistently, so check which sense a metric means before comparing numbers.

Map that onto the three mechanisms. A retrofitted citation is post hoc by construction: the evidence was found after the text was written, so it can be correct but cannot be faithful. A prompted or platform-made citation was produced with the passages in the model's context, so reliance is plausible, but nothing in the pointer proves it. Correctness can be checked from outside, by fetching the page and comparing. Faithfulness needs an experiment. One probe is to regenerate the answer with the cited source removed from context, and if the claim survives unchanged, treat the citation as decoration. That is a heuristic, not proof, and it costs a model call per claim, so reserve it for answers where misplaced trust is expensive.

Where it is expensive, do not stop at the citation. The Answer API and Research API guides both say to build a step that follows the cited URLs and checks the supporting text before showing claims to users in legal, financial, or medical settings.

How do you attach citations after generation?

The script below is a retrofit baseline. It turns a Web Search API response into passages, aligns each sentence of an uncited answer to its closest passage, and abstains when nothing matches. It uses only the standard library. passages_from_search reads the documented result fields: contents.highlights when the request set extraction_mode to highlights, otherwise snippets, which the API omits in that mode.

"""Retrofit citations onto a finished answer, then re-check the cited pages.

Standard library only. YDC_API_KEY is read only by the live Contents API call.
"""
import json
import math
import os
import re
import urllib.request
from collections import Counter

STOP = frozenset("a an and are as at be by for from has have in is it its of on or that the this to was were with".split())
TOKEN = re.compile(r"\d[\d,]*(?:\.\d+)?|[a-z][a-z0-9_]*")
QUOTES = str.maketrans({"\u2018": "'", "\u2019": "'", "\u201c": '"', "\u201d": '"', "\u2013": "-", "\u2014": "-"})


def stem(t):
    if t[0].isdigit():
        return t
    if t.endswith("ies") and len(t) > 4:
        return t[:-3] + "y"
    for suffix, floor in (("ing", 5), ("ed", 5), ("s", 3)):
        if t.endswith(suffix) and not t.endswith("ss") and len(t) > floor:
            return t[:-len(suffix)]
    return t


def words(text):
    return [stem(t.replace(",", "")) for t in TOKEN.findall(text.lower()) if t not in STOP]


def numbers(text):
    return {t for t in words(text) if t[0].isdigit()}


def sentences(answer):
    for line in answer.splitlines():
        for s in re.split(r"(?<=[.!?])\s+", line.strip()):
            if len(s.split()) >= 4:
                yield s


def passages_from_search(resp):
    """Flatten a Web Search API response into {url, text} passages."""
    out = []
    for hit in (resp.get("results") or {}).get("web") or []:
        texts = (hit.get("contents") or {}).get("highlights") or hit.get("snippets") or []
        out += [{"url": hit["url"], "text": t} for t in texts]
    return out


def attribute(answer, passages, threshold=0.4):
    """Attach the best passage to each sentence, or abstain. Lexical coverage, not entailment."""
    bags = [set(words(p["text"])) for p in passages]
    df = Counter(t for bag in bags for t in bag)

    def weight(t):  # terms found in every passage weigh almost nothing; unseen terms weigh most
        return math.log((len(bags) + 1) / (df[t] + 0.5))

    results = []
    for s in sentences(answer):
        terms = {t for t in words(s) if not t[0].isdigit()}
        total = sum(weight(t) for t in terms) or 1.0
        scored = [(sum(weight(t) for t in terms & bag) / total, p) for bag, p in zip(bags, passages)]
        score, best = max(scored, key=lambda x: x[0]) if scored else (0.0, None)
        status = "abstain"
        if best and score >= threshold:
            status = "number_conflict" if numbers(s) - numbers(best["text"]) else "cited"
        results.append({"claim": s, "status": status, "score": round(score, 2),
                        "url": best["url"] if status != "abstain" else None,
                        "passage": best["text"] if status != "abstain" else None})
    return results

Three choices matter. The score is claim coverage: the share of the claim's content words, weighted by rarity across your passages, that a passage contains. A long snippet dilutes cosine similarity for a short claim, so coverage fits better. Numbers are scored separately: if the words match but a number is missing from the passage, the status is number_conflict, not cited. Below the threshold the function abstains, the safe failure, because a missing citation is visible and a wrong one is not.

Run on short passages modeled on the Contents API docs, it returns the results below. The third claim is deliberately wrong.

ClaimStatusScoreWhy
The Contents API accepts up to 10 URLs in a single request.cited0.72Words and number match the Contents guide passage
Contents API pricing is $1.00 per 1,000 pages crawled.cited0.79Matches the billing passage
A request can include up to 20 URLs.number_conflict0.44Words match, but 20 is not in the passage
Rust enforces memory safety with a borrow checker.abstain0.00No passage covers it

Know the limits. Lexical coverage finds topical overlap, not entailment. It cannot see negation: a claim that says the API does not accept 10 URLs still matches the passage that says it does, and the test suite records that as a known limit. It abstains on synonyms such as costs and pricing. Treat the 0.4 threshold as a starting point to tune on labeled pairs from your own traffic, and swap in embeddings or a judge model when recall matters.

How do you check that a cited page still says it?

Citations decay. Pew Research Center found that 25% of webpages that existed at some point between 2013 and 2023 were no longer accessible as of October 2023, and about one in five pages from its 2021 sample were gone two years later. A citation that resolved at generation time can fail at read time, and a page can change while still loading. The second half of the module re-reads cited pages with the You.com Contents API.

That API takes up to 10 URLs per request at $1.00 per 1,000 pages as of October 2026, and returns an array of objects with url, title, and markdown, with markdown null when a crawl fails. By default it may serve cached content, so the script sends max_age set to 0, which the docs say always bypasses the cache and re-fetches the page. Add this code to the same file.

def _post_contents(batch, max_age):
    req = urllib.request.Request(
        "https://ydc-index.io/v1/contents",
        data=json.dumps({"urls": batch, "formats": ["markdown"], "max_age": max_age}).encode(),
        headers={"X-API-Key": os.environ["YDC_API_KEY"], "Content-Type": "application/json"},
        method="POST",
    )
    with urllib.request.urlopen(req, timeout=60) as resp:
        return json.load(resp)


def fetch_markdown(urls, max_age=0, post=_post_contents):
    """Contents API, 10 URLs per request. Returns {url: markdown or None}."""
    unique = list(dict.fromkeys(urls))
    found = {}
    for i in range(0, len(unique), 10):
        for page in post(unique[i:i + 10], max_age):
            found[page.get("url")] = page.get("markdown")
    return {u: found.get(u) for u in unique}


def squash(text):
    text = re.sub(r"!?\[([^\]]*)\]\([^)]*\)", r"\1", text or "")
    return " ".join(re.sub(r"[*_`#>]+", " ", text.translate(QUOTES).lower()).split())


def fragments(passage):
    parts = [squash(p) for p in re.split(r"\.\.\.|\u2026", passage)]
    return [p for p in parts if len(p.split()) >= 5] or [squash(passage)]


def still_there(results, pages):
    """Add live = present, drifted, or unreadable to every cited result."""
    for r in results:
        if r["status"] != "cited":
            continue
        page = squash(pages.get(r["url"]))
        if not page:
            r["live"] = "unreadable"
        else:
            r["live"] = "present" if any(f in page for f in fragments(r["passage"])) else "drifted"
    return results


if __name__ == "__main__":
    search = json.load(open("search.json"))  # a saved Web Search API response
    answer = open("answer.txt").read()        # the model's uncited answer
    results = attribute(answer, passages_from_search(search))
    pages = fetch_markdown([r["url"] for r in results if r["status"] == "cited"])
    for r in still_there(results, pages):
        print(r["status"], r.get("live", "-"), r["score"], r["claim"][:70])

still_there labels each cited result present, drifted, or unreadable. Unreadable means no markdown came back for that URL: a failed crawl, a removed page, or a redirect that changed the returned URL, since results are matched by URL. Treat it as unverifiable, not false. Drifted means the page loaded but none of the passage fragments appear in it. Markdown conversion can differ from the text the search index stored, so a drifted flag is a prompt to look, not a verdict. At five cited pages per answer, a live check costs about $0.005, so auditing 100 answers a day (500 pages) costs about $0.50.

These checks show that the evidence you attached exists and still loads. They do not prove entailment, and they say nothing about reliance.

Which citation mechanism should you use?

Pick by what you control and what you are willing to verify yourself.

SituationMechanismStart with
One cited answer from the public web, with little plumbingPlatform-madeThe Answer API, $5.00 per 1,000 calls as of October 2026, with a citations array and excerpts
Multi-source questions where depth matters more than speedPlatform-madeThe Research API, where research_effort sets depth and price, from $12 per 1,000 at lite to $50 at standard as of October 2026
Your own documents, with sentence-level traceabilityPlatform-madeAnthropic document citations, checking cited_text against your source text
Your choice of model and prompt over web evidencePromptedWeb Search API results, numbered sources, and marker validation, as in our RAG with web search guide
Answers that already exist without citationsRetrofittedAn aligner like the one above, with results labeled correct at best

Whichever you pick, keep the citation as an object in your data model: the claim, the source, the passage, and the mechanism that produced it. Store the retrieval time and a hash of the page text too, so a later check can tell a changed page from a bad citation. For the wider reliability stack, see the AI hallucination prevention guide.

Related Guides

Frequently Asked Questions

LLM citations are pointers from a claim in a generated answer to the evidence behind it. A usable citation has three parts: the claim, a source your code can resolve such as a URL, and the passage in that source that backs the claim. The more evidence a response carries, the cheaper it is to check.

No. A plain model call returns text. Citations appear when the system around the model supplies sources and returns pointers to them, as web search tools, document citation features, and cited-answer APIs do. Even then, a pointer shows what the answer was matched to, not that the source supports the claim or that the model relied on it.

Yes, in two ways. A prompted model can write a marker that matches no source you retrieved, so check that every marker maps to a source you sent. Or it can attach a real source to a claim the page does not make. Platform features reduce the first problem, but only reading the page catches the second.

Choose a mechanism. For one call, use an API that returns citations, such as the Answer API. To control the prompt, number your retrieved sources, ask for a marker on every claim, and validate each marker against the sources you sent. For answers that already exist, align each sentence to evidence and abstain when nothing matches. Then re-read the cited pages.

Grounding means the model generated from retrieved evidence. A citation is the visible pointer back to that evidence. A response can be grounded with no usable citations, or carry citations attached to text the model wrote from memory, so audit the citation itself instead of inferring grounding from its presence.

    Share Article:

  1. LI Test

  2. LI Test

Related resources.

What Is Grounded Generation? Cited Output for AI Applications

What Is Grounded Generation? Cited Output for AI Applications

October 12, 2026

Blog

What Is Answer Engine Optimization? A Practical Guide for Content Teams

What Is Answer Engine Optimization? A Practical Guide for Content Teams

October 7, 2026

Blog

Vector Search vs Keyword Search for RAG Pipelines

Vector Search vs Keyword Search for RAG Pipelines

September 29, 2026

Blog

5 RAG Chunking Strategies in 2026: Fidelity, Cost, and Complexity

5 RAG Chunking Strategies in 2026: Fidelity, Cost, and Complexity

September 29, 2026

Blog

What Is a Legal Research API? Building Cited Legal Research Into Applications

What Is a Legal Research API? Building Cited Legal Research Into Applications

September 16, 2026

Blog