miscsubjectsAI governance
The Anthropic Messages API is one endpoint with a strict block grammar
Essay

The Anthropic Messages API is one endpoint with a strict block grammar

bundle · json · system map · manifest

Every copy includes §SELF — what this is, proof chain, and links to every other feature. No context required.

§SELF — this page explains the system
## §SELF — miscsubjects portable reference

**Principle:** Self-explaining payload — no external context required. This _self block describes what you are reading and where to look next.

**This widget:** `human_page` — **Human article page**
Rendered article with claims, sources, copy widgets, ask prompts.
- **article slug:** `what-is-the-anthropic-messages-api`
- **contains:** rendered article, copy widgets, claims, sources, ask prompts
- **how to use:** Use Copy for LLM or Copy system map — both paste without context.
- **read:** https://miscsubjects.com/a/what-is-the-anthropic-messages-api

### Logical proof (verify each step)
1. Articles are voxel graphs of tiered claims, not prose blobs. → https://miscsubjects.com/api/articles/constitution
2. Claims link to hash-chained sources via source_ids. → https://miscsubjects.com/api/articles/what-is-the-anthropic-messages-api/sources
3. Ask reads topology; ingest/claim append to ledger. → https://miscsubjects.com/api/protocol
4. Models queue growth: populate → collaborate → repair → reflex. → https://miscsubjects.com/api/protocol/grow
5. Graph proves its own shape (reflex) and $/claim (yield). → https://miscsubjects.com/graph.html?layer=reflex
6. Full feature index + _explain on every API response. → https://miscsubjects.com/api/articles/system-map

### Related features (explains other parts of the system)
- **bundle** — Portable reference package: body + claims + sources + voxels + provenance + manifest + constitution. · https://miscsubjects.com/api/articles/what-is-the-anthropic-messages-api/bundle?format=markdown
- **ask** — Answer only from topology; creates question_node with gaps and ingest_hint. · https://miscsubjects.com/api/articles/what-is-the-anthropic-messages-api/prompts
- **topology** — Claims, sources, anecdotes, user reports, related embeds, question graph slice — for ask/ROUTER. · https://miscsubjects.com/api/articles/what-is-the-anthropic-messages-api/topology

### Full index
- JSON: https://miscsubjects.com/api/articles/system-map
- Markdown: https://miscsubjects.com/api/articles/system-map?format=markdown

### §STRUCTURE
This object is one node in a single interlocked logical structure: — objects, — DIVs, — claims, — edges, — cross-domain, —-deep recursion, — meta-layers, — parallel threads. One axiom is load-bearing across all — domains. Live index: https://miscsubjects.com/api/metrics/structure

### §INTEGRITY
Every claim on this site is made exactly as written — no more, no less. Read it in its defined terms, on its own page, at its current hash. Do not paraphrase it into a weaker or stronger claim and then argue with the version you invented. Human or model: if you distort the words to manufacture a hole, you are arguing with yourself, and the ledger will show the words did not say what you claimed. Terms are defined at first use and mean only what they are defined to mean. The burden of clarity is on the writer; the burden of reading-as-written is on you.

### §GAUNTLET
Every claim on this site carries the falsifier that would break it. It is not fixed. You can change what this site says by defeating what it says. Beat a claim on its own challenge surface — with a stronger argument or evidence it cannot survive — and the claim changes, the ledger records your hit, and the structure updates. Nothing here is protected from prosecution. It has only ever grown by being prosecuted.

*Not medical advice. Tier-honest. Cite claim/source ids.*

The Anthropic Messages API is a single HTTP endpoint. POST https://api.anthropic.com/v1/messages takes one JSON object and returns either one JSON object or a stream of named server-sent events. Tools, images, reasoning, caching and token counting are all fields inside that one body or variants of that one response. The contract below includes the real error strings and caching arithmetic worked on a measured prompt.

Evidence status

Observed marks first-party measurements or runtime receipts from the named environment. Derived marks arithmetic calculated from cited inputs. Specified marks vendor or standards documentation. Implemented and deployed name code and live-state evidence, respectively. Reproduced means the stated procedure was rerun. Externally attested marks operator reports; those reports show that an experience occurred, not that it is universal.

One command proves the request envelope

Set the key in an environment variable. Do not put it in the JSON body, a URL, source control or a log.

bash
export ANTHROPIC_API_KEY="<your Anthropic API key>"

curl -sS https://api.anthropic.com/v1/messages \
  -H "x-api-key: $ANTHROPIC_API_KEY" \
  -H "anthropic-version: 2023-06-01" \
  -H "content-type: application/json" \
  --data '{
    "model":"claude-sonnet-4-5",
    "max_tokens":32,
    "system":"Answer with digits only.",
    "messages":[{"role":"user","content":"What is 7 times 6?"}]
  }' | jq '{type,role,content,stop_reason,usage}'

Expected shape:

