What an OpenAI-Compatible Endpoint Does With Unsupported Parameters
10 min read · updated August 11, 2026
An endpoint that advertises OpenAI compatibility is promising a URL shape and a response shape. It is not promising that every parameter in the request body does anything. The gap between those two is why a compatible endpoint passes a smoke test and fails in production.
Three possible behaviours
Send a request containing a field the server does not implement and exactly one of three things happens.
- It rejects the request. You get a 400 naming the field. You find out immediately, in development, on the first call.
- It ignores the field. You get a 200 and a completion. The field had no effect and nothing anywhere says so.
- It accepts the field and does something different. It clamps your value into a range it supports, maps it onto a similar concept, or applies it to a different stage than you meant. You get a 200 and a completion that is subtly not the one you asked for.
The behaviour is usually a property of the server’s request validation rather than a considered decision per field. A server validating with a strict schema rejects everything unknown as a class; a server reading the fields it cares about out of a parsed body ignores everything unknown as a class. Which of those two your endpoint is, you can determine in one request — and it is worth knowing before you interpret anything else on this page.
Rejection, which is the good outcome
Strict validation is louder and it is what you want. vLLM’s OpenAI-compatible server validates request bodies against schemas that forbid unknown fields, and its issue tracker contains a steady stream of reports where a legitimate OpenAI parameter is refused with a validation error of the form Extra inputs are not permitted naming the offending path — see for instance vLLM issue 6890. People file these as bugs, which is understandable, but the behaviour is the safe one: nothing is silently discarded.
OpenAI’s own API rejects unknown top-level arguments too, with a 400 whose message reads Unrecognized request argument supplied: followed by the field name. Azure’s hosted version of the same API returns the identical string for parameters that exist on the newest API version but not on the version your deployment is pinned to, which is a slightly different problem wearing the same error message and worth keeping in mind before you conclude a field does not exist.
The corollary is a useful diagnostic. If you send deliberate nonsense — a field named something no API could have — and get a 200 back, your endpoint is in the ignoring category, and every conclusion you have drawn from a request succeeding is worthless as evidence of support.
# One request tells you which kind of server you are talking to.
curl -s -o /dev/null -w '%{http_code}\n' "$BASE_URL/chat/completions" \
-H "authorization: Bearer $KEY" -H 'content-type: application/json' \
-d '{"model":"'"$MODEL"'","messages":[{"role":"user","content":"hi"}],
"definitely_not_a_real_parameter": true}'
# 400 → strict validation. 200 → unknown fields are dropped silently.What each ignored parameter costs you
Silence is dangerous in proportion to how quietly the parameter fails. Taken one at a time, with the downstream consequence rather than the definition:
seed. Ignored, your reproducibility is gone and nothing indicates it. Tests that pinned a seed to get stable output start failing intermittently and you debug the model. The official shape signals its own uncertainty here through the backend fingerprint field, which a compatible endpoint typically does not return at all — so you lose both the guarantee and the signal that it was not honoured.response_format. Ignored, you still get JSON most of the time, because the prompt asks for it and the model complies. The failure rate is small and non-zero, which is the worst possible shape: it survives development, it survives staging, and it produces a parse error on some fraction of production traffic forever.tool_choice. Ignored, a constraint that said “you must call a tool” becomes a suggestion. Your code reads the tool call off the response and finds prose. This one at least crashes.stop. Ignored, generation runs to the token cap. You pay for tokens you throw away, latency rises, and if you were relying on the stop sequence to terminate a structured format you now have a trailing hallucinated continuation inside your parsed output.logprobsandtop_logprobs. Ignored, the field comes back null and any confidence gate you built reads null as falsy and passes everything, or crashes. Both are bad; the passing one is worse.stream_options. Ignored, no usage chunk arrives at the end of a stream, your cost tracking records zero tokens for every streamed request, and your dashboards show a beautiful cost reduction that the invoice does not agree with.n. Ignored, you get one choice where you asked for several. Code that iterateschoiceshandles this without complaint and your best-of-n sampling quietly becomes best-of-one.frequency_penaltyandpresence_penalty. Ignored, repetitive output that you had tuned away comes back. This is the hardest to attribute, because it looks exactly like a model quality difference.
The pattern across that list: the dangerous ones are the parameters whose absence changes a probability rather than a shape. A missing shape crashes and gets fixed. A changed probability becomes a slow quality complaint that nobody traces to a request field.
Coercion deserves its own mention because it is the hardest to detect. A server that supports a parameter over a narrower range than the official API — a temperature ceiling, a smaller cap on stop sequences, a lower maximum for top-log-probability entries — may clamp rather than reject. You asked for one thing, you got another, and both the request and the response look fine. The sampling parameter mapping page covers which of these ranges actually differ.
Probing for support rather than acceptance
The core idea: a 200 proves acceptance, not support. To test support you have to construct a request where the parameter’s effect is observable in the response, and assert on the effect. Each of these is a few lines and they are all deterministic enough to run in CI.
stop. Ask the model to count from one to twenty, withstopset to the string it will certainly emit partway through. Assert the completion ends before that string and that the finish reason indicates a stop sequence rather than length.max_tokens. Set it to a very small number against a prompt that would produce a long answer. Assert the finish reason is the length value and that the output is short. This one also tells you whether the server reports finish reasons faithfully at all.n. Request more than one completion and assert the length ofchoices. There is no ambiguity in this test.seed. Send the identical request twice at a non-zero temperature with the same seed, and assert the outputs match. A single mismatch disproves support; matching twice is weak evidence, so run it a handful of times.response_format. Request the JSON-object format with a prompt that explicitly asks for a poem in prose. Assert the body parses as JSON. A server honouring the constraint cannot produce prose; a server ignoring it will.tool_choice. Declare one tool, set the constraint that requires a call, and send a message that clearly needs no tool. Assert a tool call is present anyway.logprobs. Request them and assert the field is non-null and has the number of alternatives you asked for.- Usage on a stream. Stream a request with the usage option set and assert that a final chunk carries non-zero usage. Handle the empty-choices chunk while you are there.
Run the suite against each endpoint you route to, and run it again when the endpoint is upgraded — self-hosted servers gain and lose parameter support between releases far more often than hosted APIs do.
Keeping the answer once you have it
Write the results down as data, not as a wiki page. A small table mapping endpoint to supported-parameter set lets your client strip unsupported fields before sending — which is what you want against a strict server — and, more importantly, lets it refuse to run a code path that depends on a parameter the target does not honour.
That refusal is the whole point. The alternative to knowing is fallback logic that routes a structured-output request to an endpoint which ignores response_format, and a fallback that silently degrades correctness is worse than an outage, because an outage is visible. Shimming a missing capability is the constructive half of this; the prerequisite is a machine-readable statement of what is missing.