Citations and grounding
Citations are Claude’s native mechanism for producing grounded responses — every claim in the output can be tied back to a specific span in a source document. The model emits inline citation blocks that reference document indices and span coordinates (char range for plain text, page range for PDF, content-block range for custom content). Unlike prompt-based “tell Claude to cite,” the native feature parses citations into structured fields, guarantees valid pointers, and (per Anthropic’s internal evals) “significantly more likely to cite the most relevant quotes.”
See also
- vision-and-multimodal — document blocks (PDF, plain text, custom content) are the citation sources
- tool-use-and-function-calling —
search_resultcontent blocks for tool-driven grounding - prompt-caching — cache document blocks; citations work normally on cached docs
- rag-embeddings-vector-search — RAG patterns that integrate with this
1. The model
You provide → documents (with citations.enabled=true)
Claude returns → text blocks, each with a citations array
Each citation → references one or more spans in one or more documents
Citations are structured pointers, not regenerated quotes. The cited_text field on each citation is extracted directly from the source — meaning:
- It’s a verified quote (no hallucination of source text).
- It does not count toward output tokens — significant savings vs prompt-based approaches that ask Claude to repeat quotes.
- It’s available on the next turn at no input-token cost either.
2. Enabling citations
Set citations.enabled: true on every document in the request (currently must be on all-or-none):
client.messages.create(
model="claude-opus-4-7",
max_tokens=1024,
messages=[{
"role": "user",
"content": [
{
"type": "document",
"source": {
"type": "text",
"media_type": "text/plain",
"data": "The grass is green. The sky is blue.",
},
"title": "My Document",
"context": "This is a trustworthy document.",
"citations": {"enabled": True},
},
{"type": "text", "text": "What color is the grass and sky?"},
],
}],
)All currently-active models support citations except Haiku 3 (which is retired anyway).
3. Three document types, three citation shapes
| Document type | source.type | Chunking | Citation type | Coordinates |
|---|---|---|---|---|
| Plain text | text or file (text file) | Sentence (automatic) | char_location | start_char_index / end_char_index (0-indexed, exclusive end) |
base64 / url / file with application/pdf | Sentence (automatic) | page_location | start_page_number / end_page_number (1-indexed, exclusive end) | |
| Custom content | content (list of text blocks) | None — your blocks are the chunks | content_block_location | start_block_index / end_block_index (0-indexed, exclusive end) |
Plain text
{
"type": "document",
"source": {
"type": "text",
"media_type": "text/plain",
"data": "Plain text content...",
},
"title": "Document Title",
"context": "Trusted source",
"citations": {"enabled": True},
}Returns citations like:
{
"type": "char_location",
"cited_text": "The exact text being cited",
"document_index": 0,
"document_title": "Document Title",
"start_char_index": 0,
"end_char_index": 50,
}{
"type": "document",
"source": {
"type": "base64",
"media_type": "application/pdf",
"data": base64_encoded_pdf,
},
"title": "Quarterly report Q3 2025",
"citations": {"enabled": True},
}Returns:
{
"type": "page_location",
"cited_text": "Revenue grew 12% YoY.",
"document_index": 0,
"document_title": "Quarterly report Q3 2025",
"start_page_number": 5,
"end_page_number": 6,
}Custom content
The most precise option — your provided blocks become the chunks, no further chunking.
{
"type": "document",
"source": {
"type": "content",
"content": [
{"type": "text", "text": "First bullet point"},
{"type": "text", "text": "Second bullet point"},
{"type": "text", "text": "Third bullet point"},
],
},
"title": "Q3 highlights",
"citations": {"enabled": True},
}Returns:
{
"type": "content_block_location",
"cited_text": "First bullet point",
"document_index": 0,
"document_title": "Q3 highlights",
"start_block_index": 0,
"end_block_index": 1,
}When to use custom content: bullet lists, transcripts, code snippets, tabular rows, RAG chunks — anything where sentence-chunking would split meaningfully.
4. Response structure
Citations appear inline. The response content is a list of text blocks; each can carry its own citations array:
{
"content": [
{"type": "text", "text": "According to the document, "},
{
"type": "text",
"text": "the grass is green",
"citations": [{
"type": "char_location",
"cited_text": "The grass is green.",
"document_index": 0,
"document_title": "Example Document",
"start_char_index": 0,
"end_char_index": 20,
}],
},
{"type": "text", "text": " and "},
{
"type": "text",
"text": "the sky is blue",
"citations": [{
"type": "char_location",
"cited_text": "The sky is blue.",
"document_index": 0,
"document_title": "Example Document",
"start_char_index": 20,
"end_char_index": 36,
}],
},
{"type": "text", "text": "."},
]
}To render with footnotes:
output = []
notes = []
for block in response.content:
if block.type == "text":
if block.citations:
for cit in block.citations:
notes.append(f"[{len(notes)+1}] \"{cit.cited_text}\" — {cit.document_title}")
output.append(f"{block.text}[^{len(notes)}]")
else:
output.append(block.text)
print("".join(output))
print("\n\nFootnotes:\n" + "\n".join(notes))5. Streaming with citations
Citations stream via citations_delta events:
event: content_block_delta
data: {"type": "content_block_delta", "index": 0,
"delta": {"type": "text_delta", "text": "According to..."}}
event: content_block_delta
data: {"type": "content_block_delta", "index": 0,
"delta": {"type": "citations_delta",
"citation": {"type": "char_location", "cited_text": "...", ...}}}
event: content_block_stop
data: {"type": "content_block_stop", "index": 0}
Each citations_delta is appended to the citations[] of the current text block.
6. Citation indices
document_index: 0-indexed across alldocumentblocks in the request, spanning all messages. So if you have 5 docs in message 1 and 3 in message 5, total = 8 indices [0..7].start_char_index/end_char_index: 0-indexed, exclusive end.[0, 20)= first 20 chars.start_page_number/end_page_number: 1-indexed, exclusive end.[5, 6)= page 5 only.[5, 7)= pages 5–6.start_block_index/end_block_index: 0-indexed, exclusive end.
7. Token economics
- Slight input-token increase from added system prompt + document chunking metadata.
cited_textis free for output tokens — major savings vs asking Claude to quote in plain text.cited_textis also free on subsequent turns’ input when you pass the prior assistant message back.- Pair with prompt caching: cache the docs (place
cache_controlon the document block) for 90 % input-token savings on the document content.
8. Caching with citations
Cache the document blocks:
{
"type": "document",
"source": {"type": "text", "media_type": "text/plain", "data": long_doc},
"citations": {"enabled": True},
"cache_control": {"type": "ephemeral"},
}Subsequent requests with the same document hit cache (0.1× input cost); citations still resolve normally.
9. Compatibility
- Works with: prompt caching, token counting, batch processing, web search, web fetch, file uploads (Files API), most tools.
- Does NOT work with: Structured Outputs (
output_config.format). Citations interleave citation blocks with text, incompatible with strict JSON schema constraints. Enabling both returns 400.
10. title and context — non-citable metadata
Both go to the model but cannot be cited from:
title(short, e.g. “Q3 report”) — appears indocument_titlefield of citationscontext(longer free-text) — for orienting Claude (e.g. “This is summarized notes from a meeting” or “This is a primary source”)
Use context for RAG with mixed-trust sources: tag authoritative docs vs scratch notes.
11. Search result blocks (search_result)
A separate content-block type for tool-returned grounding. Used when a tool returns search hits and you want each hit citable:
{
"role": "user",
"content": [{
"type": "tool_result",
"tool_use_id": "toolu_01",
"content": [{
"type": "search_result",
"title": "Result title",
"source": "https://example.com/article",
"content": [{"type": "text", "text": "Snippet body..."}],
}],
}],
}Citations on subsequent turns reference these via search_result_location:
{
"type": "search_result_location",
"cited_text": "...",
"search_result_index": 0,
"source": "https://example.com/article",
"start_block_index": 0,
"end_block_index": 1,
}12. RAG integration pattern
End-to-end pattern for a RAG QA system with native citations:
def rag_query(question: str, embeddings_index, k: int = 5):
chunks = embeddings_index.search(question, k=k)
documents = [
{
"type": "document",
"source": {"type": "content", "content": [
{"type": "text", "text": chunk.text}
]},
"title": chunk.metadata["source"],
"context": f"Source: {chunk.metadata['source']}, retrieved at {now}",
"citations": {"enabled": True},
"cache_control": {"type": "ephemeral"}, # cache shared docs
}
for chunk in chunks
]
response = client.messages.create(
model="claude-opus-4-7",
max_tokens=1024,
messages=[{
"role": "user",
"content": documents + [{"type": "text", "text": question}],
}],
)
return {
"answer": "".join(b.text for b in response.content if b.type == "text"),
"citations": [
{"text": cit.cited_text, "source": cit.document_title,
"chunk_index": cit.document_index}
for b in response.content if b.type == "text"
for cit in (b.citations or [])
],
}13. Common gotchas
citations.enabledmust be all-or-none in a request — mixing enabled and disabled documents returns 400.- PDF page-number
endis exclusive —[5, 6)= page 5 only, not pages 5–6. Source of off-by-one bugs. char_locationindices are 0-indexed butpage_locationis 1-indexed — be careful when normalizing.- Custom content blocks aren’t sentence-chunked — Claude can only cite at the block level you provided. Split aggressively if you want sentence-level citations.
- Scanned PDFs without OCR layer can’t be cited — only OCR’d / text-extractable PDFs work.
- Image citations not supported — PDF figures and image content blocks return text citations only or none.
- Citations + Structured Outputs incompatible — pick one.
document_indexspans all messages — easy to miscount if docs are spread across turns.cited_textis free, but the source document is not — large docs still cost input tokens unless cached.
14. Further reading
- Citations — canonical doc
- PDF support — PDFs as document sources
- Files API — alternative to inline base64 PDFs
- Token counting — pre-flight cost calc
- Prompt caching with citations