Skip to main content
The body of a chat completion is OpenAI’s, and it travels to the provider as you wrote it. This page says exactly which parts of it this platform looks at, which parts it changes, and what happens to everything else.

What is read

Admission reads two things out of your body, both to work out what the request could cost before it is sent: Neither read changes the field, and the two fields answer differently when the value cannot be made sense of. A ceiling this cannot read falls back to being treated as absent rather than being corrected, so credit is held at the model’s published maximum output and the provider answers for its own body. model has no such fallback: it is the one field admission cannot proceed without, so a missing model and a model id in a shape this does not accept are both refused here, before any provider is called. Both refusals are in The refusals that are ours below, and both name a param, which is how you tell them from the provider’s.

What is rewritten

Two rewrites exist on every key, and two more on the platform’s own provider keys, listed below: an omitted store on Responses is sent as false, and three routing fields a reseller provider reads are removed from a chat body. All are named here because a body that arrives different from the one you sent should never be something you discover from a provider’s error message. model, when you asked for a route rather than a model. A request naming profile/{identifier} or unirouter/auto is a request for a list of models tried in order, so the body that reaches a provider carries the id of the model being tried on that attempt. See Routing profiles. max_tokens, when the provider’s own API refuses a request without one. Some providers require the field. If your body carries neither max_tokens nor max_completion_tokens and the provider is one of those, the field is added, and the value used is the model’s published max_output_tokens, which is the figure credit was already being held against. Nothing else in the body is touched, and a body that already names a ceiling is sent unchanged. Every other provider is sent your body without the field, exactly as you wrote it.

What happens to everything else

Almost nothing. There is no request schema of ours in front of the provider’s and no allowlist of parameters. temperature, top_p, stop, seed, logprobs, tools, tool_choice, response_format, and any parameter a provider adds next month all reach the provider as sent. This cuts both ways, and the second half is the half worth planning for:
  • A parameter is never refused here for being unfamiliar. You do not wait for us to add support for something your client already sends.
  • A parameter is never validated here either. If a model will not take the parameter, or will not take the value, the answer you get is the provider’s own refusal relayed with its own status and its own sentence. We do not pre-empt it with a refusal of our own, and we do not translate it. There is one exception, and it is a single value rather than a field: an empty or whitespace-only reasoning_effort is refused here with a 400, code invalid_value and param set to reasoning_effort. It is the one value no provider accepts, so relaying it would spend a round trip to be told the same thing. Any other reasoning_effort you send, string or otherwise, is forwarded. See Reasoning models. The other is a short list of fields refused on the platform’s own provider keys and forwarded on yours, in the next section.
So a 400 on a chat completion has two possible authors. Ours name a param and come from the short list below. Everything else on that status came from the provider and reads like the provider.

The fields refused on the platform’s own provider keys

A request served on the platform’s own provider keys is refused, before anything is held and before any provider is called, when it carries a field that buys something this platform cannot bill yet or asks the provider to keep the conversation under our account:
  • a service_tier other than the provider’s standard one (auto, default or on_demand on chat completions, auto or default on Responses),
  • web_search_options, or a hosted tool the provider charges for per call (web search, web fetch, code execution, and on a Gemini model a grounding tool such as google_search),
  • audio in (an input_audio part) or out (audio, or audio in modalities),
  • a cache_control marker with a 1h ttl,
  • store: true, and on POST /v1/responses also previous_response_id, conversation, an item_reference input item and background: true.
Each is a 400 with code null and param naming the field, never its value, and the message says to remove it or send the request on your own provider key. On POST /v1/responses an omitted store is sent to the provider as false. A request served on your own provider key keeps every one of them and goes out as you sent it, because the provider bills and stores it against your own account. See Bring your own key. On chat completions, the routing fields provider, models and transforms, which a reseller provider reads, are removed from the body rather than refused, so a client that sends them keeps working and the model you named is the one you are charged for. Every other field, including the reasoning and verbosity parameters a client may send, travels to the provider untouched and is answered by the provider.

The refusals that are ours

Each of these is decided before any provider is called, and none of them charges you anything. No model named. The one field admission cannot do without.
400
A model field that is not a usable id. Too long, or holding characters an id cannot hold. The message states the shape it will accept:
A well-formed id with no provider in front of it is a different refusal, 400 with the code model_id_not_pinned. See Models. A prompt too large to hold credit against. 400 with context_length_exceeded and param set to messages. This is a refusal about sizing the hold, and it happens before the model’s own context window is ever consulted, so it is not the same thing as a provider telling you the prompt does not fit. No ceiling can be worked out. 503 with model_not_priceable, when your body names no output ceiling and the model publishes no maximum output either. The message says what to do, and it is worth reading rather than retrying:
This is the one model_not_priceable that a retry does not fix. The others on that code clear on their own. See Errors.

max_tokens and what it holds

The credit held while a request runs is sized from an output ceiling, and the ceiling is the smaller of two figures: the one you sent, and the model’s own published max_output_tokens. A body asking for more than one completion, with n or best_of, holds that many output ceilings (the larger of the two when both are sent), because the provider bills each completion its own output. The prompt is held once. A balance that cannot cover the larger hold is refused with the ordinary 402. The hold is returned in full at settlement and you are charged for what the request actually produced, so sending max_tokens never costs you the difference. What it changes is how much of your account spend rate cap one request occupies while it is in flight. See Rate limits and spend controls.

The parameters a given model takes

GET /v1/models/{id} returns a parameters array for that model: the request controls it accepts, with their bounds and defaults. It is served only on the single-model call, never on the list, because a list is scanned and a detail is read.
One entry
The three bounds are in thousandths. A temperature ceiling of two arrives as 2000, and a default of nought point seven as 700. They are integers on purpose: a bound that drifted in its last decimal place would be a contract about nothing. Divide by a thousand before you show one to a person. Two things this list is and is not. It is a description of a model, useful for building a form or validating input before you spend a request finding out. It is not a filter on the API: a parameter absent from the list is still forwarded if you send it, and the provider still answers for it.

What is not on this page

A canonical list of every parameter the API accepts. There is no such list, because there is no allowlist. The parameters are OpenAI’s, plus whatever a provider takes, and the model detail above is the closest thing to a per-model answer. Headers. Nothing about how a request is served can be set from a header. See Headers.