Claude Citations feature returning empty or malformed citations — RAG grounding not working (2026) — Fix Guide (2026) | AI Error Hub
Home Providers Claude Citations malformed
Claude Citations · Inline Severity: Medium HTTP 200

Claude Citations — empty citations, wrong offsets, or missing grounding

Citations are Claude's built-in RAG grounding mechanism — the model returns not just an answer but pointers to the exact source spans it used. When they fail to appear, the ground truth silently disappears too.

By Sana K. · Cloud AI Reliability Engineer Published 2026-07-26 Verified 2026-07-26

Quick fix (TL;DR)

Resolution: Claude Citations attaches citations arrays to text blocks in the response, each with cited_text, document_index, and character offsets. Requires (a) documents provided via document content blocks with citations.enabled: true, (b) the correct beta header, and (c) sensible source formatting. Fix empty citations by structuring documents in the expected shape, and use plain-text or PDF source types — not raw string prompts.

Real error messages you'll see

These are the exact strings returned by the Claude API service and its SDKs when this error occurs. Copy-paste-searching any of them should land on this page.

Citations empty on grounded response
TextBlock(
    type="text",
    text="The Q3 revenue was $4.2M.",
    citations=[]                       # ← empty — grounding not attached
)
Citations feature not enabled
anthropic.BadRequestError: Error code: 400 - {'type': 'error', 'error': {'type': 'invalid_request_error', 'message': "'citations' is not supported without the "
"'citations-2025-01-14' beta header."}}
Wrong document block shape
anthropic.BadRequestError: Error code: 400 - {'type': 'error', 'error': {'type': 'invalid_request_error', 'message': 'citations.enabled requires source.type to be text or content, not base64.'}}

Reference

Document block shapes that support citations

Source typeCitations supportBest for
{"type": "text", "media_type": "text/plain", "data": "..."}YesPlain-text sources
{"type": "content", "content": [...]}Yes — chunked content blocksPre-chunked documents
{"type": "file", "file_id": "..."}Yes (PDF/text)Files API uploads
{"type": "base64", "media_type": "application/pdf", "data": "..."}Yes (with beta)Inline PDFs

Citation object structure

FieldTypeMeaning
typestringchar_location | page_location | content_block_location
cited_textstringExact substring from the source
document_indexintWhich document (0-based) in the messages array
document_titlestringTitle attached at upload
start_char_indexintOffset in the source (text sources)
end_char_indexintOffset in the source (text sources)
start_page_number / end_page_numberintPDF page number range

Root causes, ranked by frequency

Based on developer reports across Claude API forums, GitHub issues, and Anthropic community during 2025–2026.

  • 26%
    citations.enabled not set on the document block. Simply providing a document without opting in gives grounding but no citation objects.
  • 18%
    Beta header missing. Citations require the citations beta header on the request.
  • 14%
    Documents provided as system prompt string instead of user message content. Citations only attach to content blocks in messages, not text embedded in the system prompt.
  • 10%
    Answer generated from parametric knowledge, not the documents. Model chose to answer from its training rather than the provided docs — citations empty because none apply.
  • 8%
    Documents provided but the answer paraphrases too heavily. When Claude cannot pinpoint a source span, it omits the citation. Better prompting can encourage tighter grounding.
  • 7%
    Source encoding issue. Non-UTF8 text sources or PDFs with unusual encoding cause offset mismatches that surface as empty cited_text.
  • 7%
    Multiple documents but only one cited. Model latched onto one document; others are ignored despite relevance. Symptom of poor document ordering or overlong context.
  • 10%
    Streaming client discards citations. Some SDK versions drop the citations sub-object from the streamed content_block. Update to a version that surfaces them.

Fixes — copy-paste solutions

Fix #1

Enable citations on document blocks with the beta header

Explicit opt-in — the feature does not turn on by default.

Set the beta header on the client, provide documents as document content blocks in the user message, and mark each with citations.enabled: true. Every referenced source needs the flag.

citations_enabled.py
import anthropic

client = anthropic.Anthropic(
    default_headers={"anthropic-beta": "citations-2025-01-14"}  # verify current beta
)

def ask_with_citations(question: str, sources: list[dict]) -> anthropic.types.Message:
    """
    sources: [{"title": "Q3 2025 earnings", "text": "..."}]
    """
    content = []
    for src in sources:
        content.append({
            "type": "document",
            "source": {
                "type": "text",
                "media_type": "text/plain",
                "data": src["text"],
            },
            "title": src["title"],
            "citations": {"enabled": True},   # opt-in per document
        })
    content.append({"type": "text", "text": question})

    return client.messages.create(
        model="claude-opus-4-7",
        max_tokens=1000,
        system="Answer strictly from the provided sources. Cite every claim.",
        messages=[{"role": "user", "content": content}],
    )

response = ask_with_citations(
    "What was Q3 revenue and headcount?",
    [
        {"title": "Q3 2025 earnings", "text": "Revenue in Q3 2025 was $4.2M ..."},
        {"title": "Ops report Q3", "text": "Total headcount at end of Q3 was 47 ..."},
    ],
)

for block in response.content:
    if block.type == "text":
        print(block.text)
        for cite in block.citations or []:
            print(f"  [Source #{cite.document_index}: '{cite.cited_text}']")
For every source block you want citations from, citations.enabled: true is mandatory. Sources without the flag can still influence the answer but produce no citation objects.
Fix #2

Chunk long documents into content blocks for finer-grained citations

Citations point to whole document text by default — chunks improve granularity.

