Skip to main content
Version: 1.0.0

Passthrough Routes

A passthrough route forwards matching requests to one upstream target without translating the request body. It is useful for provider-native endpoints that AISIX does not model as first-class routes and for forward-proxy traffic delivered with its original Host header.

Each route defines how traffic matches, where it goes, how AISIX authenticates the caller, and whether AISIX injects a provider credential or forwards the caller's credential. AISIX can still apply caller access controls, request limits, guardrails, and telemetry around the relay.

Explicit route required

The former implicit /passthrough/<provider>/... tunnel has been removed. An unclaimed /passthrough/* path follows the ordinary empty-body 404 path. Create and verify the replacement route before moving client traffic.

Passthrough routes do not rewrite model identifiers. If a provider-native request names a model in its body, path, query, or headers, send the identifier expected by that provider.

Prerequisites

Prepare the following:

  • One AISIX deployment:
    • For AISIX Cloud, an environment with an attached gateway and a write-scoped admin token.
    • For the open-source AISIX gateway, a gateway configured to load a declarative resources file.
  • An upstream provider credential for an inject route. The examples use OpenAI. A forward_client route instead relays the caller's upstream credential.
  • curl and jq.

Understand the Passthrough Flow

AISIX preserves the request body while handling gateway authentication, target construction, intentional header filtering, guardrails, and telemetry:

Route matching runs in two stages:

  1. A request whose inbound Host matches a route's hosts allowlist is dispatched before the gateway's typed routes. This lets a forward proxy relay an upstream path such as /v1/messages without the gateway treating it as its own endpoint.
  2. Path-prefix matching runs after the typed routes, so a path-only route cannot shadow the gateway's /v1, /mcp, or /a2a endpoints.

When several routes match, host matches beat path-only matches, and a longer matching prefix beats a shorter one.

Configure a Provider-Native Route

The following examples expose OpenAI's native model-list endpoint at /passthrough/openai/v1/models. AISIX injects the configured OpenAI credential and requires a caller key that grants openai-tunnel.

AISIX Cloud

Export the AISIX Cloud connection details and the provider credential:

# AISIX_CP includes /api and has no trailing slash.
export AISIX_CP="YOUR_AISIX_CLOUD_ADMIN_API_BASE_URL"
export AISIX_TOKEN="YOUR_ADMIN_TOKEN"
export ENV_ID="YOUR_ENVIRONMENT_ID"
export OPENAI_API_KEY="YOUR_OPENAI_API_KEY"

Create an OpenAI provider key that is available to the environment:

PROVIDER_KEY_ID=$(curl --fail-with-body -sS -X POST \
"$AISIX_CP/provider_keys" \
-H "Authorization: Bearer $AISIX_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"display_name": "OpenAI passthrough",
"provider": "openai",
"api_key": "'"${OPENAI_API_KEY}"'",
"api_base": "https://api.openai.com/v1",
"allowed_environments": ["'"${ENV_ID}"'"]
}' | jq -er '.provider_key.id')

Create the route with the provider key ID:

ROUTE_RESPONSE=$(curl --fail-with-body -sS -X POST \
"$AISIX_CP/environments/$ENV_ID/passthrough_routes" \
-H "Authorization: Bearer $AISIX_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "openai-tunnel",
"path_prefix": "/passthrough/openai",
"target_url": "https://api.openai.com/v1",
"credential_mode": "inject",
"provider_key_id": "'"${PROVIDER_KEY_ID}"'"
}')

export ROUTE_ID=$(printf '%s' "$ROUTE_RESPONSE" | jq -er '.passthrough_route.id')
printf '%s' "$ROUTE_RESPONSE" | jq '.warnings // []'

Keep ROUTE_ID for route updates or route-scoped guardrail attachments.

Create a dedicated caller key and grant the route in the same request. The plaintext is returned once:

CALLER_RESPONSE=$(curl --fail-with-body -sS -X POST \
"$AISIX_CP/environments/$ENV_ID/api_keys" \
-H "Authorization: Bearer $AISIX_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"display_name": "OpenAI passthrough caller",
"allowed_models": [],
"allowed_routes": ["openai-tunnel"]
}')

export AISIX_API_KEY=$(printf '%s' "$CALLER_RESPONSE" | jq -er '.plaintext')
printf '%s' "$CALLER_RESPONSE" | jq '.warnings // []'

The control plane projects the provider key, route, and caller grant to attached gateways. Review any returned compatibility warnings before rollout. Warnings are advisory, so verify traffic through each gateway. In the dashboard, the same workflow is available under Provider keys, an environment's Passthrough Routes, and the caller key's Passthrough route access section.

If you grant an existing caller instead, include every route grant it should keep. The AISIX Cloud Admin API replaces the complete allowed_routes list when that field is patched.

Open-Source AISIX Gateway

Prepare an openai-prod provider key in the complete resources file, then choose a dedicated caller credential:

export PASSTHROUGH_CALLER_KEY="YOUR_CALLER_API_KEY"

Add the route and caller entries to the matching collections. Keep the provider key and other resources unchanged:

resources.yaml (route and caller key)
passthrough_routes:
- name: openai-tunnel
path_prefix: /passthrough/openai
target_url: https://api.openai.com/v1
provider_key: openai-prod

api_keys:
- display_name: passthrough-caller
key_env: PASSTHROUGH_CALLER_KEY
allowed_models: []
allowed_routes: [openai-tunnel]

The provider_key name is resolved to the provider key's derived ID when AISIX loads the file. An unknown name fails validation. Exact entries in allowed_routes are also checked against the routes defined in the file; wildcard patterns are allowed.

Validate the assembled complete file before loading it:

aisix validate --resources resources.yaml

Because this example introduces PASSTHROUGH_CALLER_KEY, start or recreate the gateway with that variable in its process environment. After it loads, use the same value for the verification request:

export AISIX_API_KEY="$PASSTHROUGH_CALLER_KEY"

Verify the Route

Export the gateway origin without a trailing slash:

# The local quickstarts use http://127.0.0.1:3000.
export AISIX_PROXY="YOUR_AISIX_GATEWAY_ORIGIN"

Request OpenAI's native model list through AISIX:

curl --fail-with-body -sS \
"$AISIX_PROXY/passthrough/openai/v1/models" \
-H "Authorization: Bearer $AISIX_API_KEY"

The route strips its path_prefix, avoids duplicating the /v1 segment already present in target_url, injects the OpenAI provider credential, and relays the upstream response.

Compare Cloud and Resources-File References

Most route fields use the same names in both management paths. References to provider and caller credentials differ:

PurposeAISIX Cloud Admin APIRecommended resources-file fieldExplicit ID accepted in a resources file
Injected provider credentialprovider_key_id (UUID)provider_key (display_name)provider_key_id
Anonymous caller principalanonymous_key_id (UUID)anonymous_key (display_name)anonymous_key_id

Name references in a resources file receive stronger load-time checking and produce candidate names when a reference is unknown. Prefer them over explicit IDs. AISIX Cloud validates that the provider key is visible to the environment and that the anonymous caller key belongs to it.

The route name is fixed after creation in AISIX Cloud. In a resources file, changing the name changes the route identity, so update every caller's allowed_routes entry at the same time.

Configuration Reference

FieldBehavior
nameRoute identity referenced by callers' allowed_routes patterns and recorded on usage events.
path_prefixGateway path prefix, matched on segment boundaries. A target_url route strips it before joining the remaining path to the target. A preserve_host route keeps the complete path. A path-only route cannot claim /v1, /mcp, /a2a, /admin, /livez, /readyz, or /metrics; a route that also matches hosts may use those upstream-owned paths.
hostsCase-insensitive inbound Host allowlist; a port is ignored. A leading *. wildcard matches one additional label and must retain at least two literal labels.
target_urlExplicit upstream base URL. Configure exactly one target shape: target_url, or preserve_host: true.
preserve_hostDerive https://<matched-host> as the target. It is accepted only with hosts, which bounds the derived destination.
auth_modegateway_key (default), header_key, or anonymous.
auth_header_nameLowercase side-channel header containing the gateway credential in header_key mode. AISIX strips it before forwarding, unless forward_client_headers names it in full; a glob such as x-* does not reach it. authorization, proxy-authorization, cookie, set-cookie, and x-api-key are rejected.
source_cidrsClient source allowlist. It is required and nonempty for anonymous; for other modes it is optional hardening.
credential_modeinject (default) or forward_client.
forward_client_headersInbound client headers relayed upstream even when this route would otherwise strip them, as exact names or single-* globs, matched case-insensitively. Empty by default. A patch replaces the stored list; send null to clear it. In the dashboard it is Forward client headers under Advanced, one entry per line. See Upstream Request Headers.
identity_headerOptional lowercase device-injected identity header. AISIX records a bounded value as client_identity and strips the header before forwarding, unless forward_client_headers names it in full; a glob such as x-* does not reach it. Configure it only behind a trusted device that removes and replaces any client-supplied value. authorization, proxy-authorization, cookie, set-cookie, and x-api-key are rejected.
timeout_msBounds upstream response headers and non-SSE body reads. It does not bound a healthy SSE relay.
enabledA disabled route matches nothing. Default: true.

At least one match dimension, path_prefix or hosts, is required. When both are present, the request must satisfy both.

Gateway Authentication Modes

  • gateway_key reads the standard gateway credential from Authorization: Bearer or x-api-key.
  • header_key reads the gateway credential from auth_header_name, leaving Authorization available for the caller's upstream credential. This is the standard forward-proxy pairing with forward_client.
  • anonymous accepts no gateway credential. The request runs as the configured caller-key principal and must originate within source_cidrs.

Every resolved principal still needs the route name granted by allowed_routes; * grants every route. A valid key without a matching grant receives 403.

In AISIX Cloud, budgets that already apply to the resolved caller are checked before dispatch. Passthrough usage currently carries no model ID, is recorded with zero cost, and does not add spend to those budgets. The open-source AISIX gateway has no local budget resource.

Upstream Credential Modes

  • inject strips inbound credential headers and injects the configured provider key. AISIX uses x-api-key plus anthropic-version for Anthropic and Authorization: Bearer for other providers. The provider key's configured header-strip and TLS settings also apply.
  • forward_client forwards the caller's upstream credential when the gateway credential arrived through header_key, or when the route is anonymous. With gateway_key, AISIX removes Authorization and x-api-key because either may contain the gateway credential.

A route relays the caller's other headers by default and strips a small set: hop-by-hop and transport headers, host, content-length, the x-aisix-* namespace, proxy-authorization, and the caller's W3C trace headers. AISIX consumes valid W3C context for its own OTLP trace and sends a gateway request ID upstream.

forward_client_headers overrides that strip, and is the one way to put a header back that the route would otherwise remove. Naming authorization under auth_mode: gateway_key therefore relays the caller's own credential — the very header the gateway consumed to authenticate them — in place of the injected one, which is what lets an internal service that authorizes on the end user keep doing so. host, content-length, hop-by-hop headers, and x-aisix-* are stripped whatever the patterns say, and a credential or trace-context header must be named exactly rather than matched by a wildcard. The route's own auth_header_name and identity_header are read the same way: AISIX consumes both, so a glob does not reach either and naming one in full is what forwards it. For every other name the route strips, a glob is enough — including a provider key strip_headers entry, though three of that list's four defaults (authorization, cookie, x-api-key) are credential slots that still need their own entry, leaving set-cookie the only default a glob restores.

There is no credential fallback: an inject route without a resolvable provider key fails closed, and a forward_client route cannot carry a provider key reference.

Envelope Detection and Usage

AISIX detects the request shape for extraction only; detection does not change the relayed body. If several recognized carrier fields appear, detection uses this order:

  1. messages for OpenAI-compatible chat or Anthropic Messages traffic.
  2. input for the OpenAI Responses shape.
  3. prompt for legacy completions or fill-in-the-middle traffic.
  4. Opaque handling for every other body, including JSON-RPC, REST, non-JSON, and empty bodies.

Detection selects the text presented to guardrails and the token fields recorded on usage events. If a detected shape yields no text, AISIX scans the complete body instead. Request and response bodies are still relayed without schema translation.

Opaque buffered responses do not receive speculative token extraction. An opaque SSE stream can report usage through a top-level usage object or a flat token report on an event: usage or event: token_usage frame.

Rate Limits

Caller API-key, team, and member request limits apply before dispatch. On an inject route, a top-level JSON model that resolves to a configured AISIX model of the same provider also reserves that model's request limits. forward_client routes do not perform this model lookup.

Request-count dimensions (rps, rpm, rph, and rpd) are enforced. AISIX also rejects a request when an applicable tpm or tpd counter is already exhausted, but passthrough token usage does not increment those counters. Use recorded usage for telemetry, not passthrough token-quota enforcement.

Concurrency is checked before upstream dispatch. For SSE, the reservation is released when AISIX returns the streaming response, not when the stream ends.

Passthrough routes have no rate-limit field or policy scope. To apply different request limits to different routes, grant them to separate caller keys and configure limits for those caller identities.

Guardrails and Streaming

In AISIX Cloud, a guardrail can be attached to one passthrough route by selecting the Passthrough routes scope. The attachment uses the route UUID. Environment, caller-key, and team guardrails can also apply.

The open-source resources file declares attachments in its guardrail_attachments collection, so a file-defined guardrail reaches passthrough traffic only if an attachment scopes it there — scope_type: env, or scope_type: passthrough_route naming the route.

Input guardrails run before upstream dispatch. A block returns 422 without contacting the upstream. Buffered responses are checked before delivery. Once an SSE response has started, a block ends the stream with an SSE content_filter error frame; it cannot change the HTTP status to 422.

SSE responses relay incrementally unless a hold-back guardrail buffers frames for inspection. AISIX does not rewrite provider-native bodies to apply redaction. For the built-in pii guardrail, a mask-action-only match is forwarded without masking; use a block action when the matched content must not reach the upstream. Guardrail kinds such as Presidio and Lakera instead block a maskable result when passthrough has no write-back channel.

Audit Capture

For successfully relayed traffic, an observability exporter with content_mode: full receives the request body as a string, subject to the exporter's content cap. Buffered responses record extracted text when the response matches a supported extraction shape and otherwise record the body as text. Streamed responses record accumulated extracted text; opaque data payloads are retained as text. Captured content is exporter-only and is never sent through the AISIX Cloud telemetry path.

Usage events carry the route name, caller, recorded token counts, and client_identity. External exporters can expose these values. The current AISIX Cloud Request Logs UI shows caller and token metadata, but it does not display passthrough_route_name or client_identity.

Migrate from the Removed Implicit Tunnel

The removed tunnel selected a provider key indirectly through an accessible model. Explicit routes replace that ambiguous selection with a fixed target and credential binding.

For each provider prefix that clients still use:

  1. Create an explicit route with the old path prefix and the intended upstream target.
  2. Bind the intended provider key in inject mode.
  3. Grant the route name to every caller that should retain access.
  4. Verify the route before moving or restarting clients.

Clients can keep their existing /passthrough/<provider>/... URL when the explicit route claims the same prefix. Requests answer ordinary 404 until that route is active.

Two former tunnel behaviors do not carry over: passthrough routes make one upstream attempt without transport retries, and their failures do not mark configured models for cooldown.

Errors

Status or signalMeaning
401Missing or invalid gateway credential for the route's authentication mode.
403The resolved caller key does not grant the route, or the client source is outside source_cidrs.
404An unclaimed /passthrough/* path reaches the ordinary empty-body not-found path.
422A guardrail blocked the request before dispatch or blocked a buffered response before delivery.
SSE content_filter frameA guardrail blocked content after a stream had started.
429A gateway request limit or budget check rejected the request, or the upstream returned a relayed 429.
Other upstream statusAISIX relays the upstream status and body after filtering response headers.

The unmatched 404 has an empty body. Other AISIX-generated failures use the gateway error envelope; upstream error statuses and bodies are relayed after response-header filtering.

Next Steps