Guides
OpenAI compatibility
What the OpenAI Chat Completions and Responses routes honor, drop, or refuse, and how tool search, structured output, prompt caching, reusable reasoning, provider fallback, and data retention behave when a model is served across several providers.
/v1/embeddings, see the Embeddings guide: text or token-ID inputs, singly or in batches. Omit stream or send stream: false; no streaming, no vector-response cache, and no Idempotency-Key replay. Completion prompt caching does not apply to embeddings.Request fields
POST https://api-pr-143.preview.experientiallabs.ai/v1/chat/completions and POST https://api-pr-143.preview.experientiallabs.ai/v1/responses take the standard OpenAI request bodies. Every field is handled one of three ways:
- Honoredon the rung that serves the request, when that rung's provider supports it.
- Dropped with disclosure when the serving rung cannot honor it but the request is otherwise servable: the field is removed and named in the top-level response body array
x-experiential-ignored-parameters(on a stream the same key rides in the chunk that carries the finish). Examples:top_pon a rung that does not sample,text.verbosityandclient_metadataon a non-OpenAI rung. - Refused before the request is admitted, as a
400withcode: unsupported_parameter(no route serves it) orcode: invalid_parameter(malformed) andparamnaming the field. Nothing is charged.
Fields refused on every route today: audio, modalities, logit_bias, seed, functions / function_call, prediction, prompt_cache_retention, and the top-level route object. The providerobject is accepted for request-level data controls and supported downstream preferences; it is not this gateway's retry or waterfall policy. Unknown message-level keys (for example messages[].agent) are rejected exactly as OpenAI rejects them.
Web search is served on every route. Chat web_search_options, the OpenRouter plugins: [{"id": "web"}] object and the :online model suffix, the Responses web_search tool, and the Anthropic web_search_*server tool all ask for the same thing. On a route whose every rung searches natively (OpenAI's own Responses API, Anthropic's own Messages API) the provider's search runs as before. On any other route the gateway runs one web search itself (Exa) for the latest user turn, gives the model the ranked results, and cites the sources: url_citation annotations on Chat (choices[0].message.annotations, one delta.annotations chunk on a stream) and Responses (announced by response.output_text.annotation.added), server_tool_use + web_search_tool_result blocks on Messages. Only the web plugin id is served; other plugin ids are a named 400. Each gateway-run search is a flat per-search fee (default $0.007, on top of the model's token price, never on BYOK) that is included in the response's usage.cost; the count is usage.server_tool_use_details.web_search_requests (usage.server_tool_use.web_search_requests on Messages). A search never fails a request: without a search backend, or on a vendor timeout, the turn still serves and the drop is disclosed (web_search->dropped(search_unavailable)).
Tool search
An agent with hundreds of tools (an MCP-heavy client, for example) can spend 100k or more tokens per turn on tool definitions alone. Tool search keeps only the relevant tools in context: declare a tool-search tool and mark the tools the model rarely needs defer_loading: true. The gateway withholds the deferred definitions from the prompt and exposes one tool_search function in their place. When the model calls it, the gateway runs a BM25 search (or a regular-expression match) over the deferred tools' names, descriptions, and parameter names, loads the matches, and re-dials the model on the same request. You only see the final tool call: the search round never reaches your client and counts as one attempt. A request gets up to 3 search rounds by default. Every model dispatch spends the request's gateway.retry attempt budget; a strict cap can stop the turn before the final answer.
curl "https://api-pr-143.preview.experientiallabs.ai/v1/chat/completions" \-H "Authorization: Bearer $EXPLABS_API_KEY" \-H "Content-Type: application/json" \-d '{"model": "gpt-5.6-luna","messages": [{"role": "user", "content": "What is the weather in Paris right now?"}],"tools": [{"type": "openrouter:tool_search", "max_results": 5},{"type": "function", "defer_loading": true, "function": {"name": "get_weather", "description": "Current weather for a city","parameters": {"type": "object", "properties": {"city": {"type": "string"}}, "required": ["city"]}}},{"type": "function", "defer_loading": true, "function": {"name": "book_flight", "description": "Book a flight between two airports","parameters": {"type": "object", "properties": {"from": {"type": "string"}, "to": {"type": "string"}}}}}]}'# The response carries the final tool call (get_weather); the search round is not returned.
- Chat Completions takes the OpenRouter spelling:
tools: [{"type":"openrouter:tool_search"}, {"type":"function","defer_loading":true,"function":{...}}, ...]. An optionalmax_resultson theopenrouter:tool_searchentry sets how many tools one search loads.tool_choice: "auto"and"required"both work; namingtool_searchin atool_choiceobject is not supported yet (engine issue experientiallabs/experiential#1047). - Responsestakes OpenAI's hosted tool spelling:
tools: [{"type":"tool_search"}, {"type":"function","name":"...","defer_loading":true, ...}]. - Messages (
/v1/messages) takes Anthropic's server tool:tools: [{"type":"tool_search_tool_bm25_20251119","name":"tool_search"}, {"name":"...","defer_loading":true,"input_schema":{...}}]. The regular-expression variant istool_search_tool_regex_20251119.
On a route whose every rung serves tool search natively (OpenAI's own Responses API, Anthropic's own Messages API) the declaration and the deferred tools pass through and the provider runs the search. On any other route the gateway runs the round itself, so the same request works on every model. If no tool is marked deferred there is nothing to search: the declaration is dropped and disclosed in x-experiential-ignored-parameters as tool_search->dropped(no_deferred_tools).
Structured output
Chat Completions takes response_format with {"type":"json_object"} or {"type":"json_schema","json_schema":{"name","schema","strict"}}; the Responses API takes the same under text.format. Structured output is a per-rung capability the gateway learns by probing each provider, never by assertion. A rung that cannot honor it is skipped; if no rung on the model's route can, the request is refused with 400, code: unsupported_capability and param: response_format. The gateway never downgrades a schema request to prose silently.
curl "https://api-pr-143.preview.experientiallabs.ai/v1/chat/completions" \-H "Authorization: Bearer $EXPLABS_API_KEY" \-H "Content-Type: application/json" \-d '{"model": "gpt-5.6-luna","max_tokens": 2048,"messages": [{"role": "user", "content": "Ada Lovelace was born in 1815 and died in 1852."}],"response_format": {"type": "json_schema", "json_schema": {"name": "person", "strict": true,"schema": {"type": "object", "properties": {"name": {"type": "string"}, "age": {"type": "integer"}}, "required": ["name", "age"], "additionalProperties": false}}}}'
- On providers other than OpenAI, set
strict: true: those providers enforce the schema themselves. - A reasoning model spends part of
max_tokensthinking. Too small a budget returns an emptycontentwithfinish_reason: "length"(a Responses call is marked incomplete). Raisemax_tokens. - Verified with a
person {name, age}schema ongpt-5.6-lunaandglm-5.2. Some models honorjson_objectbut notjson_schema(for exampledeepseek-v4.1-flash); the model page's supported parameters say which.
Prompt caching
Prefix reuse happens at the provider. The gateway forwards your request and uses cache-aware routing where supported, but fallback can move it to a cold rung. prompt_cache_key is a routing hint, not cache activation, consent to resource charges, or guaranteed provider affinity. Reported cache reads appear in:
- Chat Completions:
usage.prompt_tokens_details.cached_tokens - Responses:
usage.input_tokens_details.cached_tokens - Messages (
/v1/messages):usage.cache_read_input_tokens
Cached input is billed at the model's cached rate, the Cached $/M price on the model page. Whether a prefix caches depends on the provider that served the request: OpenAI, Anthropic, DeepSeek, Experiential Cloud, and Tencent report cache hits, while some providers (Azure-hosted third-party models, for example) report none. The top-level provider field on every finished completion names the rung that served, so a cached_tokens: 0 can be attributed to the provider that answered.
# Repeat a stable prefix and inspect reported cache reads; a hit is not guaranteed.curl -s "https://api-pr-143.preview.experientiallabs.ai/v1/chat/completions" \-H "Authorization: Bearer $EXPLABS_API_KEY" \-H "Content-Type: application/json" \-d '{"model": "gpt-5.6-luna", "messages": [{"role": "system", "content": "<your long, unchanging system prompt>"}, {"role": "user", "content": "Hello"}]}' \| jq '{provider, cached: .usage.prompt_tokens_details.cached_tokens, cost: .usage.cost}'
Reusable reasoning
On the Responses API, include: ["reasoning.encrypted_content"] returns the model's reasoning as encrypted items you replay in later turns, so a multi-turn agent keeps its hidden reasoning without the gateway storing anything.
curl "https://api-pr-143.preview.experientiallabs.ai/v1/responses" \-H "Authorization: Bearer $EXPLABS_API_KEY" \-H "Content-Type: application/json" \-d '{"model": "gpt-5.6-luna","store": false,"include": ["reasoning.encrypted_content"],"reasoning": {"effort": "medium"},"input": [{"role": "user", "content": "Plan the first step, then stop."}]}'# Replay the returned output items (including type: reasoning) as input on the next turn.
previous_response_idcontinuation works on any gateway worker for 30 days from the last turn, and only while your organization keeps prompt capture on (see Data controls). An unknown or expired id gets an explicit400 previous_response_not_foundasking you to resend the full conversation, never silent state.- An encrypted reasoning item is sealed by the provider account that produced it. Replay one sealed by another provider or organization and the gateway can strip it and re-dial within the request's attempt budget. A one-attempt policy does not allow that extra call. An executed repair is disclosed in the response header
x-gateway-replay-repair: encrypted_reasoning_stripped. Your messages, tool calls, and tool results are kept; only that hidden reasoning continuity is lost.
Provider fallback
Every model resolves through a platform-managed waterfall of provider rungs. Eligible provider failures can retry the same rung or move to the next one, within the request's attempt limits and fallback setting. A header or first-token timeout advances rather than retrying the same stalled rung. Once a rung commits, the response comes from that rung and the top-level provider field on the finished completion names it.
429 to you instead of moving to a cold rung, so the cache you paid to warm is not thrown away. A cold conversation fails over.Use gateway.retry to set per-route and total attempt caps and none or configurable exponential backoff.gateway.routing.allow_fallbacksdisables or permits the gateway's fallback, and gateway.routing.route_id selects an eligible route by its public handle. These controls apply to one request and are not forwarded to a provider. OpenRouter-styleprovider.order and provider.allow_fallbacksgovern downstream aggregator selection where forwarded, not this waterfall. Persisted BYOK ordering still usesPUT /api/models/{slug}/waterfall. Examples, limits, route discovery, and SDK retry settings are in The waterfall.
Data retention per model
GET https://api-pr-143.preview.experientiallabs.ai/api/models/{slug} returns a retention verdict for the model, folded over the platform rungs its active route actually serves (a rung the route skips never counts). GET https://api-pr-143.preview.experientiallabs.ai/api/models?retention=zdr_all_rungs or ?retention=zdr_enforceable lists only the models that meet that bar.
| retention | Meaning |
|---|---|
| zdr_all_rungs | Every platform rung the active route serves resolves to zero data retention. Whichever rung answers, the provider keeps nothing. |
| zdr_enforceable | At least one platform rung is zero-data-retention, but not all. Failover can move a request onto a rung with different terms unless your organization's require-ZDR provider policy is on, which keeps the model on a compliant route. |
| not_zdr | No platform rung on the route is zero-data-retention. |
The verdict is nullfor a model with no platform rungs (your organization's own models). Because fallback can change which provider answers, a zdr_enforceable model is only guaranteed zero-data-retention when the require-ZDR policy is set; the policy, the response headers that name the serving provider, and what the gateway itself stores are in Data controls.
Errors you will see
The gate errors an OpenAI client meets on the platform, all in the standard OpenAI error envelope with a stable code:
| Status and code | What it means |
|---|---|
| 429 insufficient_quota | Your organization is out of platform credits; the message carries the balance. Add credits or serve the model on your own provider key. |
| 429 card_required | Your organization has not yet unlocked platform-credit spend: the owner must complete the $1 card verification (credited back to the balance). The message links the verify page. Your own provider keys (BYOK) work regardless. |
| 429 free_limit_reached | The model's free-tier allowance is spent for this window. The message names the limit and its reset when one exists. |
| 429 model_requires_payment / model_requires_purchase | Every lane of this model your organization can reach needs a verified card (a completed top-up, the $1 verification counts) or a real credit purchase. The message names the remedy. |
| 400 unsupported_capability | No rung on the model's route can honor a capability the request asked for; param names the field (response_format for structured output). Pick a model whose supported parameters include it. |
| 400 unsupported_parameter / invalid_parameter | One field is refused on every route (unsupported_parameter) or malformed (invalid_parameter); param names it. Remove or fix the field and resend. |
The full code table, the envelope, and what is safe to retry are in Errors.
See also
The Anthropic API page covers the same models over /v1/messages. The full route surface is in the API reference.