Instead of one huge text source, provide many content-typed document blocks — each a paragraph or section. Citations then point to the specific chunk rather than a huge offset range.

chunked_citations.py
import anthropic
from typing import Iterable

client = anthropic.Anthropic(
    default_headers={"anthropic-beta": "citations-2025-01-14"}
)

def chunked_document(chunks: Iterable[str], title: str) -> dict:
    """Wrap a list of chunks as a content-typed document with citations."""
    return {
        "type": "document",
        "source": {
            "type": "content",
            "content": [
                {"type": "text", "text": chunk}
                for chunk in chunks
            ],
        },
        "title": title,
        "citations": {"enabled": True},
    }

# Build a chunked document from paragraphs
paragraphs = [
    "The company was founded in 2018.",
    "Q3 2025 revenue was $4.2M, up 18% YoY.",
    "Headcount at end of Q3 was 47.",
    "The board consists of five directors...",
]

doc = chunked_document(paragraphs, title="Q3 2025 earnings")

response = client.messages.create(
    model="claude-opus-4-7",
    max_tokens=500,
    system="Answer from the provided document. Every fact needs a citation.",
    messages=[{"role": "user", "content": [
        doc,
        {"type": "text", "text": "What was Q3 revenue?"},
    ]}],
)

for block in response.content:
    if block.type != "text":
        continue
    print(block.text)
    for cite in block.citations or []:
        # For content-typed sources, cite includes block start/end indices
        print(f"  [doc #{cite.document_index}, block "
              f"{cite.start_block_index}-{cite.end_block_index}: '{cite.cited_text}']")
Chunk sizes of 100-500 tokens strike the best balance — small enough for precise citations, large enough that Claude has coherent context per chunk. Very short chunks (<50 tokens) tend to produce fragmentary citations.
Fix #3

Force grounded responses with prompt engineering + strict validation

When you need every claim cited, engineer the prompt and verify at the client.

Ask explicitly for cited claims in the system prompt, then verify at the client that every text block contains at least one citation. Reject and retry ungrounded responses.

strict_grounding.py
import anthropic

client = anthropic.Anthropic(
    default_headers={"anthropic-beta": "citations-2025-01-14"}
)

STRICT_SYSTEM = """
You answer strictly from the provided documents.

Rules:
1. Every factual claim must have a citation.
2. If the documents do not contain the answer, say so exactly:
   "The provided sources do not contain this information."
3. Do NOT use your parametric knowledge — only the documents.
"""

def call_with_strict_grounding(question: str, docs: list[dict],
                              max_attempts: int = 2) -> anthropic.types.Message:
    for attempt in range(1, max_attempts + 1):
        content = [
            {
                "type": "document",
                "source": {"type": "text", "media_type": "text/plain", "data": d["text"]},
                "title": d["title"],
                "citations": {"enabled": True},
            }
            for d in docs
        ] + [{"type": "text", "text": question}]

        response = client.messages.create(
            model="claude-opus-4-7",
            max_tokens=1000,
            system=STRICT_SYSTEM,
            messages=[{"role": "user", "content": content}],
        )

        # Verify: every text block with non-trivial content has ≥1 citation
        # or explicitly says the docs do not contain the answer
        acceptable = True
        for block in response.content:
            if block.type != "text":
                continue
            text = block.text.strip()
            if not text:
                continue
            has_citations = bool(block.citations)
            disclaims = "provided sources do not contain" in text.lower()
            if not (has_citations or disclaims):
                acceptable = False
                break

        if acceptable:
            return response
        print(f"[attempt {attempt}] response contains ungrounded claim, retrying")

    return response  # last attempt, even if imperfect
This pattern is stricter than the default. For consumer chat it can feel over-formal; for legal, medical, and compliance-sensitive use cases it is the correct default.

Prevention checklist

Ship these seven safeguards once and this error stops appearing in your logs.

  • Enable citations with the beta header and per-document citations.enabled: true — feature is opt-in on both sides.
  • Provide sources in user messages as document blocks, not embedded in the system prompt.
  • Chunk long documents into content-typed blocks (100-500 tokens each) for precise citations.
  • Verify at the client that every text block has ≥1 citation (or an explicit no-source disclaimer).
  • Log citation counts per response as a grounding-quality SLI; investigate when it drops.
  • When streaming, use an SDK version that surfaces per-chunk citations objects.
  • For high-stakes use cases (legal, medical), retry ungrounded responses rather than serving them.

Frequently asked questions

No — the citations feature does not add to token cost. Claude uses the same input tokens for the documents regardless. However, well-cited responses tend to be slightly longer (more text explicitly quoting sources), which does add output tokens.
Yes — citations, tools, and extended thinking all compose. The response can contain text blocks with citations, tool_use blocks, and thinking blocks all in one message. Handle each block type explicitly.
PDF citations return start_page_number and end_page_number ranges (page-based). Plain-text sources return start_char_index / end_char_index. Content-typed sources return block indices.
The image content is understood by Claude and can influence the answer, but citation objects only reference text spans. There is no image-region citation for a specific chart or figure — Claude will describe the visual in text and cite the surrounding textual context.
Native Citations avoids a whole class of "did the LLM actually use the retrieved chunk" ambiguity — the citations are guaranteed to reference the exact provided source. LangChain / LlamaIndex still handle retrieval, chunking, and orchestration; Citations handles the last-mile grounding of the answer.

Get the weekly AI-error digest

New fixes, provider status recaps, and one deep tutorial — every Tuesday. 8,400+ engineers.