---
title: "Request and response headers"
description: "The request headers the pirouter gateway reads and the response headers it returns, the same on the inference endpoints of all three protocols. The most useful one is X-Pi-Trace-Id, on every response; X-Pi-Provider and its siblings appear only on responses a provider returned."
canonical: "https://pirouter.ai/docs/api/headers"
last-updated: "2026-10-17"
lang: "en"
source: "content/docs/en/api/headers.mdx"
---

# Request and response headers

The request headers the pirouter gateway reads and the response headers it returns, the same on the inference endpoints of all three protocols. The most useful one is X-Pi-Trace-Id, on every response; X-Pi-Provider and its siblings appear only on responses a provider returned.

The pirouter inference endpoints read the same request headers and return the same response headers on all three protocols. The one that matters most when something goes wrong is `X-Pi-Trace-Id`: every response carries it, errors included, so include its value when you report a problem.

## Response headers

Each header is listed with the response statuses it is sent with; `default` stands for the other error statuses that are not listed one by one (such as `500` and `503`).

| Header                          | Sent with                                                            | Description                                                                                                                                                                                                                                                                  |
| ------------------------------- | -------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `X-Pi-Trace-Id`                 | `200`, `400`, `401`, `402`, `404`, `413`, `422`, `429`, other errors | Platform request id, minted by the gateway. Present on every response of the inference endpoints; quote it when reporting a problem.                                                                                                                                         |
| `X-Pi-Provider`                 | `200`, other errors                                                  | Provider that served this response, in the same vocabulary as `provider.order` and `GET /v1/models/{vendor}/{model}/endpoints`. Present only when the upstream provider answered (a completion or an upstream error); after a fallback it names the provider that took over. |
| `X-Pi-Model`                    | `200`, other errors                                                  | Model slug that served this response - the same value as the body's `model`. When a fallback model took over, it is that model. Present only when the upstream provider answered.                                                                                            |
| `X-Pi-Upstream-Model`           | `200`, other errors                                                  | The full model id the upstream provider itself reported in its response, passed on verbatim (on a stream, from the first data event). Absent when the upstream response named no model; never filled from the platform's own records.                                        |
| `X-Pi-Upstream-Request-Id`      | `200`, other errors                                                  | The upstream provider's own request id, passed on verbatim, for looking the call up on the provider's side. Absent when the provider returned none.                                                                                                                          |
| `Access-Control-Allow-Origin`   | `200`, `400`, `401`, `402`, `404`, `413`, `422`, `429`, other errors | Always `*`. Credentials are the API key header, never a cookie.                                                                                                                                                                                                              |
| `Access-Control-Expose-Headers` | `200`, `400`, `401`, `402`, `404`, `413`, `422`, `429`, other errors | Lists every `X-Pi-*` response header plus `Retry-After` and `Content-Encoding`, so browser code can read them.                                                                                                                                                               |

### Which responses carry `X-Pi-Provider`

`X-Pi-Provider`, `X-Pi-Model`, `X-Pi-Upstream-Model` and `X-Pi-Upstream-Request-Id` appear only on responses a provider actually returned, whether it succeeded or returned an error. `X-Pi-Upstream-Model` and `X-Pi-Upstream-Request-Id` also need the provider to have sent that value.

These responses carry none of the four, only `X-Pi-Trace-Id` and the CORS headers:

- The gateway's own refusals: authentication, balance, rate limit, validation, no available provider.
- Requests the provider took more than 90 seconds to start answering. Their headers were sent before any provider responded. See [Long requests](https://pirouter.ai/docs/guides/timeouts.md#long-requests).

One exception: when a provider refuses pirouter's own credential (the provider returns `401` or `403`, not a problem with your key) and no other provider can take over, you get `503` with the error code `provider_unavailable`. That response carries `X-Pi-Provider` and `X-Pi-Model`, naming the provider that refused, and neither `X-Pi-Upstream-*` header.

### `Retry-After`

When pirouter rate limits a request (`429`, error code `rate_limited`), the response carries `Retry-After`: the number of seconds until the next full minute, from 1 to 60. See [Rate limits](https://pirouter.ai/docs/api/rate-limits.md).

### Provider response headers

The provider's own response headers are not passed on, except these five: `Content-Type`, `Content-Length`, `Content-Encoding`, `Cache-Control` and `Retry-After`. So a `Retry-After` the provider sends with its own `429` or `503` reaches you. A streamed response has no `Content-Length`. An error the gateway returns in place of the provider's (such as the `503` above) carries none of the five.

### Fixed security headers

Every response carries these headers with these values:

| Header                      | Value                                      |
| --------------------------- | ------------------------------------------ |
| `X-Content-Type-Options`    | `nosniff`                                  |
| `X-Frame-Options`           | `DENY`                                     |
| `Referrer-Policy`           | `strict-origin-when-cross-origin`          |
| `Content-Security-Policy`   | `frame-ancestors 'none'`                   |
| `Permissions-Policy`        | `camera=(), microphone=(), geolocation=()` |
| `Strict-Transport-Security` | `max-age=31536000; includeSubDomains`      |

## Request headers

### `Authorization` and `X-Api-Key`

Send the key in `Authorization: Bearer` or in `X-Api-Key`; when both are present, `Authorization` wins. `x-goog-api-key` is not read. The order the gateway reads them in, and how each SDK sends them, is in [Send the key with a request](https://pirouter.ai/docs/authentication.md#send-the-key-with-a-request).

### Optional request headers

All three headers below are optional, and none of them changes the content of the reply.

| Header            | What pirouter does with it                                                                                                                                                                                                                                                                                              |
| ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `X-Pi-Session-Id` | A session ID of up to 256 characters; a longer one counts as absent. Requests with the same workspace, key, model and session ID go first to the deployment this session was last routed to, as long as it still meets the routing rules. The pin lasts 1 hour and restarts each time a request is admitted             |
| `X-Title`         | Your app's name. pirouter removes control and invisible characters, keeps at most 128 characters, and records it. The **Tools** ranking on the console's **Activity** page uses it, together with `HTTP-Referer`, to recognize common apps and clients; anything it does not recognize goes under **Unrecognized apps** |
| `HTTP-Referer`    | Your app's URL. Only the origin (scheme, host and port) is recorded, and only `http` and `https` are accepted; without this header the standard `Referer` is read. A value that does not qualify is simply not recorded, and the request is handled as usual                                                            |

> **Warning:** These three headers are forwarded to the provider along with the request. Do not put anything sensitive in them.

The console's **Logs** do not show these three values today.

## Calling from a browser

The `OPTIONS` preflight a browser sends needs no key. The gateway answers it with `204`, the allowed methods (`POST, GET, OPTIONS`), the allowed request headers and `Access-Control-Max-Age: 86400`.

The allowed request headers are a fixed list: the credential headers of the three protocols (`Authorization`, `X-Api-Key`, `x-goog-api-key`), `Content-Type`, `anthropic-version`, `anthropic-beta` and `anthropic-dangerous-direct-browser-access`, the `X-Title`, `HTTP-Referer` and `X-Pi-Session-Id` headers above, and the `OpenAI-Organization`, `OpenAI-Project` and `X-Stainless-*` headers the official SDKs add in a browser. A header outside the list makes the browser's preflight fail; calls from a server are not limited by it.

`Access-Control-Allow-Origin` is `*`: the credential travels in a header, never in a cookie. `Access-Control-Expose-Headers` lists every `X-Pi-*` response header plus `Retry-After` and `Content-Encoding`, so browser code can read them.

> **Warning:** A key in browser code is visible to everyone who opens the page. Call pirouter from a browser only when your users enter their own key; keep your own key on your server, as [API overview](https://pirouter.ai/docs/api.md#authentication) explains.

## Next steps

- [Rate limits](https://pirouter.ai/docs/api/rate-limits.md): When a 429 appears and how long to wait before retrying.
- [Errors](https://pirouter.ai/docs/api/errors.md): What each error code means and how to handle it.
