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 price limit.

Endpoints

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

SDK callEndpointStatus
chat.completions.createPOST /v1/chat/completionsServed. Text, and images on a model that reads them.
completions.createPOST /v1/completionsServed. Tools are refused here.
models.listGET /v1/modelsServed, with pricing weights and length limits per model, and pricing: market, or a fixed-price model's three prices.
models.retrieveGET /v1/models/{model}Served. An alias answers with x_alias_of naming its model.
files.createPOST /v1/filesServed for purpose batch.
files.retrieve, files.contentGET /v1/files/{id}, GET /v1/files/{id}/contentServed.
files.list, files.deleteGET /v1/files, DELETE /v1/files/{id}Served. A file a batch is still working from answers 409 file_in_use.
batches.*POST and GET /v1/batchesServed: create, list, retrieve, cancel.
embeddings.create/v1/embeddingsRefused with 404.
responses.create/v1/responsesServed, without server-side state. The Responses API page has the mapping.
images, audio, moderationsRefused with 404.
fine_tuning, assistantsRefused with 404.
Anthropic Messages/v1/messagesServed, with count_tokens beside it. The Messages API page has the mapping.

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, except functions and function_call, which /v1/completions refuses with 400 like tools, and the identifiers listed below, which stay on our side.

FieldOnWhat happens
modelbothChecked against GET /v1/models. Absent, the default model is used. A retired model answers 400 model_deprecated, and a preview of the market 400 model_preview until it opens.
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, null 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, seedbothForwarded to the model untouched.
user, safety_identifier, metadata, prompt_cache_keybothAccepted and kept on our side. These fields are removed before execution; content inside messages and tools is sent to the model.
logprobs, top_logprobschatForwarded on chat. Removed on /v1/completions, whose response has nowhere to put them.
stream_options.include_usagebothHonoured. A live stream sends its final usage chunk when this is true. An idempotent streaming replay includes usage in its single result chunk.
echo, suffixcompletionsRemoved before the request reaches the model.

Content

A user message may carry images as image_url parts on a model whose datasheet on GET /v1/models lists vision. Each is a base64 data URL, or an http or https URL to a PNG, JPEG, GIF or WebP image of up to 6 MB, with at most 16 URLs and 6 MB of fetched images to a request. A URL is fetched when the request is submitted, or on a batch line when the line becomes an order, from our network and with no credentials, cookies or headers of yours, following up to 3 redirects within 15 seconds for each image; the image then travels with the request exactly as its bytes would have, and a retry under the same Idempotency-Key replays the request's order without fetching again. A URL that cannot be fetched is refused with 400 image_url_unfetchable, naming the part and the reason. A model listed as text only refuses an image with 400, and so does a model server that cannot read one. A part of any other type, such as input_audio, video_url or file, is refused with 400 by every model whose datasheet lists its capabilities. 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
price_limitnumberkey or accountThe most you will pay, in USD per 1M input tokens at the reference mix (90% cached, 2% output). A request to a market model with no price limit stated anywhere is refused with 400 missing_price_limit. Ignored on a fixed-price model, whose prices are under pricing on GET /v1/models.
price_lockbooleanaccount settingtrue bids the lower of the published quote and your price limit, so the most you can be charged is known before you send; the charge is still the level when that is lower. The request starts within 10 seconds or expires with 429. Ignored on a fixed-price model, whose price is known before the request is sent; that request waits up to its order timeout.
max_waitintegeraccount settingSeconds an unlocked request may wait in the queue, 10 seconds to 30 days. Alias expires_after. Past it the request expires with 408 and no charge, or with 429 capacity_unavailable on a fixed-price model. A locked request on a market model ignores it; its wait is bounded by the execute window.
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-Price-LimitThe same parameter as price_limit, for a client that cannot add a body field. The body wins if both are present. Ignored on a fixed-price model.
X-Expires-AfterThe same parameter as max_wait. The body wins if both are present, and a header that is not a whole number of seconds is refused with 400 even then.
Idempotency-KeyUp to 255 characters. A retry with the same key returns the original's outcome while it is running or stored, instead of running again. An expired, failed or cancelled original releases the key, and a retry is then a new request.

Carrying a price limit 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={"price_limit": 0.10}
TypeScriptheader, or a cast{ headers: { "X-Price-Limit": "0.10" } }
Gobody optionoption.WithJSONSet("price_limit", 0.10)
Javabody property.putAdditionalBodyProperty("price_limit", JsonValue.from(0.10))
.NETheader, or the options patchoptions.Patch.Set("$.price_limit"u8, 0.10)
Rubyrequest optionrequest_options: {extra_body: {price_limit: 0.10}}
Rust (async-openai)bring your own typeclient.chat().create_byot(json!({..., "price_limit": 0.10}))
OpenAI Responses methodsextra body, on /v1/responsesclient.responses.create(..., extra_body={"price_limit": 0.10})
Anthropic SDKsextra body, on /v1/messagesclient.messages.create(..., extra_body={"price_limit": 0.10})
curlthe body"price_limit": 0.10

In TypeScript the body route is a // @ts-expect-error line above price_limit: 0.10, which the SDK forwards at runtime. The .NET patch is marked experimental by that SDK, so the header is the settled route there. 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.order_idThe order this completion ran as. The completion's own id is the model server's; every /v1/orders route takes this one, and the same value is on the response as the x-openfill-order-id header.
usage.x_market.level_at_executionThe price the request was charged at: the level at dispatch, or your bid where that was lower, in the price limit's unit. On a fixed-price model, its prices at the reference mix, in the same unit.
usage.x_market.bidThe price the request rested at: your price limit, or the lower of the quote and your price limit when locked. On a fixed-price model, the same figure as level_at_execution.
usage.x_market.charged_usdWhat the request cost, in USD to nine decimal places. 0 at a level of 0.
usage.x_market.pricingmarket, or fixed on a fixed-price model. Absent on a result stored before the field existed, which reads as market.
usage.x_market.usd_per_millionOn a fixed-price model only: the three prices applied, per 1M tokens of cache-hit input, cache-miss input and output.
usage.prompt_tokens_details.cached_tokensThe prompt tokens served from cache, which are priced at the cache-hit weight, or the cache-hit price on a fixed-price model.
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 price limit 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. OpenFill serves it at /v1/responses without server-side state, and the chat completions method lands on /v1/chat/completions; the two are priced and charged alike.