Messages (Anthropic shape)
/v1/messagesThe same chat, in the Anthropic shape: a different request and response body.
Request
modelstringbodyrequiredmessagesarraybodyrequiredmax_tokensintegerbodysystemstring|arraybodytoolsarraybodytool_choiceobjectbodystreambooleanbodyResponses
Response
{
"id": "chatcmpl-…",
"type": "message",
"role": "assistant",
"model": "gpt-5.6-sol",
"content": [{ "type": "text", "text": "Hello!" }],
"stop_reason": "end_turn",
"stop_sequence": null,
"usage": { "input_tokens": 11, "output_tokens": 2, "cost_usd": "0.0000465" }
}Details
Needs a key in Authorization: Bearer or x-api-key.
The gateway accepts the Anthropic shape but does NOT proxy it verbatim: the body is rewritten into chat/completions, and the upstream answer is folded back into an Anthropic message (the id comes from the upstream, the model is the logical one you asked for). That way any catalog model works here, not only Claude.
Carried over from the body: model, system, messages, max_tokens, temperature, top_p, stream, metadata, stop_sequences (as stop), tools (name/description/input_schema → function) and tool_choice (auto → auto, any → required, tool → that named function). This is the one text route where other fields do NOT reach the upstream: the converter builds a fresh body from that list. Assistant tool_use blocks and user tool_result blocks are converted both ways, and finish_reason becomes stop_reason (stop → end_turn, length → max_tokens, tool_calls → tool_use, content_filter → stop_sequence). The gateway does not require max_tokens: without it the field never appears in the body sent upstream, and the request's worst-case price is built on the output ceiling from the model's catalog row. Anthropic's telemetry block inside system (x-anthropic-billing-header: …) is dropped: its per-request nonce sits at the very front of the prompt and busts the upstream's prefix cache every time.
This request's cost arrives inside the answer: the x-teamtoken-cost-usd header and usage.cost_usd in the body. The value is a decimal string ("0.0000465"), not a number: a number would be re-displayed by the client language's own float rules (Python would show 4.65e-05), while a string reaches your code exactly as written. The cost can also fail to arrive at all — then it is in neither the header nor the field: on a non-streaming answer when the upstream did not report it, on a stream when the model has no catalog tariff. The stream here is synthetic: the upstream answers in full and the SSE is built from the finished message — so the cost is known before the first byte, the header is present on streams too, and cost_usd sits in message_start inside message.usage (into message_delta the gateway puts output_tokens only). There is no idempotency on this route: the gateway does not cache an Anthropic answer, so a byte-identical repeat reaches the model again and is paid for again. Alongside lives POST /v1/messages/count_tokens, which answers {"input_tokens": N} — an ESTIMATE (characters/4 plus per-message and per-tool overhead), not a tokenizer's verdict. Every error arrives in one envelope (the code field is not on every status): { "error": { "message": "...", "type": "...", "code": "PROVIDER_CODE" } }.
Moderation, when it is enabled, reads the request text and blocks only on a real verdict: a moderator that is down or erroring lets the request through. The answer always carries back the logical model name you asked for. If it does not fit the gateway's wait (300 s read by default, 60 s without bytes on a stream) you get a 504; the gateway marks that non-streaming attempt, and if the upstream finished and billed anyway, the reconciler credits the amount back to your balance as a compensating grant. Upstream host names are scrubbed out of error bodies, so an error's text can differ from what the upstream sent.
This path has no route of its own: one gateway handler takes every POST /v1/*, and its list of accepted paths is closed — any other /v1/* answers 404.