tokens.flex.ai and, for everything marked Supported below, code written against api.openai.com works by changing only base_url and model. This page is the exact compatibility matrix — the endpoints and request fields the gateway implements at launch.
Spec version we track
The chat-completions request validator at our gateway is authored againstopenai-python 1.109.1 — specifically the request shape openai.types.chat.completion_create_params.CompletionCreateParamsBase. That same version is pinned in our gateway image, so the SDK you call against and the shape we validate can’t drift apart silently.
We validate a focused subset of the SDK’s surface — the fields where validation gaps were causing real problems (off-spec inputs returning 200 with garbage, or 500 with a stack trace). Unknown or unsupported fields are forwarded to the engine and accepted or rejected there. The matrix below is the exact subset.
Bumps are deliberate: when we upgrade the pin, a snapshot test fails until we audit the SDK diff, and we update this page in the same PR.
Endpoints
Request fields
Multi-turn tool calls
When a model turn is a pure tool call, the assistant message has no text to carry. OpenAI-compatible clients disagree on how to serialize that: some omitcontent, some send content: null, and some — notably the Vercel AI SDK — send content: "". Because you resend that assistant turn verbatim on the next request so the model can see its own call, whichever shape your client produces is the shape we receive.
The gateway accepts all three on an assistant message that carries tool_calls, and normalizes an empty string to null before the request reaches the model. You don’t need a client-side wrapper to rewrite content — send the message your tool-calling library produces.
That empty-string acceptance is scoped to exactly this case. On any message that is not an assistant message carrying tool_calls, an empty content string is still rejected with a 400 (param: "messages[N].content"), since an empty prompt otherwise reaches the model as nothing but chat-template tokens.
Response fields
Tool-result message ordering
A normal agent loop ends a turn with tool results and then auser message — “here are the results, now summarize.” OpenAI accepts a user message placed immediately after a tool message, and so does this gateway, on every served model. A few models ship a chat template that enforces strict role alternation and would otherwise reject that sequence; for those the gateway normalizes the request for you before it reaches the model, so the behavior matches OpenAI uniformly. You do not need to restructure your history or insert a bridge turn yourself — send the message sequence your tool-calling library produces.
Authentication & headers
- Bearer tokens only (
Authorization: Bearer sk-…). No OAuth, no organization header. - Our rate limit response includes
Retry-Afterandx-ratelimit-*headers — see errors. - CORS is restricted; the API is intended for server-to-server calls, not browsers.
Error shape
Errors use OpenAI’s envelope —{ "error": { "message", "type", … } } — in two flavors.
Request-validation failures (400) come back verbatim in OpenAI’s shape, with param set to the offending field path (or null when it isn’t field-specific) and code set to null — we reserve non-null code strings the way OpenAI does, for a narrow documented set like context_length_exceeded:
doc_url extension so your first failure points you at the dashboard: