# API reference (/docs/api)

How to read the reference — base URLs, authentication, the error envelope, SSE conventions and where changes are announced.

The reference is generated from hand-authored OpenAPI 3.1 documents that mirror the code route by route. A parity test in the repo fails whenever a public route exists without documentation, or a documented route no longer exists, so what you read here is what the servers register.

## Three services, three base URLs [#three-services-three-base-urls]

| Service | Base URL | Reference | Auth |
| --- | --- | --- | --- |
| Gateway (inference + platform) | `https://ai.codai.ro` | [Gateway](./gateway/index.mdx) | `Authorization: Bearer codai_…` |
| Resolve (verified bug fixes) | `https://resolve.codai.ro` | [Resolve](./resolve.mdx) | same `codai_` key |
| Auth (OpenID Provider) | `https://auth.codai.ro` | [Auth](./auth.mdx) | OAuth 2.1 / OIDC |

Try-it panels in each operation send real requests from your browser. Paste a key you are happy to spend a few cents with; the panels never store it.

## Authentication [#authentication]

Every gateway and Resolve call carries a codai API key in the bearer header. Keys start with `codai_`, are shown once in the [hub](https://hub.codai.ro) and are revocable there. `/v1/messages` also accepts the Anthropic-style `x-api-key` header. Ephemeral tokens from `POST /v1/tokens` are accepted only by the realtime WebSocket — a raw key in a query string is refused.

Apps that sign users in obtain a scoped key through the OIDC [connect flow](../auth/connect-flow.mdx) instead of asking for one.

## The error envelope [#the-error-envelope]

The gateway answers every failure with one JSON shape:

```json
{
  "error": {
    "message": "Invalid request body",
    "type": "invalid_request_error",
    "code": "bad_request",
    "details": { "fieldErrors": { "messages": ["Required"] } }
  }
}
```

| Status | `code` | When |
| --- | --- | --- |
| 400 | `bad_request` | Body or query failed validation — `details` holds the field errors |
| 401 | `invalid_api_key` | Missing, revoked or paused key |
| 402 | `subscription_inactive` | The subscription behind the key is inactive |
| 403 | `forbidden` | Model allow-list, role or ownership check failed |
| 404 | `not_found` | No such resource, or it belongs to someone else |
| 409 | `lease_held`, `seq_conflict`, `task_not_confirmable`, `host_offline` | Optimistic-concurrency conflicts on sessions, tasks and hosts |
| 413 | `payload_too_large` | Request body over 12 MB |
| 429 | `rate_limit_exceeded`, `quota_exceeded` | Rate limit / spend cap, or task quota — read `Retry-After` |
| 502 | `upstream_error` | Every upstream lane failed after failover |
| 503 | `search_unavailable` | The search service behind `/v1/tools/search` is down |
| 500 | `internal_error` | Unexpected fault; already reported to us |

Resolve and Auth are different services with their own, simpler shapes: Resolve returns `{ "error": "<string>" }`, Auth returns the OAuth `{ "error", "error_description" }` pair. Each reference page documents its own.

## Streaming conventions [#streaming-conventions]

- SSE endpoints answer `text/event-stream`. Chat Completions, Messages and Responses use their wire format's framing; the usage chunk on a Chat Completions stream is always emitted, whatever `stream_options` says.
- codai's own streams (agents, sessions, hosts) use named `event:` frames documented on each operation, plus `: ping` comments as heartbeats where noted.
- No stream emits `id:` lines and `Last-Event-ID` is not read. Resume with the cursor the endpoint offers (`after` on sessions) or accept a full replay (agents).
- Cost headers on a stream read `x-codai-cost-estimate: pending`; the metered cost lands later on `GET /v1/receipt`.

## Versioning and changes [#versioning-and-changes]

The gateway is unversioned beyond the `/v1` prefix. Additive changes (new fields, new headers, new endpoints) ship without notice; anything that removes or renames is announced in the [changelog](https://codai.ro/changelog) first. Field names are returned exactly as the code sends them — a few objects mix `camelCase` and `snake_case`, and the reference reflects that rather than hiding it.
