M. Patel11 min read10 views

Claude citations have four location types and one counts from 1

Enabling citations does not give you one citation shape. It gives you four, split across two documentation pages, indexed from two different bases, with the source field under two different names.

Flat schematic on deep navy: four horizontal tracks against a lime origin rail, three filled spans starting flush at the rail and one starting a full unit to its right, showing one of four series counting from a different place.
Flat schematic on deep navy: four horizontal tracks against a lime origin rail, three filled spans starting flush at the rail and one starting a full unit to its right, showing one of four series counting from a different place.
On this page

Quick answer

As of September 25, 2026, enabling citations on the Claude API does not give you one citation shape. It gives you four, and they are documented on two different pages that never appear side by side. Three of the four index from zero. One indexes from one. The field that tells you which source was cited changes its name depending on which of the four you got.

None of that is hidden. All of it is written down. It is just written down in two places, and the renderer you write after reading one page will be quietly wrong about the other.

The moment

I had a citation renderer that worked. Plain text documents in, highlighted spans out, character offsets lining up exactly against the source. I was pleased with it.

Then someone fed it a PDF and every highlight landed one page early.

My first instinct was that the model had miscounted, which is the instinct you should always distrust. It had not. My renderer had one start and one end and one assumption, and the assumption was that an index is an index. For char_location and content_block_location that assumption holds. For page_location it does not, and the docs say so in a table I had read and not absorbed.

The bug was four characters long. Finding it took most of a morning, because I was looking for it in the wrong layer.

Finding 1: there are four location types, and only three are on the citations page

Anthropic The citations documentation describes three document types and, with them, three citation location types:

Scroll to see more

Document typeChunkingCitation typeIndex basis
Plain textSentencechar_locationCharacter indices, 0-indexed
PDFSentencepage_locationPage numbers, 1-indexed
Custom contentNonecontent_block_locationBlock indices, 0-indexed

That table is the vendor's own, and it is clear. The problem is that it is complete only for document blocks.

There is a fourth type. search_result_location is produced by search result content blocks, a separate feature on a separate page, and it never appears in the table above. If you build against the citations page alone you will write a renderer with three branches and a default case, and the default case is the one your RAG pipeline will hit.

Worth saying what this is not. This is not a gap in the documentation so much as a gap between two pieces of documentation, each of which is accurate about its own subject.

Finding 2: three count from zero, one counts from one

Here is the full index basis, assembled from both pages, because I could not find it assembled anywhere:

Scroll to see more

FieldBasisEnd
document_index0-indexedn/a
start_char_index / end_char_index0-indexedexclusive
start_block_index / end_block_index0-indexedexclusive
search_result_index0-indexedn/a
start_page_number / end_page_number1-indexedexclusive

The citations page lists the bases as separate bullets. On characters: "Character indices are 0-indexed with exclusive end indices." On pages: "Page numbers are 1-indexed with exclusive end page numbers."

So the ends are consistently exclusive across all four types, which is the part that is easy to get right. The starts are not consistently based, which is the part that is easy to get wrong, because there is exactly one exception and it is the one you will test last.

A citation to page 5 of a PDF arrives as start_page_number: 5, end_page_number: 6. If your renderer treats that the way it treats start_char_index: 5, it will point at page 6 of a 1-indexed viewer, or page 4 if you are slicing a 0-indexed array of page objects. Both failures look like an off-by-one in the model rather than in you.

Finding 3: the field that names the source changes name

This one is not an index problem, it is a shape problem, and it is the reason my three-branch renderer needed a rewrite rather than a patch.

For the three document location types, the citation object carries document_index and document_title. For search_result_location, it carries search_result_index, plus source and title taken from the original search result.

json
{
  "type": "char_location",
  "cited_text": "The grass is green.",
  "document_index": 0,
  "document_title": "Example Document",
  "start_char_index": 0,
  "end_char_index": 20
}
json
{
  "type": "search_result_location",
  "source": "https://example.com/article",
  "title": "Article Title",
  "cited_text": "...",
  "search_result_index": 0,
  "start_block_index": 0,
  "end_block_index": 1
}

Same concept, three different field names for it. A renderer that reaches for citation.document_index gets undefined on search results and, depending on your language, either throws or silently renders a citation pointing at source zero. The second outcome is worse.

There is a related asymmetry in what the source object itself requires. On a document block, title and context are optional and, per the citations page, neither is citable: "title and context are optional fields that are passed to the model but not used toward cited content." On a search_result block, source, title and content are all required.

Finding 4: all-or-nothing is three rules, not one

I had filed "citations are all-or-nothing" as a single fact. It is three facts on three pages, and only two of them are the same shape.

The citations page, on documents: "Currently, citations must be enabled on all or none of the documents within a request."

The search results page, on search results: "Citations are all-or-nothing: either all search results in a request must have citations enabled, or all must have them disabled. Mixing search results with different citation settings results in an error."

Those two are the same rule applied to two block types. The third is different in kind:

The search results page, on the web search tool: "When the web search tool is enabled in the same request, citations must be enabled on all search_result blocks."

That is not an all-or-nothing rule, it is a forced-on rule, and it fires on a request you did not change. Add the web search tool to a working RAG request whose search_result blocks were deliberately citations-disabled, and the blocks you did not touch are now invalid. The thing that broke is not the thing you edited.

Defaults compound this, because they are not uniform either. Search results default to citations disabled. Documents have no default in the same sense, since you set citations.enabled per document and the all-or-none rule applies. And on the tool side, as I found when I went through the web fetch tool's provenance model, citations is optional and off by default on web_fetch while web_search has it always on and not configurable.

Four surfaces, four different answers to "is this on right now".

Finding 5: citations and structured outputs cannot coexist, and the failure is a 400

