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

OpenAI compatibility

OpenFill serves the OpenAI chat and completion shapes, adds a few fields, and refuses a few. This page says which is which, so a port is a base URL change plus a max price.

Endpoints

By the call you make in an OpenAI SDK. A served endpoint takes the request shape the SDK already sends, and a max price by any of the routes below.

SDK callEndpointStatus
chat.completions.createPOST /v1/chat/completionsServed. Text only.
completions.createPOST /v1/completionsServed. Tools are refused here.
models.listGET /v1/modelsServed, with pricing weights and length limits per model.
files.createPOST /v1/filesServed for purpose batch.
files.retrieve, files.contentGET /v1/files/{id}Served.
batches.*POST and GET /v1/batchesServed: create, list, retrieve, cancel.
embeddings.create/v1/embeddingsRefused with 404.
responses.create/v1/responsesRefused with 404.
images, audio, moderationsRefused with 404.
fine_tuning, assistantsRefused with 404.
Anthropic Messages/v1/messagesRefused with 404. Tools that speak only this API cannot be pointed here yet.

OpenFill adds endpoints no OpenAI SDK has a method for: orders, your account defaults, usage, and the public market data. They are plain HTTP, listed on the API reference.

Request fields

Each field the OpenAI shape carries is treated one of four ways: checked and forwarded, forwarded untouched, removed, or refused. Anything absent from this table is forwarded to the model untouched.

FieldOnWhat happens
modelbothChecked against GET /v1/models. Absent, the default model is used. A retired model answers 400 model_deprecated.
messageschatRequired, at least one. Each message is forwarded whole, so tool results and names pass through.
promptcompletionsA string, or an array of one string. A longer array is refused with 400.
streambothHonoured. See streaming.
max_tokens, max_completion_tokensbothChecked, then clamped to the model's max output length. A missing cap is that length.
temperature, top_p, stopbothRange checked, then forwarded. stop takes up to four strings.
n, best_ofbothMust be 1 or absent. Anything else is refused with 400 before the order exists.
tools, tool_choicechatForwarded. On /v1/completions they are refused with 400, since that shape has nowhere to put a call.
response_format, seed, userbothForwarded to the model untouched.
logprobs, top_logprobschatForwarded on chat. Removed on /v1/completions, whose response has nowhere to put them.
stream_options.include_usagebothHonoured. The final chunk of a stream carries usage only when this is true.
echo, suffixcompletionsRemoved before the request reaches the model.

Content

Text only. A message part of type image_url, input_audio or file reaches the model and is refused by it, which comes back as 400. Nothing is charged, since nothing ran.

A body over 8 MB is refused with 413, and any string carrying a null character is refused with 400.

The OpenFill fields

Six fields on the request body, none of them in the OpenAI shape. With the OpenAI SDKs they travel in extra_body; the table below this one shows the route in each SDK.

FieldTypeDefaultMeaning
max_pricenumberkey or accountThe most you will pay, in USD per 1M input tokens at the reference mix (90% cached, 2% output). A request with no max price anywhere is refused with 400 missing_bid.
price_lockbooleanaccount settingtrue bids the published quote instead of your max price, so the price is fixed before you send. The request starts within 10 seconds or cancels with 429.
max_waitintegeraccount settingSeconds the request may wait in the queue, 10 seconds to 30 days. Alias expires_after. Past it the request expires with 408 and no charge.
detachedbooleanfalseAnswer 202 with an order id at once and run the request in the background. Cannot be combined with stream.
storebooleanaccount settingfalse keeps nothing durable for this request: no result, no request log. It can only shorten what your account keeps.
retention_daysintegeraccount settingKeep this request's data for fewer days than your account setting. Clamped down to the setting; it never extends it.

Headers

HeaderMeaning
AuthorizationBearer and your key. Required on every endpoint that is yours.
X-Max-PriceThe same parameter as max_price, for a client that cannot add a body field. The body wins if both are present.
X-Expires-AfterThe same parameter as max_wait. The body wins if both are present.
Idempotency-KeyUp to 255 characters. A retry with the same key returns the original request's outcome instead of running it again.

Carrying a max price from each SDK

The OpenAI SDKs type their request bodies, so a field they do not declare needs the SDK's own route for one. The header works from every client that can set one.

ClientRouteExample
Pythonextra_bodyextra_body={"max_price": 0.10}
TypeScriptheader, or a cast{ headers: { "X-Max-Price": "0.10" } }
Gobody optionoption.WithJSONSet("max_price", 0.10)
Javabody property.putAdditionalBodyProperty("max_price", JsonValue.from(0.10))
Rubyrequest optionrequest_options: {extra_body: {max_price: 0.10}}
curlthe body"max_price": 0.10

In TypeScript the body route is a // @ts-expect-error line above max_price: 0.10, which the SDK forwards at runtime. A tool with no field and no header, such as a coding agent's settings screen, works once a max price is set on the key or the account, on Bidding.

What comes back

The OpenAI response shapes, with one object added and every choice list of length one.

FieldMeaning
usage.x_market.level_at_executionThe clearing level the request was charged against, in the max price's unit.
usage.x_market.bidThe price the request rested at: your max price, or the quote when locked.
usage.x_market.charged_usdWhat the request cost, in USD to nine decimal places. 0 at a level of 0.
usage.prompt_tokens_details.cached_tokensThe prompt tokens served from cache, which are priced at the cache-hit weight.
choices[0].message.reasoning_contentA reasoning model's chain of thought, passed through as the model sends it. In a stream it arrives in delta.reasoning_content.
choices[0].message.tool_callsAssembled from the model's stream, so a call split across chunks arrives whole.
choices[0].logprobsPresent when you asked for logprobs on chat. Always null on /v1/completions.

Reasoning models

Reasoning tokens are output tokens. They are charged at the output weight and they count toward max_tokens ahead of the visible answer, so a small cap can spend the whole budget on reasoning and return an empty content with finish_reason of length. The tokens generated are charged either way. Leave max_tokens unset, which allows the model's full output length, or set it with room for the reasoning.

A port in two lines

Both official SDKs read OPENAI_BASE_URL and OPENAI_API_KEY, as do most tools built on them. With those set, code that names no base URL reaches OpenFill unchanged, and the max price comes from the key or the account.

bash
export OPENAI_BASE_URL="https://api.openfill.ai/v1"export OPENAI_API_KEY="$OPENFILL_API_KEY"

Some newer SDK helpers default to the Responses API, which OpenFill does not serve. Use the chat completions method, or the framework's chat model class, and the request lands on /v1/chat/completions.