Docs menu

POST /v1/messages

The Anthropic Messages wire. Base URL for Anthropic clients: https://sator-api.princep.orgno /v1, because Anthropic SDKs and Claude Code append /v1/messages themselves. Requests to /v1/messages?beta=true, which Claude Code sends, route normally.

Request#

bash
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"}]
  }'
FieldNotes
modelRequired. An id from Models. Unknown → 404.
max_tokensRequired, a positive integer. Missing → 400 naming max_tokens. A value above the model's max output is lowered to it.
messagesRequired. 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.
systemTop-level, a string or an array of text blocks.
streamtrue for server-sent events. See Streaming.
temperature, top_p, top_k, stop_sequencesSampling, passed through.
tools, tool_choiceSee Tool calling.
output_config.formatjson_schema structured output. See Tool calling.
thinkingAccepted; 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.
userPassed 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#

json
{
  "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.

bash
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"}]}'
json
{"input_tokens": 9}
  • Request: model (required), messages (required), optional system and tools. Same key check as /v1/messages; an unknown model is a 404 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 in usage on 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.