Citations and showing sources
Show where AI answers come from with citations, quoted passages and source links that let users verify claims, built with the Claude citations feature.
On this page
Why this matters
An AI answer without sources asks users to take it on faith. Sometimes that’s fine: nobody needs a citation for a suggested subject line. But for anything users will act on, repeat to others or rely on later, “where did this come from?” is the first question they’ll ask.
Citations answer it. They let users verify a surprising claim in seconds, they make wrong answers visible instead of hidden, and they shift trust from the model to the source, which is where it belongs. This article covers when to show sources, the patterns that work, and how to get reliable citations from the API.
Why sources matter
Showing sources does several jobs at once:
- Verification. Users can check the claim that matters to them without re-reading everything.
- Catching mistakes. When a cited passage doesn’t support the claim, the error is obvious. Without citations, the same error looks exactly like a correct answer.
- Trust calibration. Users learn when to rely on the feature and when to check, instead of trusting everything or nothing.
- Accountability. In regulated or high-stakes settings, being able to show what an answer was based on is often a requirement.
There’s also a benefit for you: answers grounded in sources you provided are far less likely to contain invented facts, because the model is working from text in front of it rather than from memory. See what LLMs are good and bad at.
Citation patterns
Show text descriptionHide text description
4 parts, in order:
- Claim: A specific sentence in the answer Example: New hires get 25 vacation days.
- Marker: A small, clickable reference next to the claim Example: …25 vacation days. [1]
- Quoted passage: The exact text the claim is based on, shown on hover or tap Example: "Full-time employees accrue 25 days of paid leave per year."
- Source: Title and link to the full document, opened at the right place Example: Employee handbook › Leave policy
How much of that to show depends on the stakes:
| Pattern | What users see | Good for |
|---|---|---|
| Source list | “Based on: Handbook, Leave policy” under the answer | Low stakes, short answers |
| Inline markers | Numbered markers after claims, linking to sources | Most question answering |
| Quoted passages | The exact supporting text, on hover or expanded | Answers users act on or share |
| Side-by-side | Answer next to the document, with cited passages highlighted | Review work: contracts, research, compliance |
Some practical rules:
- Cite claims, not paragraphs. A marker after each specific claim is more useful than one list at the end.
- Quote exactly. Show the passage the claim relies on, word for word, so users don’t need to open the document.
- Link to the right place. Opening a 40-page document at page one defeats the purpose.
- Show uncited claims honestly. If part of an answer has no source, don’t decorate it with one. A clear difference between cited and uncited text is itself useful information.
Getting citations from the API
You could ask a model to “include sources” in its text, but then the model writes the references itself, and nothing guarantees they’re accurate. Claude’s citations feature (opens in a new tab) is more reliable: you pass documents with citations enabled, and the response comes back split into text blocks, each carrying structured citations that point to the exact passage in your documents.
This function answers a question from a set of documents and returns the answer with numbered markers and the quoted passages behind them:
import Anthropic from '@anthropic-ai/sdk';
const client = new Anthropic();
export interface Source {
title: string;
url: string;
text: string;
}
export type CitedAnswer =
| { status: 'answered'; text: string; citations: { n: number; title: string; url: string; quote: string }[] }
| { status: 'declined' }
| { status: 'incomplete'; reason: string };
export async function answerWithCitations(question: string, sources: Source[]): Promise<CitedAnswer> {
const response = await client.messages.create({
model: 'claude-opus-5-5',
max_tokens: 4096,
output_config: { effort: 'low' },
system: 'Answer using only the provided documents. If they do not contain the answer, say so plainly instead of guessing.',
messages: [
{
role: 'user',
content: [
...sources.map(
(s): Anthropic.DocumentBlockParam => ({
type: 'document',
source: { type: 'text', media_type: 'text/plain', data: s.text },
title: s.title,
citations: { enabled: true },
}),
),
{ type: 'text', text: question },
],
},
],
});
if (response.stop_reason === 'refusal') return { status: 'declined' };
// A cut-off answer may have lost the claims, or the citations, that matter most. Don't show it as complete.
if (response.stop_reason !== 'end_turn') return { status: 'incomplete', reason: String(response.stop_reason) };
let text = '';
const citations: { n: number; title: string; url: string; quote: string }[] = [];
const numberFor = new Map<string, number>();
for (const block of response.content) {
if (block.type !== 'text') continue;
text += block.text;
for (const citation of block.citations ?? []) {
// Plain-text documents produce character-range citations.
if (citation.type !== 'char_location') continue;
const source = sources[citation.document_index];
if (!source) continue;
// Reuse the same number when two claims cite the same passage.
const key = `${citation.document_index}:${citation.start_char_index}`;
let n = numberFor.get(key);
if (n === undefined) {
n = citations.length + 1;
numberFor.set(key, n);
citations.push({ n, title: source.title, url: source.url, quote: citation.cited_text });
}
text += ` [${n}]`;
}
}
return { status: 'answered', text, citations };
} Your interface then renders text with the markers as links, and each entry in citations as a source card with its quote. Things to notice:
- Citations are data, not text. Each one points to a real document by index and to an exact character range, and includes the quoted passage. Your code builds the markers; the model never types a reference.
- The system prompt allows “not in the documents”. Without that instruction, a model may stretch a weak source to answer anyway.
- Unfinished answers aren’t shown as complete. Refusals and truncated answers get their own states.
- Citations don’t combine with structured outputs. If you need JSON as well, make a separate call or extract citations first. The citations docs (opens in a new tab) list the supported document types, including PDFs, which cite by page.
Where the documents come from is a separate problem: a small set can be passed whole; a large one needs retrieval first. Project 2 combines both.
When there is no good source
The hardest citation case is when the answer isn’t in the sources. The right behavior is to say so, clearly and helpfully:
- Say what’s missing. “The handbook doesn’t cover remote work outside your country” is more useful than a generic “I don’t know”.
- Offer the next step. Link to the closest relevant section, suggest who to ask, or offer to search more widely.
- Never fill the gap from the model’s memory without saying so. If you allow answers beyond the sources, label them clearly as uncited.
Measure this behavior in your evals: include questions whose answers aren’t in the documents, and check that the feature declines to answer rather than inventing one. That single category of eval case catches many of the most damaging failures. Communicating confidence and uncertainty covers abstaining in more depth.
Common mistakes
- Asking the model to write its own references. Use structured citations from the API, so every marker points to a real passage.
- One source list for a whole answer. Cite specific claims so users can check the one they care about.
- Linking to the top of a long document. Show the quoted passage, and link to the right page or section.
- Forbidding “I don’t know”. Allow the model to say the sources don’t answer the question, and design that state.
- Never testing unanswerable questions. Include them in your eval set; they catch invented answers.
Try it
Exercise
About 60 min
Add citations to a question-answering feature
- Pick three or four documents: help articles, a handbook section, product docs.
- Use the function above to answer five questions, including two the documents don’t answer.
- Render the answer with clickable markers and a source card showing each quote.
- Check every citation: does the quoted passage actually support the claim next to it? Note any that don’t.
Show a hint
Start rendering with something simple: replace each [n] with a link to an anchor on the page, and list the sources with their quotes below the answer. You can make it prettier later.
Show a worked solution
What a good result looks like:
- Answerable questions get answers with a marker after each specific claim, and each quote clearly supports its claim.
- The two unanswerable questions get a clear “the documents don’t cover this” answer, ideally saying what is covered nearby.
- If any citation doesn’t support its claim, that’s a finding: tighten the system prompt, improve the documents, or add the case to your eval set.
Check your understanding
Key takeaways
- Sources let users verify claims, catch mistakes and calibrate trust.
- Choose the pattern by stakes: source lists, inline markers, quoted passages or side-by-side review.
- Use structured citations from the API so every marker points to a real passage.
- Design and test the “not in the sources” path: a clear, helpful “I couldn’t find this” builds trust.
Progress is saved in this browser and lights up the roadmap.
Related articles
- Context windows, retrieval and memory trade-offs Decide what goes into the model's context, from long context versus retrieval to conversation memory, and the cost, latency and quality trade-offs of each. Level: Intermediate
- Streaming and loading states Design streaming responses and loading states for AI features, from when to stream to skeletons, progress, stop buttons and handling a stream that ends early. Level: Intermediate
- Human-in-the-loop and approval flows Design approval flows for AI actions, deciding which actions need a human, how to present a proposed action clearly, and how to keep approvals fast. Level: Intermediate