> ## Documentation Index
> Fetch the complete documentation index at: https://docs.recovea.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Create a chat completion

POST `/v1/chat/completions`

Base URL `https://api.recovea.ai/v1`

```http
POST /v1/chat/completions HTTP/1.1
Host: api.recovea.ai
Authorization: Bearer $RECOVEA_API_KEY
Content-Type: application/json

{
  "model": "MODEL_ID",
  "messages": [
    {
      "role": "user",
      "content": "hi"
    }
  ]
}
```

```http
POST /v1/chat/completions HTTP/1.1
Host: api.recovea.ai
Authorization: Bearer $RECOVEA_API_KEY
Content-Type: application/json

{
  "model": "MODEL_ID",
  "messages": [
    {
      "role": "user",
      "content": "hi"
    }
  ],
  "stream": true
}
```

```http
POST /v1/chat/completions HTTP/1.1
Host: api.recovea.ai
Authorization: Bearer $RECOVEA_API_KEY
Content-Type: application/json

{
  "stream_options": {
    "include_usage": true
  },
  "model": "MODEL_ID",
  "messages": [
    {
      "role": "user",
      "content": "hi"
    }
  ],
  "stream": true
}
```

```http
HTTP/1.1 402 Payment Required

{
  "error": {
    "message": "Monthly budget reached. Traffic resumes at reset, or raise the cap.",
    "type": "insufficient_quota",
    "param": null,
    "code": "budget_exceeded"
  }
}
```

OpenAI's chat completions endpoint, forwarded to OpenAI. The body is forwarded as sent, with one addition: a streaming request (`"stream": true`) that carries no `stream_options` gets `"stream_options":{"include_usage":true}` added at the front of the object, so OpenAI sends the usage chunk the call is metered from. The rest of the body keeps its bytes, and the usage chunk comes back to you unchanged. A request that already says anything about `stream_options`, `{"include_usage": false}` included, is forwarded untouched.

## Authentication

A Recovea key, in either header. The gateway removes it before the request is forwarded.

| Header | Description |
| --- | --- |
| `Authorization` Bearer rcv_live_… one of the two | A Recovea key in `Authorization: Bearer rcv_live_…`, the form OpenAI clients send. Unknown, revoked and missing keys all receive the same 401 `invalid_api_key`. |
| `x-api-key` string one of the two | The same Recovea key in `x-api-key`, the form Anthropic clients send. Read when no `Authorization: Bearer` is present. |

## Request headers

| Header | Description |
| --- | --- |
| `x-recovea-tags` string optional | Attribution labels for this call's reading, comma-separated. Opaque to the gateway: split on commas and trimmed, never interpreted. Several header lines are read as one list. Up to 32 labels of up to 128 characters are kept; the rest are dropped from the reading and counted. Removed before the request is forwarded. Example: `checkout,team:payments` |
| `x-recovea-project` string optional | One opaque project label for this call's reading. Removed before the request is forwarded. Example: `checkout` |
| `x-recovea-run-id` string optional | The run this request belongs to (Breaker), chosen by you: 1 to 128 visible ASCII characters. Required whenever any other `x-recovea-run-*` header is sent. Removed before the request is forwarded, like every `x-recovea-run-*` header. Example: `nightly-report-7` |
| `x-recovea-run-budget` string optional | The run's budget in US dollars: a positive decimal with at most 6 decimal places. It binds for the life of the run. Sent again on a live run, a smaller figure tightens the budget and a larger one is ignored. The budget that binds comes back in `x-recovea-run-budget-effective`. Example: `5.00` |
| `x-recovea-run-ttl` string optional | The run's time limit in whole seconds, fixed by the run's first request; sending it again does not extend it. Past it, the run refuses with `run_expired`. Example: `3600` |
| `x-recovea-run-grant` string optional | A signed run-budget grant (an `rbg1` token), needed for a run budget above what a key may declare on its own. A grant that is not accepted refuses with `invalid_run_grant`, whatever the reason. Example: `rbg1.…` |

## Request body

Required, `application/json`

OpenAI's chat completion request. Forwarded as sent except for the one addition `stream_options` describes; the provider validates it.

