05 - Files API, Citations, and PDFs¶
Three closely related features that together let Claude work with documents and produce grounded, attributed responses.
Files API¶
The Files API lets you upload binary content (PDFs, images, JSON, etc.) once and reference it by ID across many requests.
Why¶
- Avoid re-uploading the same file on every request
- Keep request payloads small and fast
- Reuse uploaded content across batch requests
Upload¶
file = client.files.create(
file=open("contract.pdf", "rb"),
purpose="user_data", # purpose semantics per current docs
)
print(file.id) # file_...
Reference in Messages¶
client.messages.create(
model="claude-sonnet-4-6",
max_tokens=2048,
messages=[
{
"role": "user",
"content": [
{"type": "document", "source": {"type": "file", "file_id": file.id}},
{"type": "text", "text": "Summarize this contract."},
],
}
],
)
Lifecycle¶
- Files have storage TTL per your account (check current docs)
- Delete via
client.files.delete(file.id)when no longer needed - Track file IDs in your own datastore for reuse
Limits¶
- Maximum file size per request and per file (check docs)
- Supported types: PDFs, images (PNG/JPEG/GIF/WebP), text, JSON, more
- Some types require beta headers
Vision (Image Input)¶
Pass images via base64, URL, or file_id:
{
"type": "image",
"source": {
"type": "base64",
"media_type": "image/png",
"data": base64_string,
},
}
URL form:
{"type": "image", "source": {"type": "url", "url": "https://..."}}
File ID form:
{"type": "image", "source": {"type": "file", "file_id": file.id}}
Use cases:
- OCR replacement (Claude reads and reasons about image content)
- UI screenshot analysis
- Chart and diagram interpretation
- Visual classification
Tips:
- Resize very large images before upload to control token count
- Image tokens are billed; a 1080p image costs roughly thousands of tokens depending on detail level
PDF Support¶
PDFs are sent as document content blocks. Claude reads both text and visual layout (charts, tables, diagrams).
{
"type": "document",
"source": {
"type": "base64",
"media_type": "application/pdf",
"data": base64_pdf,
},
}
Or via file_id (preferred for reuse):
{"type": "document", "source": {"type": "file", "file_id": file.id}}
Use cases:
- Contract analysis
- Research paper summarization
- Financial filing extraction
- Compliance review
Tips:
- Large PDFs use a lot of tokens; check
count_tokensfirst - Combine with prompt caching when the same PDF is referenced many times
- Combine with citations (next section) for attribution
Citations¶
Citations let Claude attribute spans of its response to specific spans of source documents.
Enabling¶
Include citations: {enabled: true} on document content blocks:
{
"type": "document",
"source": {"type": "file", "file_id": file.id},
"citations": {"enabled": true},
"title": "Acme 2025 Annual Report",
"context": "Filed with SEC on 2025-12-31",
}
Response Shape¶
Claude's text content blocks include a citations array tied to spans:
{
"type": "text",
"text": "Revenue grew 23% in Q4...",
"citations": [
{
"type": "page_location",
"cited_text": "Q4 revenue rose to $1.2B from $980M...",
"document_index": 0,
"document_title": "Acme 2025 Annual Report",
"start_page_number": 14,
"end_page_number": 14
}
]
}
Citation types vary by document type (page_location for PDFs, char_location for text).
When to Use¶
- Compliance and audit use cases
- Customer-facing RAG that must show "sources"
- Research assistants
- Any workload where the user must verify the source
Quality Notes¶
Citations are generated by Claude based on actual evidence in the documents. They are not perfectly precise but are far more reliable than asking Claude to fabricate source markers in prose.
Tips¶
- Provide
titleandcontextper document so Claude can disambiguate - Order documents by relevance when possible
- Render citations as inline footnotes or hover tooltips for best UX
- Log citations alongside responses for compliance
Composition Pattern¶
A typical RAG-with-attribution flow:
# 1. Upload documents once
docs = [client.files.create(file=open(p, "rb"), purpose="user_data") for p in paths]
# 2. Cache the static system prompt
system = [
{
"type": "text",
"text": "You are a research assistant. Always cite your sources.",
"cache_control": {"type": "ephemeral"},
}
]
# 3. Send query with documents and citations enabled
content = []
for d in docs:
content.append({
"type": "document",
"source": {"type": "file", "file_id": d.id},
"citations": {"enabled": True},
})
content.append({"type": "text", "text": user_question})
resp = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=2048,
system=system,
messages=[{"role": "user", "content": content}],
)
# 4. Render text with citation spans
for block in resp.content:
if block.type == "text":
render(block.text, block.citations)
Common Pitfalls¶
- Re-uploading the same file every request (use file_id)
- Forgetting to enable citations explicitly
- Storing PDFs as base64 in memory and forgetting to free
- Not measuring per-image token cost
- Using citations in the system prompt instead of document blocks
Exam Focus¶
- Files API upload-once, reference-by-id pattern
documentcontent block shape- Enabling citations and reading the response
- Image input variants (base64, URL, file_id)
- Combining files + caching + citations for production RAG