Skip to main content

Upstream Request Headers

On the standard protocol endpoints, such as /v1/chat/completions, /v1/messages, and /v1/responses, AISIX builds the upstream request from scratch. It selects the upstream credential, rewrites the model name, and sends only the headers the provider's protocol requires. Headers the caller sent are not forwarded, so a caller cannot reach the upstream provider with their own credentials or trace context.

Two provider-key settings extend that default when an upstream needs more context:

  • request.default_headers adds headers AISIX generates, with values that can reference the current request, such as the calling API key's team.
  • request.forward_client_headers relays specific inbound client headers, such as anthropic-beta or a trace header, to the upstream.

Both settings live on the provider key, so every model that references it inherits them.

The main examples in this guide use AISIX Cloud. For the open-source AISIX gateway, use the same fields under provider_keys in resources.yaml.

Prerequisites

Before starting, prepare the following:

  • An upstream provider credential and endpoint. The examples create a provider key around them; see Provider Keys for the rest of that resource's fields.
  • For AISIX Cloud, access to an environment, an attached gateway, and a write-scoped admin token. For On-Premises, follow the AISIX Cloud Quickstart. To request Hybrid Cloud access, contact API7.
  • For the open-source AISIX gateway, a gateway that loads a declarative resources file.

Export the AISIX Cloud connection details and the values used by the examples:

# AISIX_CP is the Admin API base URL; include /api and omit a trailing slash.
# The local On-Premises quickstart uses http://localhost:8080/api.
export AISIX_CP="YOUR_AISIX_CLOUD_ADMIN_API_URL"
export AISIX_TOKEN="YOUR_ADMIN_TOKEN"
export ENV_ID="YOUR_ENVIRONMENT_ID"
export UPSTREAM_API_KEY="YOUR_UPSTREAM_API_KEY"
# AISIX_PROXY is the gateway origin; omit a trailing slash and endpoint path.
# The local quickstarts use http://127.0.0.1:3000.
export AISIX_PROXY="YOUR_AISIX_GATEWAY_ORIGIN"
# Set after the first example creates the key, for the update example.
export PK_ID="YOUR_PROVIDER_KEY_ID"
# A caller API key allowed on a model that references the provider key,
# and that model's alias — used by the verification request at the end.
export AISIX_API_KEY="YOUR_CALLER_API_KEY"
export MODEL_ALIAS="YOUR_MODEL_ALIAS"

Inject Request-Context Headers

Use request.default_headers when the upstream needs to know who is calling. An internal model service can then apply per-tenant quotas, attribute cost to a team, or join its own access log to the AISIX request id. None of that requires a separate upstream credential per tenant.

A header value can be a literal string, a ${...} reference, or a combination of literal text and multiple references. AISIX resolves each reference per request after authenticating the caller and selecting the model:

curl -sS -X POST "$AISIX_CP/provider_keys" \
-H "Authorization: Bearer $AISIX_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"display_name": "internal-vllm",
"provider": "byo",
"adapter": "openai",
"api_key": "'"${UPSTREAM_API_KEY}"'",
"api_base": "https://models.internal.example.com/v1",
"allowed_environments": ["'"${ENV_ID}"'"],
"request": {
"default_headers": {
"x-tenant-id": "${request.api_key.team_id}",
"x-audit-context": "key=${request.api_key.id};model=${model.name}",
"x-correlation-id": "${request.id}",
"x-upstream-tier": "premium"
}
}
}'

❶ The team that owns the calling API key. Useful as the tenant identifier for upstream quota or routing decisions.

❷ A value that combines literal text with the calling API key's identifier and the caller-facing model name.

❸ This request's correlation id, the same value AISIX sends as x-aisix-request-id and reports in its own logs.

❹ A literal value, sent unchanged on every request.

Available Variables

VariableValue
request.idCorrelation id for this request.
request.api_key.idIdentifier of the calling API key.
request.api_key.nameName of the calling API key.
request.api_key.team_idTeam that owns the calling API key.
request.api_key.user_idOrganization member who owns the calling API key.
model.idIdentifier of the resolved model.
model.nameCaller-facing name of the resolved model, not the upstream model name.
provider_key.idIdentifier of this provider key.
provider_key.nameName of this provider key.