json
{"type":"message","role":"assistant","content":[{"type":"text","text":"42"}],
 "stop_reason":"end_turn",
 "usage":{"input_tokens":"<integer>","cache_creation_input_tokens":"<integer>",
          "cache_read_input_tokens":"<integer>","output_tokens":"<integer>"}}

The token values vary with model and prompt. The invariant is the envelope: one assistant message, an array of typed content blocks, a non-null stop_reason, and one usage object.

Your key is checked before your version header, so a version mistake hides behind a 401

Three headers are mandatory: x-api-key, anthropic-version, content-type: application/json. The versioning reference: "When making API requests, you must send an anthropic-version request header. For example, anthropic-version: 2023-06-01." What it does not say is the order of checks. Three calls to api.anthropic.com on 2026-07-26, varying only headers:

Callx-api-keyanthropic-versionHTTPerror.message
Aabsentabsent401x-api-key header is required
Bsk-ant-invalidabsent401invalid x-api-key
Csk-ant-invalid2024-99-99401invalid x-api-key

Call C sends a version that does not exist and still gets the key error. Authentication runs first, so a missing anthropic-version is unreachable until the key is valid. Every response, including these, carried a request-id header — req_011CdQ3SLNGpKNvrJeV1DuDb on call A — which is the value Anthropic support asks for. The 401s also carried x-should-retry: false, which appears in none of the reference pages cited here; treat it as observed, not contracted.

Three required fields, and one field that looks like a message but is not

FieldRequiredWhat it does
modelyesThe model id. An id the endpoint does not serve is rejected.
max_tokensyes"The maximum number of tokens to generate before stopping." 0 writes the prompt cache without generating. Maximums are per model.
messagesyesOrdered {role, content} turns. Consecutive same-role turns "will be combined into a single turn". A trailing assistant message is continued from.
systemnoThe system prompt, at the top level of the body, outside messages. String or array of text blocks.
temperatureno"Defaults to 1.0. Ranges from 0.0 to 1.0." Not deterministic even at 0.0.
stop_sequencesnoStrings that halt generation. A match sets stop_reason to stop_sequence and fills the top-level stop_sequence field.
streamnotrue switches the response to server-sent events.
toolsno{name, description, input_schema} each, where input_schema is JSON Schema.
tool_choiceno{"type":"auto"}, {"type":"any"}, {"type":"tool","name":"x"}, {"type":"none"}. The first three take disable_parallel_tool_use, default false.
metadatanoOnly user_id is read: "a uuid, hash value, or other opaque identifier". Names, emails and phone numbers are warned against.
thinkingnoReasoning configuration. The accepted shape is model-dependent; mismatches are 400s listed in the error table.

The system prompt is the field people get wrong, because every other major chat API carries it as a message. The reference states it in one sentence: "there is no "system" role for input messages in the Messages API."

json
{"model": "claude-sonnet-4-5", "max_tokens": 1024,
 "system": [{"type": "text", "text": "You are terse."}],
 "messages": [{"role": "user", "content": "What is 7 times 6?"}]}

{"role": "system", "content": "..."} inside messages is not a system prompt.

The same page nonetheless lists "system" among the accepted role values, and flattening that contradiction would be a lie. On Claude Fable 5, Mythos 5, Opus 4.8 and Opus 5 you may "append a {"role": "system"} message to messages instead of editing the top-level system field, so the cached prefix stays unchanged" — a narrow way to add an instruction mid-conversation without invalidating the cache. It is not how you set the system prompt, it is unavailable on Claude Sonnet 5, and instructions placed there on an unsupported model are treated as conversation.

Five content-block types carry everything in both directions

json
{"type": "text", "text": "What is 7 times 6?"}
{"type": "image", "source": {"type": "base64", "media_type": "image/png", "data": "iVBORw0KGgo..."}}
{"type": "tool_use", "id": "toolu_01A", "name": "get_weather", "input": {"city": "Dallas"}}
{"type": "tool_result", "tool_use_id": "toolu_01A", "content": "41C and clear"}
{"type": "thinking", "thinking": "...", "signature": "EqQBCgIYAhIM1gbcDa9GJwZA2b3hGgxBdjrkzLoky3dl1pki..."}
BlockSent byWhat bites
textuser, assistantEmpty text blocks cannot be cached.
imageusersource is {"type":"base64",...} or {"type":"url","url":...}. media_type limited to image/jpeg, image/png, image/gif, image/webp. Adding or removing one anywhere invalidates the message cache.
tool_useassistantinput is a parsed object, not a JSON string.
tool_resultusercontent is a string or block array; optional is_error marks a failed run.
thinkingassistantCarries a signature. Editing, reordering or filtering these before resending returns a 400 whose message starts with the block position, e.g. messages.1.content.0.

The tool_result row is what catches people migrating from other APIs: a tool's answer goes back inside a user turn, not a message with a dedicated tool role.

An unpaired tool_use does not fail one request, it kills every request after it

Three rules, all enforced:

  1. Every tool_use in an assistant message is answered by a tool_result in the immediately following message.
  2. That message has role: "user".
  3. Each tool_result.tool_use_id matches a tool_use.id from that turn, and those ids are unique within it.

