OpenFill is in development. Inference is off, but is tested end to end, and will switch on at launch. All data currently on the site is for live testing: it will be erased at launch.

Skip to content
openfill

Messages API

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
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": "market-preview", "max_tokens": 1024, "messages": [{"role": "user", "content": "hi"}], "price_limit": 0.10}'

Headers

HeaderRule
x-api-keyYour OpenFill key. Authorization: Bearer is accepted too, and wins when both are sent.
anthropic-version, anthropic-betaAccepted with any value and ignored.
X-Price-Limit, X-Expires-After, Idempotency-KeyAs on chat. This is how a tool with no body field carries the price limit.

Request body

The Anthropic fields first, then the OpenFill extensions, which carry the same names and rules as on chat. Unlisted fields pass through unless the adapter drops them as described below.

FieldTypeDefaultMeaning
modelstringthe default modelA served model id or alias.
max_tokensintegerrequiredThe output cap, clamped down to the model's max output length. 0 is 400.
systemstring or arraynoneA string, or text blocks joined with a blank line, as the system message.
messagesarrayrequiredContent is a string or blocks. User blocks accept text, image and tool_result, an image with a base64 source, or a url source fetched as on chat; assistant blocks accept text, tool_use and thinking, and discard redacted_thinking; system blocks accept text. Tool results precede the user text, and text and images keep their order.
toolsarraynoneA nonempty name and an input_schema object are required; its type can be object or absent. Description is optional. The tool type can be absent or custom. These become a function tool. cache_control and the other Anthropic-only keys are dropped.
tool_choiceobjectautoauto, any, a named tool, or none. disable_parallel_tool_use turns parallel_tool_calls off.
temperature, top_pnumbernoneValidated as on chat, then forwarded. top_k is dropped.
stop_sequencesarraynoneUp to four strings of up to 256 characters.
streambooleanfalseServer-sent events, in the Anthropic event vocabulary.
metadata.user_idstringnoneMapped to user in the stored chat request and removed before the model request. Other metadata keys are dropped.
thinkingobjectnoneAccepted 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.
container, inference_geo, betas, stream_optionsas sentnoneAccepted and dropped. Terminal success events always carry usage.
output_config.formatobjectnoneA json_schema becomes response_format for the model.
price_limitnumberkey or accountThe most you will pay, in USD per 1M input tokens at the reference mix. On a market model, none anywhere is 400 missing_price_limit. Ignored on a fixed-price model.
price_lockbooleanaccount settingBid the lower of the published quote and price_limit, and start within the execute window or expire with 429 capacity_unavailable. Ignored on a fixed-price model.
max_waitintegeraccount settingSeconds an unlocked order may wait. Alias expires_after.
store, retention_daysas chatnonestore: false keeps nothing durable; retention_days keeps it for fewer days.

Price, wait, output and storage bounds follow the chat request rules. Every message needs a role and content; text and thinking values must be strings, and tool_use requires string id and name fields with an input object.

Refused with 400

SentWhy
document blocks, an image with a file source, and an image inside a tool_resultAn image travels as a base64 source, or a url source fetched as on chat in a user turn. Nothing is charged.
tools with a built-in typeOnly function tools run here.
mcp_serversServer-side MCP is not served.
detached: trueThe 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": "market-preview",  "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": {"order_id": "<order_id>", "pricing": "market", "level_at_execution": 0.23, "bid": 0.5, "charged_usd": 0.000446}}}
FieldMeaning
contentA 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_reasonend_turn on a normal finish, max_tokens when the cap stopped it, tool_use when the model called a tool, refusal when content was filtered, and stop_sequence only when the response carries a matched stop. Otherwise a normal finish is end_turn.
usage.input_tokensThe uncached prompt tokens, as Anthropic counts them; cache_read_input_tokens is the cached share. cache_creation_input_tokens is always 0.
usage.x_marketThe receipt: the price the order was charged at, what it bid, and the debit, with pricing and, on a fixed-price model, the prices applied, as on chat.

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
event: message_start          <- at once, with a placeholder usageevent: ping                   <- optional keepalive during silence, or after requeueevent: content_block_startevent: content_block_delta    <- text_delta per fragmentevent: content_block_stopevent: message_delta          <- stop_reason and the final usage with x_marketevent: message_stop
CaseEvents
A tool callcontent_block_start with the tool_use id and name, input_json_delta with partial_json per fragment, content_block_stop.
Reasoning before the answerA thinking block with thinking_delta events and one signature_delta, closed before the text opens.
An error after the stream openedOne error event carrying the envelope below, then the body ends. The status stays 200. A settlement failure after generation can leave a recorded or uncertain charge; inspect the message and order before retrying.

Keepalive pings continue during silence while queued or generating. Parallel tool calls are emitted as successive blocks: the first streams as it arrives, and its peers are buffered until it closes. There is no [DONE] frame; message_stop ends a successful stream.

Count tokens

POST /v1/messages/count_tokens
curl https://api.openfill.ai/v1/messages/count_tokens \  -H "x-api-key: $OPENFILL_API_KEY" -H "Content-Type: application/json" \  -d '{"model": "market-preview", "messages": [{"role": "user", "content": "hi"}]}'
json
{"input_tokens": 12}

The same body with max_tokens optional. The count estimates prompt tokens from the serialized translated body; it is not a tokenizer count. It creates no order and is never charged. It shares the inference rate budget.

Errors

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

json
{"type": "error", "error": {"type": "rate_limit_error", "message": "...", "code": "capacity_unavailable"}}
Statuserror.typeWhen
400invalid_request_errorA bad body, missing_price_limit, model_deprecated, model_preview, a bad header.
401authentication_errorThe key.
402billing_errorinsufficient_balance at the current level, or at a fixed-price model's prices.
403permission_errorThe account is suspended or being deleted.
404not_found_errormodel_not_found. Unknown routes use the gateway's OpenAI error envelope.
408timeout_errororder_expired: the wait on a market model passed max_wait.
409conflict_errororder_canceled, or idempotency_error.
410not_found_errorresult_unavailable on a replay with nothing stored.
413request_too_largeThe body is over the size limit.
429rate_limit_errorA ceiling, or capacity_unavailable on a missed lock window or when a request to a fixed-price model did not start in time. Retry-After where one is sent.
500api_errorOur side.
502api_errorThe backend failed mid-stream. Nothing charged.
503overloaded_errorThe gateway is restarting or the backend is unavailable.