# Request headers (/docs/gateway/headers)

Every header the gateway reads and every header it sends back — auth, client identity, caching, effort, thinking, sessions, tasks, cost.

All codai headers are optional and case-insensitive. Send them on `POST /v1/chat/completions`, `POST /v1/messages` and `POST /v1/responses` (the Responses route forwards every `X-Codai-*` header to the chat route it re-enters). Anything you send that the gateway does not understand is ignored.

## Authentication [#authentication]

| Header | Value |
| --- | --- |
| `Authorization` | `Bearer codai_…` — your API key, or an ephemeral `codai_eph_v1.…` token on the media surfaces. Required everywhere except the public `/vscode/*`, `/copilot/*`, `/health` and `/status` endpoints. |
| `x-api-key` | Accepted on `POST /v1/messages` only, for Anthropic SDKs. `Authorization` wins if both are present. |
| `anthropic-version`, `anthropic-beta`, `openai-beta` | Accepted and ignored. The gateway builds upstream headers itself and sets its own Anthropic betas; a client's `anthropic-beta` value is never forwarded. |

A missing or unknown key is `401 invalid_api_key`. A key from an inactive subscription is `402 subscription_inactive`.

## Client identity [#client-identity]

| Header | Effect |
| --- | --- |
| `X-Codai-Client` | Who is calling, as `<surface>/<version>` — `desktop/1.4.0`, `phone-android/0.9.2`, `codai-cli/0.3.0`, `hub/…`. Drives task attribution: `desktop` and `cli` may self-report task outcomes; everything else (IDEs, raw SDKs, curl) is `proxy` and its tasks close only on explicit user confirmation in the hub. Falls back to the `User-Agent` when absent. |
| `X-Request-Id` | Your own correlation id. Echoed back as `x-codai-trace-id`, forwarded to the upstream, and usable as `client_request_id` in `POST /v1/feedback`. |
| `X-Codai-Repo` | Repository identifier for memory scoping. When absent the gateway infers scope from the transcript. |
| `X-Codai-Agent-Id` | Attribution id recorded on tool calls in agent fleets. `[A-Za-z0-9._:-]`, max 128 chars. |

### Minimum client versions [#minimum-client-versions]

On every `/v1/*` route the gateway compares the version in `X-Codai-Client` (`<app>/<semver>`, apps `desktop`, `phone-android` — `phone` is accepted as an alias — `web` and `cli`) with two server-side minimums, configured per app as `CODAI_MIN_CLIENT` (hard) and `CODAI_RECOMMENDED_CLIENT` (soft), e.g. `desktop=0.9.0,phone-android=3.0.0`. Raising a minimum is a config change, not a code deploy.

- Below the **hard** minimum the request is rejected with **`426 client_outdated`**; `error.details` carries `app`, `min_version` and `download_url`.
- Below the **soft** minimum the request is served normally and the response carries `x-codai-upgrade: recommended; min=<version>`.
- A missing, unknown or unparseable `X-Codai-Client` (third-party SDKs, curl, dev builds) is never blocked.

```json
{ "error": { "type": "invalid_request_error", "code": "client_outdated", "message": "This codai app is too old (minimum 0.9.0). Update it to continue.", "details": { "app": "desktop", "min_version": "0.9.0", "download_url": "https://codai.ro/download" } } }
```

`x-codai-upgrade` is meant for the codai apps and is not in the CORS expose list below.

## Caching [#caching]

| Header | Effect |
| --- | --- |
| `X-Codai-Cache` | Prompt caching is **on by default** for every model. `0` or `false` turns it off — **except** on `codai*` aliases, where the opt-out is ignored. Cache reads cost roughly a tenth of fresh input, and an uncached 170 k-token agent loop once cost one user thousands of dollars a day, so the router alias does not let you disable it. |

Response side: `x-codai-cache-read` and `x-codai-cache-write` carry the cache token counts when known before the first byte; `usage.prompt_tokens_details.cached_tokens` carries the final read count in the body.