| Field | Description |
| --- | --- |
| `model` string optional passthrough | Forwarded as sent. The model that served the call is read from the response, not from here. |
| `messages` array of any optional passthrough | Forwarded as sent and never read. |
| `stream` boolean optional read | Read. When it is literally `true` and `stream_options` is absent, the gateway adds `stream_options`. |
| `stream_options` object or null optional read | Read for presence only. Absent on a streaming request, the gateway adds `{"include_usage":true}` so the provider sends the usage chunk. Present in any form, it is forwarded untouched. |

## Responses

### 200 OK

The provider's response, returned byte for byte, with `x-recovea-request-id` added and, under an armed cap or a run budget, the headers below.

| Header | Description |
| --- | --- |
| `x-recovea-request-id` string required | The gateway's id for this request: `req_` and 32 hex characters. On every response the gateway sends, forwarded or refused. Quote it when you ask about a request. |
| `x-request-id` string required | On a forwarded response, the provider's own request id, passed through untouched. On a response the gateway originates, a `req_` id the gateway fills in; it is not the same value as `x-recovea-request-id`. |
| `x-recovea-budget-scope` "organization" \\| "key" \\| "run" optional | Which kind of cap bound this request, or refused it. On a response served under an armed cap it names the tightest one; on a refusal it names the cap that refused. Absent when no armed cap bound the request, and on refusals that read no cap. |
| `x-recovea-budget-window` "run" \\| "day" \\| "week" \\| "month" \\| "year" \\| "lifetime" optional | The clock that cap runs on. Calendar windows are UTC; `run` is the life of one run. Rides wherever `x-recovea-budget-scope` does and names the same cap. |
| `x-recovea-run-budget-effective` string optional | The run budget that binds, in US dollars at 6 decimals. On a response served under a run that has a budget. Example: `0.005000` |
| `x-recovea-run-clamp-reason` "policy_max" \\| "grant_max" \\| "workspace_ceiling" \\| "exposure_headroom" optional | Present only when the declared run budget was lowered, naming the bound that lowered it. |

| Field | Description |
| --- | --- |
| `model` string optional read | Read from a bounded copy of the response to record the call. |
| `choices` array of object optional read |  |
| `choices[].finish_reason` string optional read | The first choice's finish reason is read to record the call. |
| `usage` object optional read | Provider-reported token counts, read to record the call. Never estimated. |
| `usage.prompt_tokens` integer optional read | Read from a bounded copy of the response to record the call. |
| `usage.completion_tokens` integer optional read | Read from a bounded copy of the response to record the call. |
| `usage.prompt_tokens_details` object optional |  |
| `usage.prompt_tokens_details.cached_tokens` integer optional read | Read from a bounded copy of the response to record the call. |
| `usage.completion_tokens_details` object optional |  |
| `usage.completion_tokens_details.reasoning_tokens` integer optional read | Read from a bounded copy of the response to record the call. |

### 400 Bad Request

A malformed `x-recovea-run-*` header, named in the message. Nothing was forwarded.

| Header | Description |
| --- | --- |
| `x-recovea-request-id` string required | The gateway's id for this request: `req_` and 32 hex characters. On every response the gateway sends, forwarded or refused. Quote it when you ask about a request. |
| `x-request-id` string required | On a forwarded response, the provider's own request id, passed through untouched. On a response the gateway originates, a `req_` id the gateway fills in; it is not the same value as `x-recovea-request-id`. |

```http
HTTP/1.1 400 Bad Request

{
  "error": {
    "message": "x-recovea-run-id must be 1 to 128 visible ASCII characters, and is required whenever any x-recovea-run-* header is sent.",
    "type": "invalid_request_error",
    "param": null,
    "code": "invalid_run_id"
  }
}
```

| Refused by | `error.code` and `error.message` |
| --- | --- |
| A malformed or missing x-recovea-run-id | `invalid_run_id` x-recovea-run-id must be 1 to 128 visible ASCII characters, and is required whenever any x-recovea-run-\* header is sent. |
| An unparseable x-recovea-run-budget | `invalid_run_budget` x-recovea-run-budget must be a positive decimal amount of US dollars with at most six decimal places, for example 5.00. |
| An unparseable x-recovea-run-ttl | `invalid_run_ttl` x-recovea-run-ttl must be a positive whole number of seconds. |
| A run-budget grant that is refused | `invalid_run_grant` x-recovea-run-grant did not verify. Mint a new run-budget grant for this workspace and run. |

### 401 Unauthorized

`invalid_api_key`: the Recovea key is unknown, revoked or missing, and all three receive this one body. `provider_key_missing`: no key for this destination is connected to your organization. `provider_key_unavailable`: one is connected but cannot be read right now; retry shortly.

