Gateway headers and extensions
Besides the standard OpenAI and Anthropic fields, LYRouter defines a few request headers, response headers and special model names: request headers control parameter validation, declare a session and require native passthrough; response headers report dropped parameters and remaining quota; special model names call a virtual model, Auto routing or the Default Model. All of these are optional, and calls without them work as in the standard protocols. HTTP header names are case-insensitive.
Quick reference
| Name | Direction | Purpose | Endpoints |
|---|---|---|---|
X-Gateway-Strict-Parameters: true |
Request | Returns 400 instead of dropping parameters | Images, videos |
x-session-id |
Request | Declares a session so logs are grouped by it; forwarded to the upstream as is | All |
x-gateway-passthrough: require |
Request | Requires Anthropic native passthrough and returns 400 when it is not possible | /v1/messages |
X-Title |
Request | Identifies your application in the Client column of the logs | All |
X-Gateway-Unsupported-Parameters |
Response | Lists the parameters the gateway dropped | Images, videos |
x-aigw-quota-<metric>-limit / -remaining / -reset |
Response | Limit, remaining amount and reset time of the applicable quotas | Endpoints that call a model |
anthropic-ratelimit-* |
Response | Rate limit information in the Anthropic format | /v1/messages |
Retry-After, X-RateLimit-Reason, X-RateLimit-Reset |
Response | The API key is circuit-broken or banned | Endpoints that call a model |
@vmodel/<name>, @preset/<name> |
Model name | Calls a virtual model | Chat endpoints and others |
lyrouter/auto |
Model name | Auto routing | Chat endpoints |
model omitted |
Model name | Uses the workspace's Default Model | All model endpoints |
Request headers
X-Gateway-Strict-Parameters
Image and video endpoints drop parameters the selected model does not support, carry on, and list the dropped parameters in the X-Gateway-Unsupported-Parameters response header. With this request header, the gateway returns 400 as soon as any parameter would be dropped, without calling the upstream and without billing.
- Value: only lowercase
trueis recognized; any other value (includingTrueand1) counts as absent. - Endpoints:
/v1/images/generations,/v1/images/edits,/v1/videos,/v1/videos/regenerations,/v1/videos/context. It does not control the parameters chat endpoints remove during cross-protocol conversion; see Chat Completions.
curl https://api.lyrouter.com/v1/images/generations \
-H "Authorization: Bearer $LYROUTER_API_KEY" \
-H "Content-Type: application/json" \
-H "X-Gateway-Strict-Parameters: true" \
-d '{"model":"MODEL_ID","prompt":"A Shiba Inu wearing sunglasses","guidance_scale":7}'
When the selected model does not support guidance_scale, the response is:
{
"error": {
"message": "unsupported parameters for this model: guidance_scale",
"type": "invalid_request_error",
"code": "unsupported_parameters"
}
}
The parameters each model supports are listed in Multimodal capabilities.
x-session-id
Assign a fixed session ID to a conversation (for example, generate a UUID for each conversation) and send the same value with every request in that session. For log grouping, x-session is equivalent.
- Log grouping: the Session column of the logs groups requests by this value, with the source shown as "Declared (client x-session-id)", and you can also filter by Session ID. Without this header, the gateway infers sessions from the conversation content, the source shows "Inferred (gateway-derived)", and the tag ends with
≈. - Forwarded as is: the gateway forwards this header to the upstream without changing its value.
- Upstream session affinity: the platform has turned on session affinity for a few channels. On those channels, when you do not send this header, the gateway adds
X-Session-Id: aigw-<hash>automatically; when you send it, your value is used. Whether it affects upstream caching or instance assignment is up to the provider. - Does not affect gateway routing: the gateway's own session affinity works per "API key + model", as described in Channels and routing, and has nothing to do with this header.
curl https://api.lyrouter.com/v1/chat/completions \
-H "Authorization: Bearer $LYROUTER_API_KEY" \
-H "Content-Type: application/json" \
-H "x-session-id: 6f1c7e2a-3b4d-4e5f-9a8b-1c2d3e4f5a6b" \
-d '{"model":"MODEL_ID","messages":[{"role":"user","content":"Let us pick up where we left off"}]}'
import os
import uuid
from openai import OpenAI
client = OpenAI(
api_key=os.environ["LYROUTER_API_KEY"],
base_url="https://api.lyrouter.com/v1",
)
session_id = str(uuid.uuid4()) # Generate once per conversation, then reuse it every turn
completion = client.chat.completions.create(
model="MODEL_ID",
messages=[{"role": "user", "content": "Let us pick up where we left off"}],
extra_headers={"x-session-id": session_id},
)
import { randomUUID } from "node:crypto";
import OpenAI from "openai";
const client = new OpenAI({
apiKey: process.env.LYROUTER_API_KEY,
baseURL: "https://api.lyrouter.com/v1",
});
const sessionId = randomUUID(); // Generate once per conversation, then reuse it every turn
const completion = await client.chat.completions.create(
{
model: "MODEL_ID",
messages: [{ role: "user", content: "Let us pick up where we left off" }],
},
{ headers: { "x-session-id": sessionId } },
);
x-gateway-passthrough
When you call an Anthropic-protocol channel through /v1/messages, the gateway by default converts the request into its internal format and back. Sending x-gateway-passthrough: require means "the request body must reach the upstream as is, with no conversion": when that cannot be met, the gateway returns 400 and states which condition failed, instead of silently falling back to conversion.
| Value | Behavior |
|---|---|
require (case-insensitive) |
Native passthrough is mandatory; returns 400 when it is not possible |
prefer, any other value, or absent |
Default behavior: passthrough when possible, conversion otherwise, and the request completes as usual |
- This header is read only by the gateway and is not forwarded to the upstream.
- The LYRouter public gateway currently has native passthrough turned off, so every request with
requirereturns 400; do not send this header unless you need it.
When a request is rejected, the error message is native passthrough was required by `x-gateway-passthrough: require` but is not available for this request (<reason>). On /v1/messages it comes back in the Anthropic error format (invalid_request_error); on other endpoints (such as /v1/responses) it is a single line of plain text, yet its response header is currently still Content-Type: application/json, so check whether the body is JSON before parsing it. For what each reason code in parentheses means and how to handle it, see Protocol compatibility and limits.
X-Title
Send X-Title: <app name> in the request headers. When the gateway has not recognized a known tool (such as Claude Code or Cursor) from other signals, the Client column of the logs shows this app name, and usage analytics uses it for the client breakdown. This header is forwarded to the upstream as is.
Response headers
X-Gateway-Unsupported-Parameters
When the gateway drops parameters from an image or video request, the response carries this header, with comma-separated parameter names as the value, for example X-Gateway-Unsupported-Parameters: response_format or guidance_scale,watermark. The request itself runs as usual. In production, check this header, or use X-Gateway-Strict-Parameters: true to turn dropping into an error.
x-aigw-quota-*
When a quota applies to the call, the response carries quota headers. They appear on successful responses and on the 429 returned when a quota is reached, and also on many 400s, for example a wrong Content-Type, a model that does not support the endpoint called, a 400 returned by the upstream, or a rejected native passthrough; 401, 403, 503 and the 400 for a missing model (missing_model) do not carry them. There are three headers per applicable metric:
| Response header | Meaning |
|---|---|
x-aigw-quota-<metric>-limit |
The limit. Whole Credits for credits; token or request counts for the others |
x-aigw-quota-<metric>-remaining |
The remaining amount, based on usage when the request was sent and excluding this request's consumption; never below 0 |
x-aigw-quota-<metric>-reset |
The next reset time, as a UTC instant in RFC 3339 format. This value is currently later than the actual reset: about 2 minutes later for daily and monthly quotas (for example, a monthly quota that resets at 16:00:00Z returns 16:02:00Z to 16:02:06Z) and about 1 minute later for per-minute quotas, and it also varies by a few seconds between requests. Use it only to estimate the reset time, not for precise timing. Total quotas without a reset period do not include this header |
The metrics are credits, tokens, requests, input-tokens and output-tokens. When several quotas apply to the same metric at once (for example an API key quota and a workspace quota), the one with the least remaining is reported.
x-aigw-quota-credits-limit: 2000
x-aigw-quota-credits-remaining: 1532
x-aigw-quota-credits-reset: 2026-10-31T16:02:00Z
x-aigw-quota-requests-limit: 600
x-aigw-quota-requests-remaining: 597
x-aigw-quota-requests-reset: 2026-10-14T06:31:00Z
Reading response headers with an SDK:
curl -s -D - -o /dev/null https://api.lyrouter.com/v1/chat/completions \
-H "Authorization: Bearer $LYROUTER_API_KEY" \
-H "Content-Type: application/json" \
-d '{"model":"MODEL_ID","messages":[{"role":"user","content":"Hello"}]}' \
| grep -i '^x-aigw-quota-'
import os
from openai import OpenAI
client = OpenAI(
api_key=os.environ["LYROUTER_API_KEY"],
base_url="https://api.lyrouter.com/v1",
)
raw = client.chat.completions.with_raw_response.create(
model="MODEL_ID",
messages=[{"role": "user", "content": "Hello"}],
)
print(raw.headers.get("x-aigw-quota-credits-remaining"))
completion = raw.parse()
import OpenAI from "openai";
const client = new OpenAI({
apiKey: process.env.LYROUTER_API_KEY,
baseURL: "https://api.lyrouter.com/v1",
});
const { data: completion, response } = await client.chat.completions
.create({ model: "MODEL_ID", messages: [{ role: "user", content: "Hello" }] })
.withResponse();
console.log(response.headers.get("x-aigw-quota-credits-remaining"));
Quota headers appear only on endpoints that call a model (chat, Responses, Messages, embeddings, rerank, images, audio and video submission); /v1/models, video task queries and similar endpoints do not carry them. For the 429 returned when a quota is used up, see 429: rate limits and quotas.
Retry-After, X-RateLimit-Reason, X-RateLimit-Reset
These headers appear only when the gateway circuit-breaks or bans an API key because of a high error rate:
| Scenario | Status | Response headers |
|---|---|---|
Temporary circuit break (api_key_circuit_open) |
429 | Retry-After: <seconds>, X-RateLimit-Reason: api_key_circuit_open, X-RateLimit-Reset: <Unix seconds when it lifts> |
Permanent ban (api_key_permanently_banned) |
403 | X-RateLimit-Reason: api_key_permanently_banned |
- A 429 for a used-up quota carries no
Retry-After; wait according toreset_atin the error body orx-aigw-quota-*-reset. - The upstream provider's own
Retry-Afteris forwarded to you only on/v1/messages. - How to handle these is covered in 429: rate limits and quotas and 401 / 403: authentication and access.
anthropic-ratelimit-*
When you call through /v1/messages, the response carries rate limit headers in the Anthropic format, which clients such as Claude Code use to pace their requests:
- When the upstream serving the request returns
anthropic-ratelimit-*, the gateway forwards them as is (together with the upstream'sRetry-After); these numbers are the upstream account's limits. - When the upstream returns none and you have request or token quotas, the gateway generates
-limit,-remainingand-resetforanthropic-ratelimit-requests-*,-tokens-*,-input-tokens-*and-output-tokens-*from your quotas. Credits quotas have no Anthropic equivalent; readx-aigw-quota-credits-*instead.
It is normal for anthropic-ratelimit-* and x-aigw-quota-* to show different numbers in the same response: the former may come from the upstream, while the latter is always your quota on LYRouter.
Request ID
The gateway does not return a request ID in the response headers. To investigate a particular call, copy the Request ID at the top of the log detail in the console; see Errors and request identifiers.
The response body from some upstreams (such as channels serving GLM models) contains "request_id":"aigw-…": this is the session ID the gateway adds automatically (X-Session-Id; see "x-session-id" above), echoed back by the upstream. It is not LYRouter's Request ID. When the request carries x-session-id, the echoed value is the one you sent.
Special model names
| Value | Meaning | Available on |
|---|---|---|
@vmodel/<name> |
Calls a virtual model in the workspace | /v1/chat/completions, /v1/messages, /v1/responses; when the virtual model's model list consists entirely of embedding or rerank models, also /v1/embeddings and /v1/rerank |
@preset/<name> |
The old prefix for virtual models, equivalent to @vmodel/ |
Same as above |
lyrouter/auto |
Auto routing, which picks among the candidate models by the intent of the request | /v1/chat/completions, /v1/messages, /v1/responses |
model omitted, or "" or null |
Uses the workspace's Default Model | Every endpoint that takes model |
- Virtual models and
lyrouter/autoused on image, video or audio endpoints return 403Model is not available in the model plaza; these endpoints accept only specific model IDs. - Enabled virtual models and
lyrouter/autoappear in the results ofGET /v1/models; see List models. - When
modelis omitted and the workspace has no Default Model, the request returns 400you must provide a model parameter(missing_model). The Default Model is usually a chat model, so omittingmodelon embeddings, rerank or audio endpoints most likely returns 400unsupported_model_api; always name the model ID on these endpoints. How to write model IDs is covered in Model identifiers and discovery.