This is the one I would most want to know before designing rather than after.

The citations page, on structured outputs: "Citations cannot be used together with structured outputs. If you enable citations on any user-provided document (document blocks or search_result blocks) and also include the output_config.format parameter (or the deprecated output_format parameter), the API returns a 400 error."

The stated reason is mechanical rather than arbitrary: "citations require interleaving citation blocks with text output, which is incompatible with the strict JSON schema constraints of structured outputs."

Note how wide the trigger is. It covers both user-provided block types, and it covers both the current parameter and the deprecated one. If you were migrating from output_format to output_config.format and assumed the old name was inert, it is not inert here.

Architecturally this is a fork, not a flag. A pipeline that wants grounded answers and a guaranteed JSON envelope cannot have both in one call. You either take citations and parse prose, or take structured output and carry provenance yourself in the schema. Anyone building on the structured outputs path should know that enabling citations later is not an additive change.

Finding 6: cited_text is free in both directions

A genuinely pleasant one, and the only finding here that makes something cheaper.

The citations page, on token costs: "The cited_text field is provided for convenience and does not count toward output tokens." The same section adds: "When passed back in subsequent conversation turns, cited_text is also not counted toward input tokens."

So the quoted passage is not billed on the way out, and not billed again on the way back in. The search_result_location field table repeats the output half for its own type.

This is not free overall. The same section is explicit that enabling citations "incurs a slight increase in input tokens because of system prompt additions and document chunking". The point is narrower and still useful: the part that looks most expensive, a verbatim quotation repeated on every citation in a long multi-turn conversation, is the part you are not paying for twice.

Finding 7: for two of the four types, granularity is your job

Chunking is automatic for plain text and PDF, both to sentences. For custom content documents and for search results it is not automatic at all.

On search results, the rule is stated plainly: Claude cites whole blocks, never substrings within a block. So a search_result whose content is one large text block can only ever produce one citation covering the whole thing. That is a correct citation and a useless one.

If you want sentence-level precision from a RAG chunk you have two routes, and the docs name both. Put each chunk into a plain text document and let automatic sentence chunking do the work, or split the content into smaller blocks yourself and accept block granularity. The choice is real: automatic chunking is better precision, custom blocks are better control over boundaries that sentences would cut across, such as bullet lists and transcripts.

One connecting detail worth noting, given that documents can be referenced by file_id through the Files API rather than inlined: the chunking behaviour follows the document type, not the delivery method. A PDF is sentence-chunked and page-cited whether you sent the bytes or a file reference.

What I did not verify

  • I did not run these requests. Every claim above is read from Anthropic's published documentation as it stood on September 25, 2026, and quoted rather than paraphrased where the exact wording carries the weight. I checked the citations page against a copy I took on September 24 and the two are byte-identical, so it is at least stable across a day.
  • I did not test the 400 error from the structured outputs conflict, so I cannot tell you what the error body says or which parameter it names.
  • I did not confirm the forced-on web search rule by triggering it. It is documented under Limitations on the search results page and I am reporting the documentation, not a reproduction.
  • I did not examine image citations, because there are none to examine. The docs state that only text citations are supported and that citing images from PDFs is not currently possible.
  • I did not cover the streaming shape beyond noting that citations arrive as a citations_delta inside content_block_delta events. That deserves its own measurement rather than a paragraph.
  • Platform availability differs by feature and I did not audit it. Search result blocks are documented as available on the Claude API, Amazon Bedrock and Google Cloud; do not assume the document types match that list exactly.

Postscript: the renderer was never reading the wrong number. It was reading the right number and counting from the wrong place, which is a much harder bug to see, because everything on the screen looks plausible.

M

Written by

M. Patel

Frequently asked questions

How many citation location types does the Claude API return?

Four. The citations documentation covers three that come from document blocks: char_location for plain text, page_location for PDFs and content_block_location for custom content documents. A fourth, search_result_location, is produced by search result content blocks and is documented on a separate page, so it does not appear in the citations page's document-type table.

Are Claude citation indices 0-indexed or 1-indexed?

Both, depending on the field. Document indices, character indices, content block indices and search result indices are all 0-indexed. Page numbers are 1-indexed. All end values are exclusive across every type. That single exception for pages is the most common source of off-by-one errors in citation renderers, because it is the only type that does not count from zero.

Can I use Claude citations and structured outputs in the same request?

No. As of September 2026 Anthropic's citations documentation states that enabling citations on any user-provided document, meaning document blocks or search_result blocks, while also passing output_config.format or the deprecated output_format parameter returns a 400 error. The stated reason is that citations interleave citation blocks with text output, which conflicts with the strict JSON schema constraints of structured outputs.

Why did enabling the web search tool break my existing search_result blocks?

Because the web search tool changes citations from optional to mandatory. The search results documentation states under Limitations that when the web search tool is enabled in the same request, citations must be enabled on all search_result blocks. If your existing blocks were deliberately citations-disabled, adding web search invalidates blocks you never edited.

Does cited_text count toward Claude API token usage?

No, in both directions. Anthropic's citations documentation states that cited_text is provided for convenience and does not count toward output tokens, and that when passed back in subsequent conversation turns it is also not counted toward input tokens. Enabling citations does still add some input tokens for system prompt additions and document chunking, so the feature is not free overall, only the quoted passage itself.

Claude rate limit headers measure two things two ways

The anthropic-ratelimit headers are not one meter. Input is an estimate that gets corrected, output is a running tally, and the tokens header reports whichever limit currently binds.

12 min read46

Your JSON schema is a second cached artifact

Structured outputs and strict tool use turn your JSON schema into a separate cached artifact. It has its own 24 hour lifetime, invalidation rules that invert what you would guess, and a retention boundary that zero data retention does not cover.

10 min read42