| Header | Description |
| --- | --- |
| `x-recovea-request-id` string required | The gateway's id for this request: `req_` and 32 hex characters. On every response the gateway sends, forwarded or refused. Quote it when you ask about a request. |
| `x-request-id` string required | On a forwarded response, the provider's own request id, passed through untouched. On a response the gateway originates, a `req_` id the gateway fills in; it is not the same value as `x-recovea-request-id`. |

```http
HTTP/1.1 401 Unauthorized

{
  "error": {
    "message": "Invalid API key.",
    "type": "invalid_request_error",
    "param": null,
    "code": "invalid_api_key"
  }
}
```

| Refused by | `error.code` and `error.message` |
| --- | --- |
| An unknown, revoked or missing key | `invalid_api_key` Invalid API key. |
| No provider key connected | `provider_key_missing` No OpenAI key is connected for this workspace. |
| The provider key cannot be read right now | `provider_key_unavailable` The OpenAI key for this workspace is temporarily unavailable. Retry shortly. |

### 402 Payment Required

Refused before the provider was contacted: nothing was forwarded and nothing was spent. An armed cap answers `budget_exceeded` with `type` `insufficient_quota`, one shape for every limit; only the sentence moves, naming the window and, for a key's cap, the scope. The organization's monthly sentence is the original constant: "Monthly budget reached. Traffic resumes at reset, or raise the cap." A cap refusal carries `x-recovea-budget-scope` and `x-recovea-budget-window` and no `Retry-After`. A run's own refusals (`run_expired`, `run_closed` and the rest) share the status and the `type`, carry their own codes, and state no budget headers.

| Header | Description |
| --- | --- |
| `x-recovea-request-id` string required | The gateway's id for this request: `req_` and 32 hex characters. On every response the gateway sends, forwarded or refused. Quote it when you ask about a request. |
| `x-request-id` string required | On a forwarded response, the provider's own request id, passed through untouched. On a response the gateway originates, a `req_` id the gateway fills in; it is not the same value as `x-recovea-request-id`. |
| `x-recovea-budget-scope` "organization" \\| "key" \\| "run" optional | Which kind of cap bound this request, or refused it. On a response served under an armed cap it names the tightest one; on a refusal it names the cap that refused. Absent when no armed cap bound the request, and on refusals that read no cap. |
| `x-recovea-budget-window` "run" \\| "day" \\| "week" \\| "month" \\| "year" \\| "lifetime" optional | The clock that cap runs on. Calendar windows are UTC; `run` is the life of one run. Rides wherever `x-recovea-budget-scope` does and names the same cap. |

```http
HTTP/1.1 402 Payment Required

{
  "error": {
    "message": "Monthly budget reached. Traffic resumes at reset, or raise the cap.",
    "type": "insufficient_quota",
    "param": null,
    "code": "budget_exceeded"
  }
}
```

| Refused by | `error.code` and `error.message` |
| --- | --- |
| The organization's cap on the month window | `budget_exceeded` Monthly budget reached. Traffic resumes at reset, or raise the cap. |
| The organization's cap on the day window | `budget_exceeded` Daily budget reached. Traffic resumes at reset, or raise the cap. |
| The organization's cap on the week window | `budget_exceeded` Weekly budget reached. Traffic resumes at reset, or raise the cap. |
| The organization's cap on the year window | `budget_exceeded` Yearly budget reached. Traffic resumes at reset, or raise the cap. |
| The organization's cap on the lifetime window | `budget_exceeded` Lifetime budget reached. This budget does not reset; raise the cap to continue. |
| A key's cap on the day window | `budget_exceeded` Daily budget reached for this API key. Traffic resumes at reset, or raise the cap. |
| A key's cap on the week window | `budget_exceeded` Weekly budget reached for this API key. Traffic resumes at reset, or raise the cap. |
| A key's cap on the month window | `budget_exceeded` Monthly budget reached for this API key. Traffic resumes at reset, or raise the cap. |
| A key's cap on the year window | `budget_exceeded` Yearly budget reached for this API key. Traffic resumes at reset, or raise the cap. |
| A key's cap on the lifetime window | `budget_exceeded` Lifetime budget reached for this API key. This budget does not reset; raise the cap to continue. |
| A run over its own budget | `budget_exceeded` Run budget reached. This run is stopped for good; start a new run to continue. |
| A run past its time limit | `run_expired` This run's time limit has passed. Start a new run to continue. |
| A run that was closed | `run_closed` This run was closed. Start a new run to continue. |
| No concurrent run budget left | `run_exposure_exhausted` This workspace has no concurrent run budget left. Close a finished run, or wait for one to end. |
| A run budget that resolves to zero | `run_envelope_zero` The run budget resolved to zero. Raise the run budget policy for this key or workspace. |
| A gateway node holding as many runs as it can | `run_registry_full` This gateway node is holding as many runs as it can. Retry shortly. |

