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 call | Endpoint | Status |
|---|---|---|
| chat.completions.create | POST /v1/chat/completions | Served. Text only. |
| completions.create | POST /v1/completions | Served. Tools are refused here. |
| models.list | GET /v1/models | Served, with pricing weights and length limits per model. |
| files.create | POST /v1/files | Served for purpose batch. |
| files.retrieve, files.content | GET /v1/files/{id} | Served. |
| batches.* | POST and GET /v1/batches | Served: create, list, retrieve, cancel. |
| embeddings.create | /v1/embeddings | Refused with 404. |
| responses.create | /v1/responses | Refused with 404. |
| images, audio, moderations | Refused with 404. | |
| fine_tuning, assistants | Refused with 404. | |
| Anthropic Messages | /v1/messages | Refused 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.
| Field | On | What happens |
|---|---|---|
| model | both | Checked against GET /v1/models. Absent, the default model is used. A retired model answers 400 model_deprecated. |
| messages | chat | Required, at least one. Each message is forwarded whole, so tool results and names pass through. |
| prompt | completions | A string, or an array of one string. A longer array is refused with 400. |
| stream | both | Honoured. See streaming. |
| max_tokens, max_completion_tokens | both | Checked, then clamped to the model's max output length. A missing cap is that length. |
| temperature, top_p, stop | both | Range checked, then forwarded. stop takes up to four strings. |
| n, best_of | both | Must be 1 or absent. Anything else is refused with 400 before the order exists. |
| tools, tool_choice | chat | Forwarded. On /v1/completions they are refused with 400, since that shape has nowhere to put a call. |
| response_format, seed, user | both | Forwarded to the model untouched. |
| logprobs, top_logprobs | chat | Forwarded on chat. Removed on /v1/completions, whose response has nowhere to put them. |
| stream_options.include_usage | both | Honoured. The final chunk of a stream carries usage only when this is true. |
| echo, suffix | completions | Removed 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.
| Field | Type | Default | Meaning |
|---|---|---|---|
| max_price | number | key or account | The 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_lock | boolean | account setting | true 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_wait | integer | account setting | Seconds 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. |
| detached | boolean | false | Answer 202 with an order id at once and run the request in the background. Cannot be combined with stream. |
| store | boolean | account setting | false keeps nothing durable for this request: no result, no request log. It can only shorten what your account keeps. |
| retention_days | integer | account setting | Keep this request's data for fewer days than your account setting. Clamped down to the setting; it never extends it. |
Headers
| Header | Meaning |
|---|---|
| Authorization | Bearer and your key. Required on every endpoint that is yours. |
| X-Max-Price | The same parameter as max_price, for a client that cannot add a body field. The body wins if both are present. |
| X-Expires-After | The same parameter as max_wait. The body wins if both are present. |
| Idempotency-Key | Up 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.
| Client | Route | Example |
|---|---|---|
| Python | extra_body | extra_body={"max_price": 0.10} |
| TypeScript | header, or a cast | { headers: { "X-Max-Price": "0.10" } } |
| Go | body option | option.WithJSONSet("max_price", 0.10) |
| Java | body property | .putAdditionalBodyProperty("max_price", JsonValue.from(0.10)) |
| Ruby | request option | request_options: {extra_body: {max_price: 0.10}} |
| curl | the 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.
| Field | Meaning |
|---|---|
| usage.x_market.level_at_execution | The clearing level the request was charged against, in the max price's unit. |
| usage.x_market.bid | The price the request rested at: your max price, or the quote when locked. |
| usage.x_market.charged_usd | What the request cost, in USD to nine decimal places. 0 at a level of 0. |
| usage.prompt_tokens_details.cached_tokens | The prompt tokens served from cache, which are priced at the cache-hit weight. |
| choices[0].message.reasoning_content | A 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_calls | Assembled from the model's stream, so a call split across chunks arrives whole. |
| choices[0].logprobs | Present 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.
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.