## Effort and thinking [#effort-and-thinking]

| Header | Effect |
| --- | --- |
| `X-Codai-Effort` | `minimal` · `low` · `medium` (default) · `high` · `max`. Wins over the body's `reasoning_effort`. See [models](./models.mdx#effort-and-thinking). |
| `X-Codai-Thinking` | `1` or `true` enables extended thinking explicitly. |
| `X-Codai-Thinking-Budget` | Thinking budget in tokens (positive integer, default 16 384). |
| `X-Codai-Thinking-Pin` | `1` keeps the full thinking budget on tool-continuation turns. Without it the gateway clamps injected budgets to 4 096 tokens on turns whose last message is a tool result, so agent loops do not pay `max` on every step. |

Response side: `x-codai-effort`, `x-codai-effort-applied`, `x-codai-effort-continuation-dampened`.

## Sessions and memory [#sessions-and-memory]

| Header | Effect |
| --- | --- |
| `X-Codai-Session-Id` | Stable id for a logical conversation. Enables sticky routing (keeps prompt caches warm), per-session cost roll-ups in `GET /v1/receipt`, and session memory. Header beats `body.user`. IDE clients usually omit it; the gateway then derives a key from the transcript. |
| `X-Codai-New-Session` | `1` starts a brand-new persistent session with a fresh id, ignoring any `X-Codai-Session-Id`. |
| `X-Codai-No-Recall` | `1` skips reading prior memory for this turn but still persists it. |
| `X-Codai-Incognito` | `1` makes the turn fully stateless — nothing read, nothing written, no sticky routing. |
| `X-Codai-Compact` | `auto` enables deterministic server-side context compaction (truncates stale tool results outside the protected tail, no model call). `off` disables it. Response: `x-codai-compacted: true|false`. |

## Agent behaviour [#agent-behaviour]

| Header | Effect |
| --- | --- |
| `X-Codai-Mode` | `agent` switches to plan-and-execute agent mode (raised depth cap, planning system message). Requires a tier with tools and sub-agents; otherwise `403 forbidden`. Echoed as `x-codai-mode: agent`. |
| `X-Codai-Disable-Subagents` | `1` skips the `delegate_to_model` tool injection. The VS Code config sets this — Copilot owns its own orchestration. |
| `X-Codai-Best-Of` | `3` forces best-of-N sampling on this request; `0` disables the auto-gate. Response: `x-codai-best-of-status`. |
| `X-Codai-Cascade` | `verify` forces draft-and-verify on this request. Response: `x-codai-cascade-status`. |

## Tasks and billing [#tasks-and-billing]

codai bills per **verified successful task**, not per token. These headers connect a request to its task.

| Header | Effect |
| --- | --- |
| `X-Codai-Task-Id` | Your own task id (`[A-Za-z0-9._:-]`, max 128). Correlates all turns of one task; without it the gateway derives a key from the transcript. |
| `X-Codai-Task-Outcome` | `pass` or `fail`. Honoured only from `desktop` and `cli` surfaces **and** only with machine evidence — otherwise ignored and logged. |
| `X-Codai-Task-Evidence` | `exec_verdict` or `reexec` — what proves the outcome. Required for `X-Codai-Task-Outcome` to take effect. |
| `X-Codai-No-Task` | `1` opts a utility call out of task counting. Honoured only on utility aliases such as `codai-fast`; on a full `codai` request it is rejected with `400`, so the task cap cannot be bypassed. |
| `X-Codai-Device`, `X-Codai-Device-Platform`, `X-Codai-Device-Name` | Device registration for the shared-sessions protocol (desktop, phone). Not needed for plain API calls. |

Response side: `x-codai-task-id` (the task this turn attached to), `x-codai-event-id` (the usage row — pass it to `POST /v1/feedback`).

## Response headers [#response-headers]