### 429 Too Many Requests

Two refusals share this status, and `error.code` tells them apart. `rate_limit_exceeded` is the rate limit for your plan. `budget_exceeded` with `type` `rate_limit_error` is an armed cap in pause mode: the same sentences as the 402, and it carries `x-recovea-budget-scope` and `x-recovea-budget-window`. Both carry `Retry-After`. Nothing queues and nothing is forwarded.

| Header | Description |
| --- | --- |
| `x-recovea-request-id` string required | The gateway's id for this request: `req_` and 32 hex characters. On every response the gateway sends, forwarded or refused. Quote it when you ask about a request. |
| `x-request-id` string required | On a forwarded response, the provider's own request id, passed through untouched. On a response the gateway originates, a `req_` id the gateway fills in; it is not the same value as `x-recovea-request-id`. |
| `retry-after` integer required | Whole seconds before a retry can succeed. For the rate limit, until a request fits again; for a cap in pause mode, until its window resets (a lifetime cap never resets and sends 86400, a back-off rather than a reset). |
| `x-recovea-budget-scope` "organization" \\| "key" \\| "run" optional | Which kind of cap bound this request, or refused it. On a response served under an armed cap it names the tightest one; on a refusal it names the cap that refused. Absent when no armed cap bound the request, and on refusals that read no cap. |
| `x-recovea-budget-window` "run" \\| "day" \\| "week" \\| "month" \\| "year" \\| "lifetime" optional | The clock that cap runs on. Calendar windows are UTC; `run` is the life of one run. Rides wherever `x-recovea-budget-scope` does and names the same cap. |

```http
HTTP/1.1 429 Too Many Requests
Retry-After: 1

{
  "error": {
    "message": "Rate limit reached for this workspace.",
    "type": "rate_limit_error",
    "param": null,
    "code": "rate_limit_exceeded"
  }
}
```

| Refused by | `error.code` and `error.message` |
| --- | --- |
| The rate limit | `rate_limit_exceeded` Rate limit reached for this workspace. |
| The organization's cap on the day window, pause mode | `budget_exceeded` Daily budget reached. Traffic resumes at reset, or raise the cap. |
| The organization's cap on the week window, pause mode | `budget_exceeded` Weekly budget reached. Traffic resumes at reset, or raise the cap. |
| The organization's cap on the month window, pause mode | `budget_exceeded` Monthly budget reached. Traffic resumes at reset, or raise the cap. |
| The organization's cap on the year window, pause mode | `budget_exceeded` Yearly budget reached. Traffic resumes at reset, or raise the cap. |
| The organization's cap on the lifetime window, pause mode | `budget_exceeded` Lifetime budget reached. This budget does not reset; raise the cap to continue. |
| A key's cap on the day window, pause mode | `budget_exceeded` Daily budget reached for this API key. Traffic resumes at reset, or raise the cap. |
| A key's cap on the week window, pause mode | `budget_exceeded` Weekly budget reached for this API key. Traffic resumes at reset, or raise the cap. |
| A key's cap on the month window, pause mode | `budget_exceeded` Monthly budget reached for this API key. Traffic resumes at reset, or raise the cap. |
| A key's cap on the year window, pause mode | `budget_exceeded` Yearly budget reached for this API key. Traffic resumes at reset, or raise the cap. |
| A key's cap on the lifetime window, pause mode | `budget_exceeded` Lifetime budget reached for this API key. This budget does not reset; raise the cap to continue. |

### 502 Bad Gateway

The gateway could not reach the destination at all: the connection or the first byte failed. The only 5xx the gateway originates on a forwarded call. A stream that fails after its first byte ends instead; bytes already sent are not replaced.

