# Messages API

> The Markdown form of https://openfill.ai/docs/api/messages. The index of every docs page is https://openfill.ai/llms.txt, and every page in one file is https://openfill.ai/llms-full.txt.

The Anthropic Messages shape, translated at the edge onto the same order pipeline as chat. A request here is priced, queued, charged, logged and replayed exactly as a chat request; the body, the events and the error envelope are Anthropic's.

## Create a message

**POST /v1/messages**

```bash
curl https://api.openfill.ai/v1/messages \
  -H "x-api-key: $OPENFILL_API_KEY" \
  -H "anthropic-version: 2023-06-01" \
  -H "Content-Type: application/json" \
  -d '{"model": "placeholder-large", "max_tokens": 1024, "messages": [{"role": "user", "content": "hi"}], "max_price": 0.10}'
```

### Headers

| Header | Rule |
| --- | --- |
| x-api-key | Your OpenFill key. Authorization: Bearer is accepted too, and wins when both are sent. |
| anthropic-version, anthropic-beta | Accepted with any value and ignored. |
| X-Max-Price, X-Expires-After, Idempotency-Key | As on chat. This is how a tool with no body field carries the max price. |

### Request body

The Anthropic fields first, then the OpenFill extensions, which carry the same names and rules as on chat. A field absent from this table is forwarded to the model untouched.

| Field | Type | Default | Meaning |
| --- | --- | --- | --- |
| model | string | `the default model` | A served model id or alias. |
| max_tokens | integer | required | The output cap, clamped down to the model's max output length. 0 is 400. |
| system | string or array | none | A string, or text blocks joined with a blank line, as the system message. |
| messages | array | required | Roles user, assistant and system. Content is a string or blocks: text, tool_use, tool_result and thinking. A tool_result becomes a tool message placed before the user text. |
| tools | array | none | name, description and input_schema become a function tool. cache_control and the other Anthropic-only keys are dropped. |
| tool_choice | object | `auto` | auto, any, a named tool, or none. disable_parallel_tool_use turns parallel_tool_calls off. |
| temperature, top_p | number | none | Forwarded. top_k is dropped. |
| stop_sequences | array | none | Up to four strings of up to 256 characters. |
| stream | boolean | `false` | Server-sent events, in the Anthropic event vocabulary. |
| metadata.user_id | string | none | Forwarded as user. |
| thinking | object | none | Accepted and ignored, as are context_management, cache_control and service_tier, so a client that sends them by default keeps working. Reasoning is the model's own decision. |
| output_config.format | object | none | A json_schema becomes response_format for the model. |
| max_price | number | `key or account` | The most you will pay, in USD per 1M input tokens at the reference mix. None anywhere is 400 missing_bid. |
| price_lock | boolean | `account setting` | Bid the lower of the published quote and max_price, and start within the execute window or expire with 429 capacity_unavailable. |
| max_wait | integer | `account setting` | Seconds an unlocked order may wait. Alias expires_after. |
| store, retention_days | as chat | none | store: false keeps nothing durable; retention_days keeps it for fewer days. |

### Refused with 400

| Sent | Why |
| --- | --- |
| image, document and other non-text blocks | Text only. Nothing is charged. |
| tools with a built-in type | Only function tools run here. |
| mcp_servers | Server-side MCP is not served. |
| detached: true | The stored result is chat-shaped, so the detached path is POST /v1/chat/completions. |

### Response

```json
{
  "id": "msg_<order_id>", "type": "message", "role": "assistant", "model": "placeholder-large",
  "content": [
    {"type": "thinking", "thinking": "...", "signature": ""},
    {"type": "text", "text": "..."},
    {"type": "tool_use", "id": "...", "name": "get_weather", "input": {"city": "Boston"}}
  ],
  "stop_reason": "tool_use", "stop_sequence": null,
  "usage": {"input_tokens": 234, "output_tokens": 56, "cache_read_input_tokens": 1000, "cache_creation_input_tokens": 0,
            "x_market": {"level_at_execution": 0.23, "bid": 0.5, "charged_usd": 0.0023}}
}
```

| Field | Meaning |
| --- | --- |
| content | A thinking block when the model sent reasoning, then the text, then one tool_use block per call. A block absent from the answer is absent from the list. |
| stop_reason | end_turn on a normal finish, max_tokens when the cap stopped it, tool_use when the model called a tool, stop_sequence when one matched. |
| usage.input_tokens | The uncached prompt tokens, as Anthropic counts them; cache_read_input_tokens is the cached share. cache_creation_input_tokens is always 0. |
| usage.x_market | The receipt: the price the order was charged at, what it bid, and the debit. |

### Streaming

`200` and a `text/event-stream` body in the Anthropic event vocabulary, each frame an `event:` line and a `data:` line.

**a text answer, in order**

```bash
event: message_start          <- at once, with a placeholder usage
event: ping                   <- every 10 s while the order waits, and while it generates
event: content_block_start
event: content_block_delta    <- text_delta per fragment
event: content_block_stop
event: message_delta          <- stop_reason and the final usage with x_market
event: message_stop
```

| Case | Events |
| --- | --- |
| A tool call | content_block_start with the tool_use id and name, input_json_delta with partial_json per fragment, content_block_stop. |
| Reasoning before the answer | A thinking block with thinking_delta events and one signature_delta, closed before the text opens. |
| An error after the stream opened | One error event carrying the envelope below, then the body ends. The status stays 200 and nothing is charged. |

> The ping every ten seconds is a parsed Anthropic event and it carries bytes, so both of Claude Code's watchdogs stay quiet through a queue. Its whole-request timeout, ten minutes by default, is the reader's to raise for a longer wait.

## Count tokens

**POST /v1/messages/count_tokens**

```bash
curl https://api.openfill.ai/v1/messages/count_tokens \
  -H "x-api-key: $OPENFILL_API_KEY" -H "Content-Type: application/json" \
  -d '{"model": "placeholder-large", "messages": [{"role": "user", "content": "hi"}]}'
```

```json
{"input_tokens": 12}
```

The same body without `max_tokens`. The count is the gateway's own prompt estimate, the arithmetic it prices a request with, so it is close rather than exact. It creates no order and is never charged. Claude Code reads it for its context gauge; without it the gauge would be fed through a paid request.

## Errors

Every error on these two routes is the Anthropic envelope, with the gateway's message unchanged and its code kept beside the type so `missing_bid` and `capacity_unavailable` stay machine-readable.

```json
{"type": "error", "error": {"type": "rate_limit_error", "message": "...", "code": "capacity_unavailable"}}
```

| Status | error.type | When |
| --- | --- | --- |
| 400 | invalid_request_error | A bad body, missing_bid, model_deprecated, a bad header. |
| 401 | authentication_error | The key. |
| 402 | billing_error | insufficient_balance at the current level. |
| 403 | permission_error | The account is suspended or being deleted. |
| 404 | not_found_error | model_not_found, or an unknown route. |
| 408 | timeout_error | order_expired: the wait passed max_wait. |
| 409 | conflict_error | order_canceled, or idempotency_error. |
| 410 | not_found_error | result_unavailable on a replay with nothing stored. |
| 413 | request_too_large | The body is over the limit. |
| 429 | rate_limit_error | A ceiling, or capacity_unavailable on a missed lock window. Retry-After where one is sent. |
| 500 | api_error | Our side. |
| 502 | api_error | The backend failed mid-stream. Nothing charged. |
| 503 | overloaded_error | The gateway is restarting or the backend is unavailable. |

Previous: [Responses API](https://openfill.ai/docs/api/responses). Next: [Orders, account and usage](https://openfill.ai/docs/api/orders).
