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": "placeholder-large", "max_tokens": 1024, "messages": [{"role": "user", "content": "hi"}], "max_price": 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-Max-Price, X-Expires-After, Idempotency-KeyAs 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.

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.
messagesarrayrequiredRoles 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.
toolsarraynonename, description and input_schema 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_pnumbernoneForwarded. top_k is dropped.
stop_sequencesarraynoneUp to four strings of up to 256 characters.
streambooleanfalseServer-sent events, in the Anthropic event vocabulary.
metadata.user_idstringnoneForwarded as user.
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.
output_config.formatobjectnoneA json_schema becomes response_format for the model.
max_pricenumberkey or accountThe most you will pay, in USD per 1M input tokens at the reference mix. None anywhere is 400 missing_bid.
price_lockbooleanaccount settingBid the lower of the published quote and max_price, and start within the execute window or expire with 429 capacity_unavailable.
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.

Refused with 400

SentWhy
image, document and other non-text blocksText only. 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": "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}}}
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, stop_sequence when one matched.
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.

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                   <- every 10 s while the order waits, and while it generatesevent: 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 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
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"}}
Statuserror.typeWhen
400invalid_request_errorA bad body, missing_bid, model_deprecated, a bad header.
401authentication_errorThe key.
402billing_errorinsufficient_balance at the current level.
403permission_errorThe account is suspended or being deleted.
404not_found_errormodel_not_found, or an unknown route.
408timeout_errororder_expired: the wait 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 limit.
429rate_limit_errorA ceiling, or capacity_unavailable on a missed lock window. 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.