Messages (Anthropic format)

POST /v1/messages creates a model response in the Anthropic Messages format, which Claude Code and the Anthropic SDKs send. The base URL is the origin only, https://api.zdr.sh: the SDKs append /v1/messages themselves.

Headers#

HeaderNotes
x-api-keyThe duck- key. Authorization: Bearer is accepted instead
anthropic-versionAny value is accepted; the answer uses the current format
anthropic-betaAccepted as an open list; a beta the model does not know is ignored

POST /v1/messages?beta=true is accepted.

Request#

FieldTypeNotes
modelstringRequired. A model id from GET /v1/models. A claude- id is mapped when the key's Claude mapping is on, which is the default; otherwise it is refused with not_found_model
max_tokensintegerRequired. The output cap, reasoning included
messagesarrayuser and assistant turns. Content is a string or blocks: text, image, tool_use, tool_result, thinking
systemstring or blocksThe system prompt, kept at the front of the prefix
streambooleanDefault false. With true, Anthropic server-sent events
toolsarray{ "name", "description", "input_schema" }
tool_choiceobject{"type": "auto"}, {"type": "any"}, {"type": "tool", "name": "..."} or {"type": "none"}
thinkingobject{"type": "enabled", "budget_tokens": N} or {"type": "adaptive"} on reasoning models
output_configobjecteffort is read as the reasoning effort
metadataobjectuser_id names the end user; other flat entries are kept for attribution and never sent to a model
stop_sequencesarrayStop sequences

Documents arrive as blocks with a source of base64 or url (PDFs). Server tools such as web_search_20250305 are accepted when the model supports them; otherwise they are left out and x-token-notice says <tool> unavailable.

cURLbash
curl -sSf https://api.zdr.sh/v1/messages \  -H "x-api-key: $ZDR_API_KEY" \  -H "anthropic-version: 2023-06-01" \  -H "Content-Type: application/json" \  -d '{"model": "glm-5.3", "max_tokens": 128, "messages": [{"role": "user", "content": "Reply with the single word: ready."}]}'

Thinking blocks#

Reasoning comes back as thinking blocks, streamed as content_block_delta events with thinking_delta and signature_delta. The signature is issued by zdr.sh. Send the block back unchanged on the next turn when your flow replays it.

Tool use#

The model calls tools with tool_use blocks carrying an id, a name and a JSON input. Answer with a tool_result block that carries the same tool_use_id. Tool call ids are rewritten, so an id you received is valid to send back and one you invent is not.

Response#

json
{"id":"msg_9f2f0a...","type":"message","role":"assistant","model":"glm-5.3","content":[{"type":"text","text":"Ready."}],"stop_reason":"end_turn","stop_sequence":null,"usage":{"input_tokens":24,"output_tokens":8,"cache_read_input_tokens":0,"cache_creation_input_tokens":0},"ai_generated":true,"ai_notice":"[AI-generated content]"}
FieldTypeNotes
idstringmsg_ followed by random characters
modelstringThe id you asked for, or the id the Claude mapping chose
contentarraytext, thinking and tool_use blocks
stop_reasonstringend_turn, max_tokens, stop_sequence, tool_use or refusal
usage.input_tokensintegerUncached input tokens
usage.output_tokensintegerGenerated tokens, thinking included
usage.cache_read_input_tokens, usage.cache_creation_input_tokensintegerCached input and cache writes. A cache write is free
ai_generated, ai_noticeboolean, stringThe AI marker. AI transparency

Count tokens#

POST /v1/messages/count_tokens returns {"input_tokens": N} for a payload without running it. Nothing is billed. A count uses one request of the key's rate limit and one account concurrency slot, exactly as the request it sizes.

cURLbash
curl -sSf https://api.zdr.sh/v1/messages/count_tokens \  -H "x-api-key: $ZDR_API_KEY" \  -H "anthropic-version: 2023-06-01" \  -H "Content-Type: application/json" \  -d '{"model": "glm-5.3", "messages": [{"role": "user", "content": "Count this sentence."}]}'

Errors and headers#

Errors keep the Anthropic shape with the request id: {"type": "error", "error": {"type": "...", "message": "..."}, "request_id": "..."}. A 503 is overloaded_error, a 401 authentication_error. Errors lists the classes and the retry rules.

The headers are the same as on the OpenAI endpoints: x-request-id, x-token-cost, x-ratelimit-*, x-token-cache, x-token-notice, x-ai-generated and x-ai-model, and retry-after on a 429 or 503.