Only these variables resolve at request time. The AISIX Cloud Admin API rejects a value that references any other name when you save the provider key. This makes a typo fail immediately instead of becoming a header that never arrives. A resources file has a separate load-time interpolation step, so escape request-context references as described in Configure in a Resources File.

No variable exposes a secret. Caller API keys, upstream credentials, and signing material are not part of the request context a header value can read.

note

A header whose variables do not all have a value for a request is dropped from that request rather than sent empty. If the calling API key belongs to no team, a request through the example above carries x-audit-context and x-correlation-id, but no x-tenant-id. An empty x-tenant-id would tell the upstream that the tenant is the empty string.

Protected Header Names

request.default_headers cannot set authorization, x-api-key, x-goog-api-key, api-key, x-amz-security-token, x-amz-date, x-amz-content-sha256, proxy-authorization, cookie, or host. AISIX Cloud rejects these names when you save the provider key, and the gateway drops them if they reach runtime configuration.

A default header also cannot replace a header that the selected provider bridge already set, such as the upstream credential, content-type, or x-aisix-request-id. The broader transport and namespace restrictions under Caller Headers AISIX Never Forwards apply to headers received from the caller; they are not a blanket restriction on default_headers.

Forward Client Headers

Use request.forward_client_headers when callers need to pass a header through AISIX to the provider. Common cases are an Anthropic beta feature flag, a distributed-tracing header, and a routing hint an internal model platform reads.

List the headers to relay. Each entry is either an exact header name or a name with a single * wildcard, matched case-insensitively. Both blocks can sit on the same provider key — they are shown separately only to keep each example focused:

curl -sS -X POST "$AISIX_CP/provider_keys" \
-H "Authorization: Bearer $AISIX_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"display_name": "internal-vllm-forwarding",
"provider": "byo",
"adapter": "openai",
"api_key": "'"${UPSTREAM_API_KEY}"'",
"api_base": "https://models.internal.example.com/v1",
"allowed_environments": ["'"${ENV_ID}"'"],
"request": {
"forward_client_headers": [
"anthropic-beta",
"traceparent",
"x-trace-*"
]
}
}'

❶ An exact name. A caller sending anthropic-beta reaches the provider with it; a caller who does not send it changes nothing.

❷ The W3C trace context header, so a trace continues into the upstream instead of ending at AISIX.

❸ A wildcard entry, matching x-trace-id, x-trace-parent, and any other name with that prefix. Use x-* to relay every x- header except the ones in Caller Headers AISIX Never Forwards, which no pattern can reach.

An empty or absent list — the default — forwards nothing.

Callers cannot opt themselves in: the list is provider-key configuration, and a header a caller sends that no entry names is dropped exactly as before.

Change the Configuration Later

In AISIX Cloud, PATCH /provider_keys/{id} accepts the same request block, and the change reaches every model that references the key.

The block is replaced whole, so send the fields to keep, not only the ones that changed. Read the current block first — GET /provider_keys/{id} returns it — then submit the edited version:

curl -sS -X PATCH "$AISIX_CP/provider_keys/$PK_ID" \
-H "Authorization: Bearer $AISIX_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"request": {
"default_headers": {"x-tenant-id": "${request.api_key.team_id}"},
"forward_client_headers": ["anthropic-beta", "x-trace-*"]
}
}'

An empty "request": {} clears the block, returning the key to the bundled defaults for its provider. Omitting the field entirely leaves the stored block alone.

In the dashboard, the same block is under Advanced wire-shape overrides on a provider key's edit form, pre-filled with what is stored.

Caller Headers AISIX Never Forwards

Some headers are never taken from the caller, regardless of configuration. The gateway filters them at runtime even if a broad forward_client_headers pattern would otherwise match them.

This is about the caller's copy. AISIX still sends its own value for several of these names — it authenticates with the provider key's credential, sets content-type for the body it builds, and stamps x-aisix-request-id. That request ID may be one the caller supplied. AISIX still emits it as its own header rather than relaying the caller's copy, so the value on the wire is single.

