Claude Citations Format Invalid — Fix 400 Malformed Document (2026) | AI Error Hub
Anthropic Claude Citations Severity: Medium HTTP 400

Claude Citations — Document Block Format Rejected with 400 invalid_request_error

Claude returned 400 invalid_request_error when you enabled citations on a document block. The block structure doesn't match the expected schema: wrong source type combination, missing required fields, malformed content array, or unsupported combinations. This page has the exact valid schemas for every document type.

Error code: invalid_request_error HTTP: 400 Category: Citations Last tested: Nov 16, 2026
Quick Fix (TL;DR)

Every citation-enabled document block needs: type: "document", a valid source object matching one of three shapes (text, base64, content), citations: {enabled: true}, and optionally title/context. For custom-content docs, the source's content array must contain {type: "text", text: "..."} objects only — no nested documents or images.

The Error Messages You're Seeing

Response body variants
# Missing source type:
HTTP/1.1 400 Bad Request
{"type":"error","error":{"type":"invalid_request_error",
  "message":"document.source.type is required. Expected 'text', 'base64', 'file', or 'content'."}}

# Invalid combination — text with base64 data:
{"type":"error","error":{"type":"invalid_request_error",
  "message":"document.source with type 'text' does not accept a 'data' field with base64 content. Use 'data' as plain string."}}

# Custom-content with unsupported nested types:
{"type":"error","error":{"type":"invalid_request_error",
  "message":"document.source.content[0] must be of type 'text'. Nested document or image blocks not supported inside content array."}}

# Citations enabled on image-only document:
{"type":"error","error":{"type":"invalid_request_error",
  "message":"citations feature not supported for image-only documents."}}

# Missing media_type on text source:
{"type":"error","error":{"type":"invalid_request_error",
  "message":"document.source.media_type is required when source.type is 'text' or 'base64'. Expected 'text/plain', 'application/pdf', etc."}}

Valid Document Block Schemas (Nov 2026)

