# Anthropic format

> Original: https://api.6io.io/en/docs/messages/ (HTML). All pages: https://api.6io.io/llms.txt

`POST /v1/messages` takes a request in the Anthropic Messages format and works with `op` models ([OpenRouter](https://api.6io.io/md/en/openrouter.md)). 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

```bash
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](https://docs.anthropic.com/en/api/messages). Default provider-routing settings may be added; see [OpenRouter models](https://api.6io.io/md/en/openrouter.md). 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](https://api.6io.io/md/en/billing.md).

## 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.

```python
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_delta` chunk has `usage`. Its `usage.cost` is the cost at the provider in dollars, not our charge. The amount in credits is in `GET /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: no` header.

## Errors

On this route all our errors come in the Anthropic format:

```json
{"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](https://api.6io.io/md/en/retries.md) and [Errors and limits](https://api.6io.io/md/en/errors-and-limits.md).

## 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) and `preset` fields;
- paid server-side tools: `tools` with the type `openrouter:…`, and the Anthropic types `web_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.