| Header | Meaning |
| --- | --- |
| `x-codai-routed-to` | Concrete upstream model that served. |
| `x-codai-provider` | Provider adapter kind, e.g. `vertex-anthropic`. |
| `x-codai-upstream` | Same as `routed-to`, set alongside the cost receipt. |
| `x-codai-trace-id` | Your `X-Request-Id` or a minted UUID. |
| `x-codai-event-id` | Id of the `usage_events` row for this request. |
| `x-codai-task-id` | Task this request attached to. |
| `x-codai-cost-micro-usd` | Integer micro-USD for this request, when known at header time. |
| `x-codai-cost-estimate` | `pending` on native streams — see `GET /v1/receipt`. |
| `x-codai-ai-generated` | `true` on every response that carries model output — chat, messages, responses, images, audio (speech and transcription), music, video, renders, agent runs. Machine-readable AI-generated marker (EU AI Act art. 50(2)). Never set on errors. |
| `x-codai-generator` | `codai/<upstream model>` that produced the output, e.g. `codai/claude-sonnet-5`; `codai/codai` when the model is not known at header time. Sent with `x-codai-ai-generated`. |
| `x-codai-cache-read`, `x-codai-cache-write` | Cache token counts when known up front. |
| `x-codai-spend-day-usd`, `x-codai-spend-day-cap-usd`, `x-codai-spend-month-usd`, `x-codai-spend-month-cap-usd` | Your running spend versus the cap, when spend caps are enabled for your account. |
| `x-codai-exec-verify` | `passed` · `refined` · `refine_failed` · `skipped` · `error` on `codai-labs` execution-verified answers. |
| `x-codai-embed-fallback` | `<requested>-><served>` when an embedding model was substituted. |
| `x-ratelimit-limit-minute`, `x-ratelimit-remaining-minute`, `x-ratelimit-reset-minute`, `x-ratelimit-limit-hour`, `x-ratelimit-remaining-hour`, `x-ratelimit-reset-hour` | Sliding-window request limits. `reset` is a Unix timestamp in seconds. |
| `Retry-After` | On `429`: seconds until the oldest entry ages out of the window. Honour it — it is exact, not a flat 60. |

All of these are in the CORS `Access-Control-Expose-Headers` list, so browser code can read them.

## Idempotency [#idempotency]

Chat requests have no `Idempotency-Key`. Retrying a chat request creates a new request and a new usage row; prompt caching keeps the retry cheap. For a stable identity across retries, send the same `X-Request-Id` — it becomes `client_request_id` on the usage row and lets `POST /v1/feedback` target the turn.

Billed codai Studio POSTs (`/v1/videos`, `/v1/audio/music`, `/v1/studio/renders`, `/v1/studio/timelines`, `/v1/studio/documents/*`, `/v1/studio/creatives/*` renders and exports, `/v1/studio/batches`) and sandbox runs accept `Idempotency-Key` (1-255 printable ASCII characters, kept 24 h):

- same key + same body: the first answer is replayed with `idempotent-replayed: true` and nothing is charged again;
- same key + a different body: `422 idempotency_key_mismatch`;
- the first request is still running: `409 idempotency_in_progress` — retry later.

A request that failed frees its key, so a retry with the same key runs again. SDKs: `ext: { headers: { 'Idempotency-Key': key } }` (TypeScript), `ext={"headers": {"Idempotency-Key": key}}` (Python).

## Studio wallet hold [#studio-wallet-hold]

Every paid Studio dispatch first places a hold of its price on your wallet. When the spendable balance (wallet minus compute reserve minus open holds) is short, the call answers `402 insufficient_funds` with `quote_micro_eur` and `balance_micro_eur` and nothing is dispatched or charged. The hold is settled with the billed amount when the job finishes and released when it fails. `GET /v1/studio/quote?action=video&seconds=8` returns the price and `sufficient` before you run anything; it is free.