Purposesource.typeRequired fieldsCitations?
Plain text doctextmedia_type: text/plain, data (string)✓ Yes
PDF (inline)base64media_type: application/pdf, data (base64 string)✓ Yes
PDF (uploaded)filefile_id: file_...✓ Yes
Multi-chunk RAGcontentcontent: [{type:text, text:...}, ...]✓ Yes (block-level)
Image (analysis only)base64media_type: image/*, data✗ No

What Actually Causes This Error

32%
Mismatched source.type and required fieldstext-type with missing media_type; content-type without content array
22%
Wrong data field formatBase64 sent as raw bytes; text sent as base64-encoded
16%
Custom-content array with non-text sub-blocksNested documents or images inside content[]
12%
Citations enabled on image contentFeature only supports text-based sources
10%
Missing citations wrappercitations: true (bad) vs citations: {enabled: true} (good)
8%
Beta header outdatedOlder SDK sending pre-GA schema

Fixes That Work (Tested Nov 2026)

1Use the Correct Schema for Each Source Type

Python · four working examples
# === TYPE 1: Plain text document === plain_text_doc = { "type": "document", "source": { "type": "text", "media_type": "text/plain", "data": "Full text content of the document goes here...", }, "title": "Q4 Report", "citations": {"enabled": True}, } # === TYPE 2: PDF (inline base64) === import base64 pdf_bytes = open("report.pdf", "rb").read() pdf_doc = { "type": "document", "source": { "type": "base64", "media_type": "application/pdf", "data": base64.standard_b64encode(pdf_bytes).decode(), }, "title": "Report PDF", "citations": {"enabled": True}, } # === TYPE 3: PDF via Files API === file_doc = { "type": "document", "source": {"type": "file", "file_id": "file_01ABC"}, "title": "Uploaded PDF", "citations": {"enabled": True}, } # === TYPE 4: Custom-content (chunks for RAG) === chunks_doc = { "type": "document", "source": { "type": "content", "content": [ {"type": "text", "text": "Chunk 1 body..."}, {"type": "text", "text": "Chunk 2 body..."}, {"type": "text", "text": "Chunk 3 body..."}, # NO images, NO nested documents allowed here ], }, "title": "Multi-chunk RAG doc", "citations": {"enabled": True}, }

2Validate Document Blocks Before Sending

Python · schema check helper
import base64 VALID_SOURCE_TYPES = {"text", "base64", "file", "content"} VALID_MEDIA_TYPES = { "text/plain", "text/markdown", "text/csv", "application/pdf", "application/vnd.openxmlformats-officedocument.wordprocessingml.document", } def validate_document_block(block: dict) -> list: """Return list of validation errors. Empty list = valid.""" errors = [] if block.get("type") != "document": errors.append("type must be 'document'") return errors # stop early source = block.get("source", {}) stype = source.get("type") if stype not in VALID_SOURCE_TYPES: errors.append(f"source.type must be one of {VALID_SOURCE_TYPES}") # Per-type field checks if stype == "text": if source.get("media_type") not in VALID_MEDIA_TYPES: errors.append("text source needs valid media_type") if not isinstance(source.get("data"), str): errors.append("text source.data must be plain string") elif stype == "base64": data = source.get("data", "") try: base64.b64decode(data, validate=True) except Exception: errors.append("base64 source.data is not valid base64") if "media_type" not in source: errors.append("base64 source needs media_type") elif stype == "file": fid = source.get("file_id", "") if not fid.startswith("file_"): errors.append("file source needs valid file_id") elif stype == "content": content = source.get("content", []) if not isinstance(content, list) or not content: errors.append("content source needs non-empty content array") for i, item in enumerate(content): if item.get("type") != "text": errors.append(f"content[{i}] must be type 'text' (got {item.get('type')})") # Citations flag cites = block.get("citations", {}) if not isinstance(cites, dict) or "enabled" not in cites: errors.append("citations must be {enabled: true|false}, not bare boolean") return errors # Usage — fail loud in dev, log in prod errors = validate_document_block(my_doc_block) if errors: raise ValueError(f"Invalid document block: {errors}")

3Migrate Legacy Schemas Automatically

Python · normalize old formats
import base64 def normalize_document_block(raw: dict) -> dict: """Accept various legacy shapes; return canonical form.""" # Handle {type: 'document', source: raw string} if isinstance(raw.get("source"), str): raw = { "type": "document", "source": { "type": "text", "media_type": "text/plain", "data": raw["source"], }, "title": raw.get("title", "Document"), } # Handle citations: true → citations: {enabled: true} if raw.get("citations") is True: raw["citations"] = {"enabled": True} elif raw.get("citations") is False: raw["citations"] = {"enabled": False} # Handle pdf as filename → auto-encode base64 src = raw.get("source", {}) if src.get("pdf_path"): with open(src["pdf_path"], "rb") as f: raw["source"] = { "type": "base64", "media_type": "application/pdf", "data": base64.standard_b64encode(f.read()).decode(), } # Filter unsupported content types out of content array if raw.get("source", {}).get("type") == "content": raw["source"]["content"] = [ item for item in raw["source"]["content"] if item.get("type") == "text" ] return raw

Preventing This Error Going Forward

  • Wrap document block construction in a validator. Catches schema issues before API call.
  • Use SDK helper classes (types.DocumentBlockParam). Type-checked by mypy; avoids field errors.
  • Test all four source types in CI. Catches regressions when Anthropic updates schemas.
  • Keep SDK on latest minor version. Schema definitions ship with SDK updates.
  • Never send raw citations: true. Legacy shorthand no longer accepted.
  • Filter content[] arrays to text-only. Silent field drops cause confusing errors downstream.
  • Log the exact failed payload on 400. Redact sensitive fields but keep structure for debugging.
SK
Tested by Sana K.
Researcher · AI Error Hub
Last verified: Nov 15, 2026 · SDK: anthropic-python 0.40+

Frequently Asked Questions

Yes. A user message content array can contain multiple document blocks alongside text blocks. Each document has its own citations setting. Citations returned in the response reference which document (via document_index) contributed each cited passage.

text is a single string blob — Claude cites character ranges. content is a structured array of text chunks — Claude cites specific chunks by index. Use content for RAG where you want chunk-level attribution; use text for whole documents where character-range citations are fine.

No — only {type: "text", text: "..."} sub-blocks. Nested images, documents, or tool_use blocks are rejected. Images intended for analysis go in separate image content blocks alongside the document.

Currently only {enabled: bool} is supported. There's no way to pass additional citation config per document. Add metadata (author, date, section) to the document title or context field instead.

Yes — the optional context field lets you provide document-level metadata that Claude uses when deciding what to cite. Example: context: "Legal contract signed 2024-05-01". Doesn't count toward citations but improves quality.