Header groupHeadersWhy
Authenticationauthorization, x-api-key, api-key, x-goog-api-key, x-amz-date, x-amz-security-token, x-amz-content-sha256, proxy-authorizationAISIX authenticates to the provider with the provider key's own credential. Forwarding the caller's credential would disclose it to a third party.
Session and routingcookie, set-cookie, hostPrevents session material from crossing into upstream traffic and blocks host-based redirection of the upstream request.
Transport and proxyconnection, keep-alive, transfer-encoding, content-length, content-type, content-encoding, accept, accept-encoding, te, trailer, upgrade, expect, proxy-authenticateThese describe the caller's connection, a request body AISIX rewrites, or an intermediary rather than the upstream request AISIX sends.
Gateway-ownedx-aisix-*AISIX asserts these itself, and a caller's copy is never relayed. A header named in proxy.request_id.accept_headersx-aisix-request-id by default — is an exception only in that AISIX reads the request ID from it. The header itself is still not forwarded: the gateway emits the value it read as its own x-aisix-request-id, so the upstream receives a single value.
Client SDKx-stainless-*Version headers the caller's SDK sends about itself. Relaying them to a provider that reads the same headers for its own SDK breaks the call.

If a request genuinely needs its original headers, use a passthrough route instead. A route forwards the request as received, minus the stripped headers for its credential mode, and gives up the protocol translation, unified telemetry, and cross-provider features of the standard endpoints.

Which credentials cross is the route's explicit choice, never a strip-list side effect. An inject route applies the provider key's strip_headers list and always strips authorization and x-api-key before injecting the provider credential, so the upstream never receives two credentials. A forward_client route relays the caller's own credential headers verbatim and strips only the gateway's side-channel headers. Use forward_client only for an upstream the caller's credential is meant for.

Precedence

When the same header name comes from more than one place, the first of these wins:

  1. Headers AISIX owns, such as the upstream credential, content-type, and x-aisix-request-id.
  2. request.default_headers.
  3. A header relayed by request.forward_client_headers.

A header is single-valued on the wire: AISIX replaces rather than appends, so an upstream never receives both an operator value and a caller value for the same name.

Configure in a Resources File

The open-source AISIX gateway supports the same fields in its resources file:

provider_keys:
- display_name: internal-vllm
provider: byo
adapter: openai
api_key: ${UPSTREAM_API_KEY}
api_base: https://models.internal.example.com/v1
request:
default_headers:
x-tenant-id: $${request.api_key.team_id}
x-audit-context: key=$${request.api_key.id};model=$${model.name}
x-correlation-id: $${request.id}
forward_client_headers:
- anthropic-beta
- x-trace-*

See the Resources File Reference for the full field catalog.

caution

In a resources file, every ${NAME} without an escape is substituted from the environment when the file loads. To preserve a request-context reference for per-request rendering, escape its dollar sign as $${...}. The loader converts $$ to a literal $, so $${request.id} reaches the gateway runtime as ${request.id}. An escaped name that is not in Available Variables does not resolve at runtime, and the gateway drops that header.

Verify

Send a request through a model that references the provider key, including a header the allowlist names:

curl -sS -X POST "$AISIX_PROXY/v1/chat/completions" \
-H "Authorization: Bearer $AISIX_API_KEY" \
-H "Content-Type: application/json" \
-H "x-trace-id: trace-0001" \
-d '{
"model": "'"${MODEL_ALIAS}"'",
"messages": [{"role": "user", "content": "ping"}]
}'

Then confirm on the upstream side that the request arrived with the expected headers. Check the access log of the upstream service where you can.

If you point api_base at a request-inspection service instead, remember that AISIX sends the provider key's credential and the request body to whatever that api_base names. Use an endpoint you control, a throwaway credential on the test provider key, and a prompt that carries no real data.

If an expected header is missing, check the following:

  • The header name is listed in forward_client_headers, or matches one of its wildcard entries.
  • The header is not in Caller Headers AISIX Never Forwards.
  • For a default_headers value with a variable, the calling API key actually has that attribute. A key with no team drops a header that references request.api_key.team_id.
  • In a resources file, each request-context reference escapes the load-time environment interpolation as $${...}.

Next Steps