Anthropic format
POST /v1/messages takes a request in the Anthropic Messages format and works with op models (OpenRouter). You can use the Anthropic SDK and other compatible clients that allow changing the address and key, subject to the limitations below. The vn group (Venice) is not available in this format: it has no native Anthropic endpoint.
A model is named the same way as across the API: op/<model>. Bare Anthropic names (claude-…) are not accepted — GET /v1/models lists every model with prices.
A request
curl https://api.6io.io/v1/messages \
-H "Authorization: Bearer sixio_sk_..." \
-H "anthropic-version: 2023-06-01" \
-H "Content-Type: application/json" \
-d '{
"model": "op/anthropic/claude-haiku-4.5",
"max_tokens": 200,
"messages": [
{"role": "user", "content": "Answer briefly: what is Idempotency-Key for?"}
]
}'The key can also be sent the way Anthropic clients do — in the x-api-key: sixio_sk_... header. Both ways work; there is no need to send both. The anthropic-version header is forwarded to the provider; anthropic-beta and other additional Anthropic headers are not, so features that require them are unavailable.
The required fields are model, max_tokens (a positive integer) and a non-empty messages. Supported Anthropic parameters (system, temperature, tools, metadata and others) go to the provider: what they mean is in the format documentation. Default provider-routing settings may be added; see OpenRouter models. Messages with tool_use, tool_result and image blocks work as they do at the provider.
The response comes in the Anthropic format. model holds the name you sent (op/…), and the request ID is in the X-Sixio-Request-Id header.
max_tokens is required for a reason: before sending, we reserve money for the longest answer you allowed. Only what was actually spent is charged. More in Balance and prices.
Streaming
With "stream": true the answer arrives as it is generated, as SSE, in the Anthropic format: message_start, content_block_start, content_block_delta (including tool-call deltas — input_json_delta), content_block_stop, message_delta with the final usage, and message_stop at the end. The event: and ping lines pass as they are. There is no [DONE] marker from the OpenAI format here — the stream ends with the message_stop event.
from anthropic import Anthropic
client = Anthropic(base_url="https://api.6io.io", api_key="sixio_sk_...", max_retries=0)
with client.messages.stream(
model="op/anthropic/claude-haiku-4.5",
max_tokens=300,
messages=[{"role": "user", "content": "Tell a short fairy tale"}],
) as stream:
for text in stream.text_stream:
print(text, end="", flush=True)The example disables automatic SDK retries: after a disconnect or ambiguous response, a retry may start another paid generation. For retries, use one Idempotency-Key for the original request and first check its status using X-Sixio-Request-Id, if received.
- The
message_deltachunk hasusage. Itsusage.costis the cost at the provider in dollars, not our charge. The amount in credits is inGET /v1/requests/{id}. - If you close the connection early, generation at the provider may already have started and its cost will be accounted for. We keep reading the response, but if the cost is not received, the reserve remains held until it is confirmed or an administrator resolves it.
- If there is an intermediate server between you and the API (nginx and the like), turn off response buffering. We send the
X-Accel-Buffering: noheader.
Errors
On this route all our errors come in the Anthropic format:
{"type": "error", "error": {"type": "billing_error", "message": "Insufficient credits"}}| Status | error.type |
When it happens |
|---|---|---|
| 400 | invalid_request_error |
a bad body, no max_tokens or messages, a rejected parameter |
| 401 | authentication_error |
the key is missing or wrong |
| 402 | billing_error |
not enough credits on the balance (including for a free model) |
| 403 | permission_error |
the account is blocked or restricted |
| 404 | not_found_error |
no such model or page |
| 409 | invalid_request_error |
a repeat with the same Idempotency-Key |
| 413 | invalid_request_error |
the body is larger than PUBLIC_API_MAX_BODY_KB (4 MB by default) |
| 422 | invalid_request_error |
the same Idempotency-Key with a different body |
| 429 | rate_limit_error |
an account limit was hit, retry after Retry-After |
| 500–503 | api_error |
a failure on our side or at the provider |
Explicit refusals of the request, including a moderation 403, keep the provider's status and body; the reserve is released. We replace other errors with our own response: for example, a provider 429 becomes 503 api_error and releases the reserve, while a provider 5xx or a disconnect after sending leaves the outcome unknown and the reserve held for reconciliation. A 503 status alone therefore does not tell you whether a retry is safe. Check the message and the request status if you have its ID. See Retries and failures and Errors and limits.
Idempotency and free models
The Idempotency-Key header works as in chat: while a request is retained under the key, a repeat with the same key and body does not start a second generation — the client gets 409 with the X-Sixio-Request-Id of the first request. If the reserve is released after an explicit refusal or an error where the request was not accepted, the key is freed too: a retry can start a new generation.
Free models (price 0 in the catalog, the :free suffix at OpenRouter) are called like every other model, and the charge for them is zero. The condition is the same: the API balance must be above zero, otherwise 402 billing_error.
What is rejected before any charge
These requests get 400 invalid_request_error and never reach the provider:
- the
plugins,web_search_options,models(a list of fallback models) andpresetfields; - paid server-side tools:
toolswith the typeopenrouter:…, and the Anthropic typesweb_search_*,computer_*,bash_*,text_editor_*,code_execution_*— the token-based reserve does not estimate their cost.
The :online model suffix is also unsupported: if the model is absent from the catalog, the response is 404 not_found_error; otherwise it is 400 invalid_request_error. The request does not reach the provider.
The client's own tools (tools with the type custom, calls through tool_use/tool_result) work as they do at the provider.