POST /v1/messages
The Anthropic Messages wire. Base URL for Anthropic clients: https://sator-api.princep.org — no /v1, because Anthropic SDKs and Claude Code append /v1/messages themselves. Requests to /v1/messages?beta=true, which Claude Code sends, route normally.
Request#
curl https://sator-api.princep.org/v1/messages \
-H "x-api-key: $SATOR_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "Content-Type: application/json" \
-d '{
"model": "deepseek-v4-flash",
"max_tokens": 64,
"system": "Be brief.",
"messages": [{"role": "user", "content": "Say hi"}]
}'| Field | Notes |
|---|---|
model | Required. An id from Models. Unknown → 404. |
max_tokens | Required, a positive integer. Missing → 400 naming max_tokens. A value above the model's max output is lowered to it. |
messages | Required. user and assistant turns; content as a string or as text, image, document, tool_use, tool_result and thinking blocks. A system message is also accepted in place, text only (a string or text blocks), as Claude Code sends it mid-conversation. |
system | Top-level, a string or an array of text blocks. |
stream | true for server-sent events. See Streaming. |
temperature, top_p, top_k, stop_sequences | Sampling, passed through. |
tools, tool_choice | See Tool calling. |
output_config.format | json_schema structured output. See Tool calling. |
thinking | Accepted; translated to a reasoning-effort setting for models that express it that way. Only when it is enabled does a translated model's reasoning come back as thinking blocks — unsigned, ahead of the text; otherwise the reasoning is left out of the reply and still billed as output. |
user | Passed through. |
Headers: the key in x-api-key or Authorization: Bearer (either works); anthropic-version, defaulting to 2023-06-01 if absent; anthropic-beta, forwarded as sent. Bodies over 5 MB are rejected 413.
There is no prompt_cache_key field on this wire. Use the x-session-id header instead — see Prompt caching.
Response#
{
"id": "msg_...",
"type": "message",
"role": "assistant",
"model": "deepseek-v4-flash",
"content": [{"type": "text", "text": "Hi there! How can I help?"}],
"stop_reason": "end_turn",
"stop_sequence": null,
"usage": {
"input_tokens": 12,
"output_tokens": 8,
"cache_read_input_tokens": 0,
"cache_creation_input_tokens": 0
}
}cache_read_input_tokens were billed at the cache-read rate. Every response carries an x-request-id header, which is also the request_id inside an error body.
Streaming#
With "stream": true the body is text/event-stream with named events, in this order:
event: message_start
data: {"type":"message_start","message":{"id":"msg_...","type":"message","role":"assistant","model":"deepseek-v4-flash","content":[],"stop_reason":null,"usage":{"input_tokens":12,"output_tokens":0,"cache_read_input_tokens":0,"cache_creation_input_tokens":0}}}
event: content_block_start
data: {"type":"content_block_start","index":0,"content_block":{"type":"text","text":""}}
event: content_block_delta
data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"Hi there! How can I help?"}}
event: content_block_stop
data: {"type":"content_block_stop","index":0}
event: message_delta
data: {"type":"message_delta","delta":{"stop_reason":"end_turn","stop_sequence":null},"usage":{"input_tokens":12,"output_tokens":8,"cache_read_input_tokens":0,"cache_creation_input_tokens":0}}
event: message_stop
data: {"type":"message_stop"}The stream always ends at message_stop. ping events may appear and can be ignored.
POST /v1/messages/count_tokens#
The Anthropic wire's token counter, served so that clients which count context through it — Anthropic SDKs, Claude Code — never have to fall back to a billed inference request.
curl https://sator-api.princep.org/v1/messages/count_tokens \
-H "x-api-key: $SATOR_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "Content-Type: application/json" \
-d '{"model": "deepseek-v4-flash", "system": "Be brief.", "messages": [{"role": "user", "content": "Say hi"}]}'{"input_tokens": 9}- Request:
model(required),messages(required), optionalsystemandtools. Same key check as/v1/messages; an unknown model is a404 not_found_error. - The count is an estimate, not the model's tokenizer. It is a deterministic characters-per-token rule over your own text plus a small per-message overhead, within roughly ±15% on English prose and code and further out on CJK text; tool schemas are undercounted. It excludes the model's chat-template and system overhead, so on a short prompt it reads well below
usage.input_tokens(6 against 85 for "Say hi" in our test). Use it for context budgeting, not for predicting a bill — the real counts arrive inusageon every completion. - Never billed, and not counted against the 300 requests/minute ceiling. Nothing is sent to a model.
Errors#
The Anthropic envelope, {"type": "error", "error": {"type", "message"}, "request_id"}, with the error type derived from the status the way Anthropic's SDKs expect. Every status and code is in Errors.