| Header | Description |
| --- | --- |
| `x-recovea-request-id` string required | The gateway's id for this request: `req_` and 32 hex characters. On every response the gateway sends, forwarded or refused. Quote it when you ask about a request. |
| `x-request-id` string required | On a forwarded response, the provider's own request id, passed through untouched. On a response the gateway originates, a `req_` id the gateway fills in; it is not the same value as `x-recovea-request-id`. |

| Field | Description |
| --- | --- |
| `error` object required |  |
| `error.message` string required |  |
| `error.type` string required |  |
| `error.param` string or null required |  |
| `error.code` string required |  |

```http
HTTP/1.1 502 Bad Gateway

{
  "error": {
    "message": "Upstream provider unreachable.",
    "type": "api_error",
    "param": null,
    "code": "upstream_unreachable"
  }
}
```

### 503 Service Unavailable

An armed cap's live spend cannot be confirmed on this node right now, so the request is refused rather than let through unprotected, and nothing is forwarded. `error.code` is `budget_unverifiable` and `Retry-After` is `1`: the condition clears when a fresh checkpoint lands, so retry. It is not an out-of-credit answer; a spent budget answers 402.

| Header | Description |
| --- | --- |
| `x-recovea-request-id` string required | The gateway's id for this request: `req_` and 32 hex characters. On every response the gateway sends, forwarded or refused. Quote it when you ask about a request. |
| `x-request-id` string required | On a forwarded response, the provider's own request id, passed through untouched. On a response the gateway originates, a `req_` id the gateway fills in; it is not the same value as `x-recovea-request-id`. |
| `retry-after` 1 required | Always `1`: a floor, not a promise. The condition clears when a fresh checkpoint lands. |
| `x-recovea-budget-scope` "organization" \\| "key" \\| "run" required | Which kind of cap bound this request, or refused it. On a response served under an armed cap it names the tightest one; on a refusal it names the cap that refused. Absent when no armed cap bound the request, and on refusals that read no cap. |
| `x-recovea-budget-window` "run" \\| "day" \\| "week" \\| "month" \\| "year" \\| "lifetime" required | The clock that cap runs on. Calendar windows are UTC; `run` is the life of one run. Rides wherever `x-recovea-budget-scope` does and names the same cap. |

```http
HTTP/1.1 503 Service Unavailable
Retry-After: 1

{
  "error": {
    "message": "Monthly spend cannot be confirmed right now. Retry in a moment.",
    "type": "insufficient_quota",
    "param": null,
    "code": "budget_unverifiable"
  }
}
```

| Refused by | `error.code` and `error.message` |
| --- | --- |
| The organization's cap on the month window | `budget_unverifiable` Monthly spend cannot be confirmed right now. Retry in a moment. |
| The organization's cap on the day window | `budget_unverifiable` Daily spend cannot be confirmed right now. Retry in a moment. |
| The organization's cap on the week window | `budget_unverifiable` Weekly spend cannot be confirmed right now. Retry in a moment. |
| The organization's cap on the year window | `budget_unverifiable` Yearly spend cannot be confirmed right now. Retry in a moment. |
| The organization's cap on the lifetime window | `budget_unverifiable` Lifetime spend cannot be confirmed right now. Retry in a moment. |
| A key's cap on the day window | `budget_unverifiable` Daily spend for this API key cannot be confirmed right now. Retry in a moment. |
| A key's cap on the week window | `budget_unverifiable` Weekly spend for this API key cannot be confirmed right now. Retry in a moment. |
| A key's cap on the month window | `budget_unverifiable` Monthly spend for this API key cannot be confirmed right now. Retry in a moment. |
| A key's cap on the year window | `budget_unverifiable` Yearly spend for this API key cannot be confirmed right now. Retry in a moment. |
| A key's cap on the lifetime window | `budget_unverifiable` Lifetime spend for this API key cannot be confirmed right now. Retry in a moment. |
| A run over its own budget | `budget_unverifiable` Run spend cannot be confirmed right now. Retry in a moment. |

### Any other status

Any other status is the provider's own response, passed through byte for byte, error bodies included. The gateway adds `x-recovea-request-id`.

| Header | Description |
| --- | --- |
| `x-recovea-request-id` string required | The gateway's id for this request: `req_` and 32 hex characters. On every response the gateway sends, forwarded or refused. Quote it when you ask about a request. |

Generated from openapi.json, which the gateway renders from `relay/src/proxy.rs`, `relay/src/stream_usage.rs` at commit `4b1805b24ff8`.