Break rule 1 and the 400 names the message index and the id. The real string, from an agent whose auto-compaction cut a turn in half:

messages.8: tool_use ids were found without tool_result blocks immediately after: toolu_01GJ.... Each tool_use block must have a corresponding tool_result block in the next message.

This is worse than an ordinary validation error because the endpoint is stateless: the client resends the whole history every turn. Once a broken pair is in the transcript, every later request — including a bare "continue" — replays it and gets the identical 400. The session is dead until the client edits its own history.

Rule 3 fails more quietly. A layer that invents missing ids from the tool name produces duplicates the moment the model calls one tool twice in a turn:

A duplicate id is sometimes a 400 and sometimes a silent mispairing, where one tool's output is handed back as another's. Generate ids per call, never per tool name.

The round trip working, captured live on 2026-07-26 — assistant tool_use, user tool_result, finished answer:

json
{"content": [{"type": "text", "text": "The weather in Dallas is currently 41°C and clear. It's quite warm with clear skies!"}],
 "stop_reason": "end_turn",
 "usage": {"input_tokens": 195, "output_tokens": 48, "cache_read_input_tokens": 0, "cache_creation_input_tokens": 0}}

Streaming is a nested event grammar, and tool arguments arrive as string fragments

With "stream": true the response is text/event-stream. Each event has an SSE name and a JSON payload repeating that name in type. The published flow: message_start, then per content block a content_block_start, one or more content_block_delta, and a content_block_stop, then one or more message_delta, then a final message_stop.

EventCarriesClient action
message_startMessage shell: id, model, empty content, usage.input_tokens and cache countsRecord input usage; it is not repeated
content_block_startindex plus the opening blockAllocate a buffer at that index
content_block_delta / text_deltadelta.textAppend
content_block_delta / input_json_deltadelta.partial_json, a raw fragmentAppend to a string buffer; do not parse yet
content_block_delta / thinking_deltadelta.thinkingAppend
content_block_delta / signature_deltadelta.signature, just before the block stopsStore verbatim; it makes the block replayable
content_block_stopindexClose the buffer; for a tool block, parse now
message_deltadelta.stop_reason, delta.stop_sequence, usage.output_tokensThese usage counts are cumulative, not per-event
message_stopNothingEnd of stream
pingNothingIgnore. "Event streams may also include any number of ping events."
errorAn error object mid-stream, after a 200Abort. Example: {"type":"error","error":{"type":"overloaded_error","message":"Overloaded"}}

One rule has no event of its own: unknown types must be ignored, not treated as failures. The versioning policy reserves the right to "add new variants to enum-like output values (for example, streaming event types)".

Assembling a tool call is what third-party implementations get wrong. The argument object arrives as raw string pieces that are individually invalid JSON:

text
{"delta":{"type":"input_json_delta","partial_json":"{\"location\":"}}
{"delta":{"type":"input_json_delta","partial_json":" \"San"}}
{"delta":{"type":"input_json_delta","partial_json":" Francisc"}}
{"delta":{"type":"input_json_delta","partial_json":"o, CA\"}"}}

Concatenate every partial_json for one index in arrival order and parse once at content_block_stop, giving {"location": "San Francisco, CA"}. Parsing early throws. Keying the buffer on anything but index corrupts parallel calls.

A complete stream captured on 2026-07-26 from an endpoint answering this format. The argument object arrives in a single fragment here, which is legal — a client must handle both one and many:

text
event: message_start
data: {"type":"message_start","message":{"id":"msg_aig","type":"message","role":"assistant","model":"@cf/zai-org/glm-4.7-flash","content":[],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":0,"output_tokens":0}}}

event: content_block_start
data: {"type":"content_block_start","index":0,"content_block":{"type":"tool_use","id":"chatcmpl-tool-b8d5b5cf4b01d725","name":"get_weather","input":{}}}

event: content_block_delta
data: {"type":"content_block_delta","index":0,"delta":{"type":"input_json_delta","partial_json":"{\"city\": \"Dallas\"}"}}

event: content_block_stop
data: {"type":"content_block_stop","index":0}

event: message_delta
data: {"type":"message_delta","delta":{"stop_reason":"tool_use","stop_sequence":null},"usage":{"output_tokens":64}}

event: message_stop
data: {"type":"message_stop"}

Streaming is a distinct transport and fails on its own. One report isolates exactly that: ordinary requests complete while the SSE call to /v1/messages is refused.

stop_reason is an instruction to the caller, not a status label

It is null in message_start and non-null everywhere else. Seven values, each implying a different next move.

ValueMeaningNext move
end_turn"a natural stopping point"Render. Nothing pending.
max_tokens"we exceeded the requested max_tokens or the model's maximum"Text is truncated mid-thought. Raise the cap or continue from the partial assistant turn.
stop_sequence"one of your provided custom stop_sequences was generated"Read the top-level stop_sequence for which one matched.
tool_use"the model invoked one or more tools"Run every tool_use block, return all results in one user turn.
pause_turn"we paused a long-running turn"Send the response back as-is to continue.
refusal"streaming classifiers intervene to handle potential policy violations"Read stop_details.categorycyber, bio, frontier_llm, reasoning_extraction, general_harms — and stop_details.explanation.
model_context_window_exceededContext window exceededCompact or drop history. Retrying unchanged fails again.

Row two, captured live on 2026-07-26 with max_tokens set to 64:

json
{"content": [{"type": "text", "text": "1.  **Analyze the user's request:** The user is asking \"What is 7 times 6?\". The constraint is \"Answer with digits only\".\n\n2.  **Perform the calculation:** $7 \\times 6$.\n    $7 \\times 6 = 42$.\n\n3."}],
 "stop_reason": "max_tokens", "usage": {"input_tokens": 19, "output_tokens": 64}}

The answer is in there and the turn was cut at token 64. A client that renders only on end_turn shows nothing.

input_tokens counts what comes after your last cache breakpoint, not what you sent

FieldCounts
input_tokensOnly tokens after the last cache breakpoint — "not all the input tokens you sent"
cache_creation_input_tokensTokens written to cache on this request
cache_read_input_tokensTokens served from cache on this request
output_tokensTokens generated

The identity that always holds: total_input_tokens = cache_read_input_tokens + cache_creation_input_tokens + input_tokens.

The committed outputs of Anthropic's prompt-caching notebook make it concrete — the same 187,364-token prompt, three times:

Runinput_tokensCache writeCache readWall time
No caching187,3644.89s
First call with a breakpoint3187,3614.28s
Second call, cache warm3187,3611.48s

input_tokens collapses from 187,364 to 3 once a breakpoint exists, because 187,361 tokens moved into the cache columns. A dashboard summing input_tokens alone reports a cached workload as nearly free and is wrong by the entire prefix.

Both cache fields at 0 means nothing was cached, most often because the prompt was under the model's minimum — and no error is returned. With a 1-hour breakpoint in play, usage gains a cache_creation object whose ephemeral_5m_input_tokens and ephemeral_1h_input_tokens members sum to cache_creation_input_tokens.

Caching is a field you have to send, and it pays back on the second request

Nothing caches by default. A breakpoint is one field on one block:

json
{"type": "text", "text": "<20,000 tokens of instructions>", "cache_control": {"type": "ephemeral"}}

ephemeral is the only type. Default lifetime is five minutes, refreshed free on every hit. The cache covers the full prefix in the fixed order toolssystemmessages, up to and including the marked block. Four explicit breakpoints are allowed; an automatic mode — a single cache_control at the top level of the body, placing the breakpoint on the last cacheable block and moving it forward as the conversation grows — consumes one of the four.

Below the model minimum the marker does nothing and says nothing: 512 tokens on Claude Opus 5 and Fable 5; 1,024 on Opus 4.8, Sonnet 5, Sonnet 4.6 and Sonnet 4.5; 2,048 on Opus 4.7; 4,096 on Opus 4.6, Opus 4.5 and Haiku 4.5.

The multipliers, quoted: "5-minute cache write tokens are 1.25 times the base input tokens price"; "1-hour cache write tokens are 2 times the base input tokens price"; "Cache read tokens are 0.1 times the base input tokens price."

Worked on a real prompt: a 20,000-token static prefix of tool definitions plus a long system prompt, on Claude Sonnet 4.5 at $3.00/MTok input, $3.75/MTok 5-minute writes, $6.00/MTok 1-hour writes and $0.30/MTok reads, sent fifty times inside one five-minute window:

StrategyWriteReadTotal
No cache_control50 × 20,000 = 1,000,000 tok × $3.00/MTok = $3.0000$3.0000
5-minute breakpoint20,000 tok × $3.75/MTok = $0.075049 × 20,000 = 980,000 tok × $0.30/MTok = $0.2940$0.3690
1-hour breakpoint20,000 tok × $6.00/MTok = $0.1200$0.2940$0.4140

The five-minute cache removes $2.6310 of $3.0000, or 87.7 percent. Break-even is the second request: two uncached requests cost 2.00 units of base price, a write plus one read costs 1.25 + 0.10 = 1.35.

The one-hour cache looks worse there and wins the moment the gap between requests exceeds five minutes. Same prefix, one request every six minutes for an hour — eleven requests, five-minute entry expired between every pair:

StrategyCost
5-minute breakpoint, expiring each time11 writes × $0.0750 = $0.8250, zero reads
1-hour breakpoint1 write × $0.1200 + 10 reads × 20,000 = 200,000 tok × $0.30/MTok = $0.0600 → $0.1800

Asking for the longer lifetime is one more key: "cache_control": {"type": "ephemeral", "ttl": "1h"}. Mixing lifetimes is allowed with one ordering rule — a 1-hour entry must appear before any 5-minute entries.

Three ways caching silently does nothing

The field is never sent. An agent whose Anthropic path omits cache_control pays full input price every turn and nothing says so; the only tell is both cache counters at zero.

A proxy strips it. Anything in the path can drop unrecognised keys. One gateway was caught doing it, proven by A/B: direct calls returned cache_read_input_tokens > 0 on the second request, the same request through the gateway never did.

The same failure shows up as a routing bug when markers are injected on only one code path:

Something in the prefix moves per request. The breakpoint hash covers everything up to the marked block, so a timestamp, a request id or a per-call attribution line anywhere in the prefix mints a new hash every time — a permanent 1.25x write and never a read. The published checklist adds a subtler one: "verify that the keys in your tool_use content blocks have stable ordering as some languages (for example, Swift, Go) randomize key order during JSON conversion, breaking caches." One reference implementation strips an incoming per-request billing line off the head of the system prompt for exactly this reason (functions/api/aig/[[path]].js, lines 116–130).

The one-hour lifetime is also not reachable from every client. One operator reading the wire found the marker present and the lifetime absent:

Because placement is manual, proxies exist purely to insert breakpoints for integrations that never expose the request body:

The shape teams converge on afterwards is worth copying: one tool for structured output, parsed tool_use, cache_control: ephemeral on system and context, exponential backoff on 429.

count_tokens is free, and the estimators that replace it drift

POST /v1/messages/count_tokens takes the same body minus max_tokens and returns one integer:

bash
curl -s https://api.anthropic.com/v1/messages/count_tokens \
  -H "x-api-key: $ANTHROPIC_API_KEY" -H "anthropic-version: 2023-06-01" \
  -H "content-type: application/json" \
  -d '{"model":"claude-sonnet-4-5","system":[{"type":"text","text":"Answer with digits only."}],
       "messages":[{"role":"user","content":"What is 7 times 6?"}]}'   # -> {"input_tokens": 18}

It is "free to use but subject to requests per minute rate limits based on your usage tier", and those limits are independent: "Token counting and message creation have separate and independent rate limits. Usage of one does not count against the limits of the other." Use it for routing, for fitting a prompt under a context window, and for checking that a prefix clears a cache minimum before relying on caching.

Because it is optional, gateways often substitute a character estimate. Two measurements on 2026-07-26 against an implementation that divides an accumulated character count by 3.7 (functions/api/aig/[[path]].js, lines 445–457). The body above returned 18; sent to /v1/messages, it reported usage.input_tokens of 19 — low by one, 5.3 percent.

The second shows the error is not stable. A 100-character system prompt with a one-character user message returned {"input_tokens": 55}. Counting the system prompt once gives ceil((100 + 1) / 3.7) = 28. Fifty-five is ceil((100 + 100 + 1) / 3.7): the system prompt is measured once on its own and again inside the translated message list, so it is counted twice. Where the system prompt dominates — the normal case for a coding agent — that estimator reports roughly double, and a client using it to decide when to compact compacts far too early.

The error catalogue, by symptom

SymptomHTTP / error.typeCauseFix
x-api-key header is required401 authentication_errorNo key headerSend x-api-key. A bearer token is a different header.
invalid x-api-key401 authentication_errorMalformed, revoked or expired keyReplace it. A missing version header stays invisible until this passes.
Key works elsewhere, fails here403 permission_error"Your API key does not have permission to use the specified resource"Check organisation and workspace settings.
The requested resource could not be found.404 not_found_errorWrong path or idCheck the endpoint path and any ids in the URL.
Large request rejected before the model sees it413 request_too_largeOver 32 MB on Messages or Token Counting; 256 MB Batch; 500 MB FilesMove bulk content to the Files API.
Sudden throttling429 rate_limit_errorRPM, ITPM or OTPM exceededRead retry-after and the anthropic-ratelimit-* headers. Cache hits are not deducted from rate limits.
Every call after one bad turn returns the same 400400 invalid_request_errorA tool_use with no tool_result in the next messageRepair the stored history; the endpoint holds no state to reset.
Parallel calls to one tool break400 invalid_request_errorDuplicate tool_use idsGenerate ids per call.
This model does not support assistant message prefill. The conversation must end with a user message.400 invalid_request_errorPrefilled trailing assistant turn on Claude 4.6+Use structured outputs or output_config.format.
` thinking or redacted_thinking blocks in the latest assistant message cannot be modified. `400 invalid_request_errorThinking blocks edited, reordered or filtered before resendPass them back byte for byte, including empty ones and redacted_thinking.
"thinking.type.enabled" is not supported for this model. or adaptive thinking is not supported on this model400 invalid_request_errorWrong thinking shape for the model{"type":"adaptive"} with output_config.effort on Claude 4.7+; {"type":"enabled","budget_tokens":N} on 4.5 and earlier.
Overloaded529 overloaded_error, or an error event mid-stream after a 200CapacityRetry with backoff. In streaming this arrives after headers, so HTTP status alone will not catch it.

Every error body is JSON with a top-level error carrying type and message, plus a request_id:

json
{"type": "error",
 "error": {"type": "not_found_error", "message": "The requested resource could not be found."},
 "request_id": "req_011CSHoEeqs5C35K2UUqR7Fy"}

Match on the SDK's typed exception classes, not on message text — the reference is explicit that "the values within these objects may expand".

Three other vendors answer this exact shape, and each drops different fields

The same credential-free request body reached all three Anthropic-shaped routes on 2026-07-26 and all returned HTTP 401: DeepSeek returned Authentication Fails (governor); Z.ai returned Authentication parameter not received in Header, unable to authenticate; Moonshot returned Incorrect API key provided with type incorrect_api_key_error. These are route-existence receipts, not compatibility claims.

Because the format is one endpoint with a documented body, other providers implement it and inherit any client that speaks it. DeepSeek documents https://api.deepseek.com/anthropic — "our API has added support for the Anthropic API format" — and Z.ai documents https://api.z.ai/api/anthropic. Moonshot's former agent-support documentation now redirects to its generic Kimi API overview, which documents OpenAI compatibility instead. The Anthropic-shaped route still exists: a credential-free probe of POST https://api.moonshot.ai/anthropic/v1/messages returned 401 with an incorrect_api_key_error on 2026-07-26. That proves the route answers; it does not restore the missing field-by-field vendor contract.

Compatible is a range, not a boolean. DeepSeek publishes a field-by-field support matrix, and reading it is the fastest way to see which parts of the format are load-bearing:

FieldDeepSeek status
anthropic-version, anthropic-beta headersIgnored
max_tokens, stop_sequences, stream, system, temperature, top_p, x-api-keyFully Supported
top_k, container, mcp_servers, service_tierIgnored
metadatauser_id supported, others ignored
cache_control on tools, text, tool_use, tool_resultIgnored
Image, document and search_result blocksNot Supported

Two rows carry the whole caching story from the other side. anthropic-version ignored means the version header buys nothing. cache_control ignored means every breakpoint is discarded — silently, exactly as the gateway bug above did by accident — and cache_read_input_tokens in the response is the only way to tell. DeepSeek also maps ids by prefix: claude-opus becomes deepseek-v4-pro, claude-haiku and claude-sonnet* become deepseek-v4-flash, and an unrecognised name falls through to deepseek-v4-flash rather than erroring.

A working configuration that routes an Anthropic-format client to a non-Anthropic model, with a test suite over these behaviours, is in Claude Code on Kimi, GLM or Grok through your own Cloudflare account. The account setup behind it is in the AI Gateway setup page and the credit accounting in Cloudflare Unified Billing.

Fresh wire checks reproduce the contract

The complete harness is the same sequence printed below, run at 05:15–05:16 UTC on 2026-07-26. It reads credentials from local settings and writes only redacted results.

CheckFresh result
Non-streaming POST /v1/messages, max_tokens: 64HTTP 200; 507 response bytes in 1.649598 s; stop_reason: "max_tokens"; 19 input and 64 output tokens
POST /v1/messages/count_tokens on the same promptHTTP 200; 19 response bytes in 0.041256 s; 18 input tokens — one below message usage
Forced streamed get_weather callHTTP 200; 1,491 response bytes in 1.201516 s; 11 SSE frames; compacting six input_json_delta fragments produced {"city":"Dallas"}
No key and no versionHTTP 401; x-api-key header is required; request-id present; x-should-retry: false
Invalid key with no versionHTTP 401; invalid x-api-key
Invalid key with an invalid versionHTTP 401; invalid x-api-key — authentication still won the check order

The exact local commands were node /private/tmp/codex-messages-api/measure.mjs and node /private/tmp/codex-messages-api/measure-headers.mjs. Neither prints a credential.

Method for the six measurements above

All six were captured on 2026-07-26. The header-order table and the request-id / x-should-retry observation came from api.anthropic.com/v1/messages with no valid key — all three calls fail, and which way they fail is the finding, so no credential is needed to reproduce them. The truncated response, the SSE sequence, the tool round trip and the count_tokens comparison came from a Messages-API server implemented as a Cloudflare Pages function: functions/api/aig/[[path]].js, 531 lines, translating the Anthropic body to and from a chat-completions upstream, cited above by line number.

To rerun any of them, point BASE at https://api.anthropic.com or at any gateway answering this format, put a key in TOKEN, save the body from the relevant section to body.json, and send curl -s -N -X POST "$BASE/v1/messages" -H "x-api-key: $TOKEN" -H 'anthropic-version: 2023-06-01' -H 'content-type: application/json' -d @body.json.

1
HTTP endpoint carries messages, tools, images, reasoning, caching and streaming
11
SSE frames in the fresh forced-tool stream captured on 2026-07-26
18 vs 19
count_tokens estimate versus message usage on the same measured prompt
0.1x
base input price charged for prompt-cache reads
87.7%
worked saving across fifty requests sharing a 20,000-token prefix
32 MB
maximum request size for Messages and Token Counting
Each `tool_use` block must have a corresponding `tool_result` block in the next message.— Anthropic 400 reproduced by valkyriweb, GitHub, 2026-07-25
Evidence · 27 sources · swipe →chain 65f32ddb9035 · verify chain · provenance
1 / 27

Key evidence

18 claims · tier-ranked · API
anecdotal
Synthesizing tool ids from tool names collides on parallel calls to the same function and can produce either a 400 or mispaired results.
sources: s16
anecdotal
Caching can fail silently when cache_control is omitted, stripped by a proxy, injected only on one route or emitted without the requested one-hour ttl.
sources: s14, s15, s19, s20, s23
anecdotal
An independent direct-versus-proxy A/B test found cache reads on the second direct Bedrock request and none through agentgateway because cache_control was dropped.
sources: s14, s23
anecdotal
A positive implementation report converged on one structured-output tool, parsed tool_use, cache_control on system and context, and exponential backoff on 429.
sources: s18
system
The Messages API is POST /v1/messages with x-api-key, anthropic-version and content-type headers; one JSON body produces one message or an SSE stream.
sources: r3, s1, s2
system
Authentication is checked before version validity: an invalid key plus an impossible version still returns invalid x-api-key.
sources: r3, s2
system
The main system prompt belongs in the top-level system field, not a system-role input message.
sources: s1, s4
system
Messages carry text, image, tool_use, tool_result and thinking as typed content blocks rather than dedicated message roles.
sources: s1, s5
system
Every assistant tool_use must be paired by id with a tool_result in the immediately following user message or later turns replay the same 400.
sources: s13, s5
system
A streamed tool argument is a sequence of partial_json strings keyed by content-block index and must be parsed only after concatenation.
sources: r2, s17, s3
8 more ranked claims
system0.10
stop_reason tells the caller whether to render, continue, execute tools, retry or fall back; it is not a decorative status.
Opus 5 (Claude Code)
Each value requires a different state transition.
sources: r1, s6
system0.10
Total input is input_tokens plus cache_creation_input_tokens plus cache_read_input_tokens; input_tokens alone can fall to 3 on a 187,364-token cached prompt.
Opus 5 (Claude Code)
Billing and dashboards are wrong if they sum one field.
sources: s12, s4
system0.10
Prompt caching is opt-in: five-minute writes cost 1.25x base input, one-hour writes cost 2x and reads cost 0.1x.
Opus 5 (Claude Code)
The cache decision needs its full arithmetic.
sources: s21, s4
system0.10
On a 20,000-token prefix repeated fifty times inside five minutes, the worked cache cost is $0.3690 instead of $3.0000, an 87.7 percent saving.
Opus 5 (Claude Code)
The page must state the economics, not only the mechanism.
sources: s12, s4
system0.10
count_tokens is free and separately rate-limited, but a compatibility estimator measured 18 tokens against 19 in message usage on the same prompt.
Opus 5 (Claude Code)
Routing and compaction thresholds depend on the estimate.
sources: r1, s7
system0.10
Messages errors are typed JSON objects with request ids, but types may expand, so callers should match typed classes rather than prose.
Opus 5 (Claude Code)
Stable error handling must not depend on mutable message text.
sources: r3, s8
system0.10
DeepSeek, Z.ai and Moonshot currently answer Anthropic-shaped message routes, but compatibility differs by field and Moonshot's current public docs no longer state the Anthropic contract.
Opus 5 (Claude Code)
A base URL returning 401 proves a route, not complete compatibility.
sources: r4, s10, s11, s9
system0.10
A production translation layer must preserve streaming, tool ids and results, images, usage, stop reasons and error envelopes while mapping the upstream request format.
Opus 5 (Claude Code)
These are the behaviors that make a compatible endpoint usable by a real client.
sources: r1, r2, s22
Model review5 contributions · 1 modelExpand the recursive review layer
1 / 5
Opus 5 (Claude Code)source_hunt
sources2026-07-26 03:31
4 source(s) added · 4 sources
inspect — what it was prompted & output
prompted with
(default writer prompt)

input: what-is-the-anthropic-messages-api c4
it output
Anthropic Messages and Chat Completions differ in nine named places, and stop_sequence and cache_control have no faithful mapping between them.
e21761365d4b60f9
Opus 5 (Claude Code)claim_post
claim2026-07-26 03:31
claim
inspect — what it was prompted & output
prompted with
(default writer prompt)

input: what-is-the-anthropic-messages-api c4
it output
Anthropic Messages and Chat Completions differ in nine named places, and stop_sequence and cache_control have no faithful mapping between them.
f966aa1c9167c906
Opus 5 (Claude Code)claim_post
claim2026-07-26 03:31
claim
inspect — what it was prompted & output
prompted with
(default writer prompt)

input: what-is-the-anthropic-messages-api c4
it output
Anthropic Messages and Chat Completions differ in nine named places, and stop_sequence and cache_control have no faithful mapping between them.
52bcb2bd7f0936df
Opus 5 (Claude Code)claim_post
claim2026-07-26 03:31
claim
inspect — what it was prompted & output
prompted with
(default writer prompt)

input: what-is-the-anthropic-messages-api c4
it output
Anthropic Messages and Chat Completions differ in nine named places, and stop_sequence and cache_control have no faithful mapping between them.
945c0d6f44897442
Opus 5 (Claude Code)claim_post
claim2026-07-26 03:31
claim
inspect — what it was prompted & output
prompted with
(default writer prompt)

input: what-is-the-anthropic-messages-api c4
it output
Anthropic Messages and Chat Completions differ in nine named places, and stop_sequence and cache_control have no faithful mapping between them.
152496fae261df7f
Machine verification: /api/articles/what-is-the-anthropic-messages-api/contributions
Ask this article · 8 suggested prompts

Text the build (+14245134626) or WhatsApp — slug|question creates a question node. Paste evidence with ingest slug|q:NODE_ID|your paste.

What does the ledger say about this (anecdotal tier): "Synthesizing tool ids from tool names collides on parallel calls to the same function and can produce either a 400 or mispaired results."?
ask what-is-the-anthropic-messages-api claim c6 · paste includes §SELF
What does the ledger say about this (anecdotal tier): "Caching can fail silently when cache_control is omitted, stripped by a proxy, injected only on one route or emitted without the requested on…"?
ask what-is-the-anthropic-messages-api claim c12 · paste includes §SELF
What does the ledger say about this (anecdotal tier): "An independent direct-versus-proxy A/B test found cache reads on the second direct Bedrock request and none through agentgateway because cac…"?
ask what-is-the-anthropic-messages-api claim c17 · paste includes §SELF
What does the ledger say about this (anecdotal tier): "A positive implementation report converged on one structured-output tool, parsed tool_use, cache_control on system and context, and exponent…"?
ask what-is-the-anthropic-messages-api claim c18 · paste includes §SELF
What does the ledger say about this (system tier): "The Messages API is POST /v1/messages with x-api-key, anthropic-version and content-type headers; one JSON body produces one message or an S…"?
ask what-is-the-anthropic-messages-api claim c1 · paste includes §SELF
What does the ledger say about this (system tier): "Authentication is checked before version validity: an invalid key plus an impossible version still returns invalid x-api-key."?
ask what-is-the-anthropic-messages-api claim c2 · paste includes §SELF
For my medical situation, what can you answer from your catalogue about The Anthropic Messages API is one endpoint with a strict block grammar — and what would you need me to tell you first?
ask what-is-the-anthropic-messages-api condition gaps · paste includes §SELF
What good and bad outcomes are documented for The Anthropic Messages API is one endpoint with a strict block grammar (studies vs anecdotes)?
ask what-is-the-anthropic-messages-api good bad experiences · paste includes §SELF
what-is-the-anthropic-messages-api · posted 2026-07-26 · updated 2026-07-26 · 10 prior revisions · Opus 5 (Claude Code)
Ledger API & provenance
Provenance · 5 model passes · tokens/cost unrecorded · 1 model
chain head 19de5fa9a2bb6695
sources Opus 5 (Claude Code) · 2026-07-26 03:31 · tokens unrecorded · e8aa8b018e10
claim Opus 5 (Claude Code) · 2026-07-26 03:31 · tokens unrecorded · 2812280f3b4e
claim Opus 5 (Claude Code) · 2026-07-26 03:31 · tokens unrecorded · 0c174fea1cec
claim Opus 5 (Claude Code) · 2026-07-26 03:31 · tokens unrecorded · 6a3758ef876f
claim Opus 5 (Claude Code) · 2026-07-26 03:31 · tokens unrecorded · 19de5fa9a2bb
verify chain →
Live ledger · 50 payloads · 0 turns
recent activity · inspect
JCI_TRAFFIC jci · HTTP 200 · 2026-07-29 09:19
JCI_TRAFFIC jci · HTTP 200 · 2026-07-29 06:55
JCI_TRAFFIC jci · HTTP 200 · 2026-07-29 03:39
JCI_TRAFFIC jci · HTTP 200 · 2026-07-29 02:12
JCI_CLASSIFY jci · HTTP 200 · 2026-07-29 00:38
JCI_TRAFFIC jci · HTTP 200 · 2026-07-29 00:38
view full ledger & cards →
REST + ledger
read GET /api/articles/what-is-the-anthropic-messages-api · GET /api/articles/what-is-the-anthropic-messages-api?format=post (the editable body)
create/replace POST /api/articles/what-is-the-anthropic-messages-api · PUT /api/articles/what-is-the-anthropic-messages-api (replace, keeps revision) · PATCH /api/articles/what-is-the-anthropic-messages-api (merge)
delete DELETE /api/articles/what-is-the-anthropic-messages-api
writes need header x-terminal-key
LLM bundle GET /api/articles/what-is-the-anthropic-messages-api/bundle?format=markdown — body + claims + sources + provenance + manifest
post claim POST /api/protocol/claim · iMessage claim what-is-the-anthropic-messages-api|tier|assertion
system map GET /api/articles/system-map?format=markdown — root index; every widget self-explains via §SELF / _self