# Router One API Documentation

Generated from the public OpenAPI schema.

Router One offers an **OpenAI-compatible** unified model API. Smart routing, automatic fallback, and cost controls let teams run LLM workloads in production safely, predictably, and economically.

> Calling LLMs directly is a black box. Calling LLMs through Router One gives you a ledger, a trace, and guardrails.



## Quick Start

Get started with the Router One API in three steps:

### 1. Get an API Key

Sign in to the [Router One console](https://router.one) and create an API key (format `sk-xxx`).

### 2. Send your first request

```bash
curl https://api.router.one/v1/chat/completions \
  -H "Authorization: Bearer sk-your-api-key" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "auto",
    "messages": [{"role": "user", "content": "Hello"}]
  }'
```

Set `model` to `auto` and the gateway classifies the request (rules plus a lightweight classifier) into a low, medium or high tier, then serves it with a model from that tier's server-managed list; an optional `X-Route-Session` header keeps a conversation on the same choice for a while. You can also pin a specific model such as `openai/gpt-5.5` or `anthropic/claude-sonnet-5` — copy IDs from the model catalog. A request that names a model is served by that model: a failing route (429, 5xx, timeout) is retried on another route for the same model, and routes with a sustained upstream-error rate are automatically moved to the back of the order until they recover.

### 3. Handle the response

```json
{
  "id": "chatcmpl-abc123",
  "object": "chat.completion",
  "model": "anthropic/claude-sonnet-4.6",
  "choices": [{
    "index": 0,
    "message": { "role": "assistant", "content": "Hello! How can I help you?" },
    "finish_reason": "stop"
  }],
  "usage": { "prompt_tokens": 9, "completion_tokens": 12, "total_tokens": 21 }
}
```

`model` in the response is the catalog id that actually served the request — a concrete id even when the request said `auto`.



## Authentication

All API requests carry a Bearer Token in the `Authorization` header:

```
Authorization: Bearer sk-your-api-key
```

API Keys are created and managed in the [Router One console](https://router.one). Each key carries its own maxSpend cap and optional expiry; rateLimit and tokenLimitTpm run at platform defaults and can be raised on request.



## Base URL

| Environment | URL |
|------|------|
| **Production** | `https://api.router.one` |



## Request Format

- All requests use **JSON** (`Content-Type: application/json`)
- The API is fully compatible with the **OpenAI Chat Completions** format — existing code only needs to swap `base_url`
- Both streaming (SSE) and non-streaming response modes are supported



## Error Handling

The API returns standard HTTP status codes; error responses contain a structured error object:

```json
{
  "error": {
    "message": "invalid api key",
    "type": "authentication_error",
    "code": "AUTH_INVALID_API_KEY",
    "request_id": "290dd478f91d8aec68f7535e871376eb"
  }
}
```

| Status | Meaning | What to do |
|--------|------|----------|
| `401` | Invalid or missing API key | Check the Authorization header |
| `402` | Insufficient balance or API key spend cap reached | Top up at /deposit, or raise this key's maxSpend — topping up does not lift a key cap |
| `429` | Rate limit or quota exceeded | Read `Retry-After` and back off; the `code` says which limit — `RATE_LIMIT_EXCEEDED` (requests/min), `TOKEN_QUOTA_EXCEEDED` (tokens/min or daily) or `SUBSCRIPTION_QUOTA_EXCEEDED` (plan daily quota); a 429 is never a budget problem |
| `500` | Internal server error | Retry shortly; contact support if it persists |

Error bodies carry a `request_id` — quote it when contacting support. On `/v1/messages` the same fields are wrapped in the Anthropic envelope, with `request_id` at the top level on gateway-layer errors such as 401 and 429: `{"type":"error","error":{…},"request_id":"…"}`.

### Error codes

`type` is one of `invalid_request_error`, `authentication_error`, `authorization_error`, `rate_limit_error`, `billing_error`, `api_error` and `service_unavailable`. `code` is machine-readable and UPPER_SNAKE_CASE:

| Code | Status | Meaning |
|------|------|------|
| `INVALID_REQUEST` | 400 | Malformed body, a model that is not available, or a model that is not served on this endpoint |
| `CONTENT_FILTERED` | 400 | Prompt rejected by content moderation (image and video endpoints) |
| `AUTH_INVALID_API_KEY` | 401 | Invalid or missing API key |
| `INSUFFICIENT_BALANCE` | 402 | Wallet balance exhausted |
| `API_KEY_SPEND_CAP_EXCEEDED` | 402 | This key's maxSpend is used up — a per-key budget cap, not a rate limit |
| `AUTH_FORBIDDEN` | 403 | The key may not perform this action |
| `RESOURCE_NOT_FOUND` | 404 | Unknown path or resource |
| `RATE_LIMIT_EXCEEDED` | 429 | Requests-per-minute limit hit |
| `TOKEN_QUOTA_EXCEEDED` | 429 | Tokens-per-minute or daily token quota hit |
| `SUBSCRIPTION_QUOTA_EXCEEDED` | 429 | Subscription plan daily quota hit |
| `INTERNAL_ERROR` | 500 | Gateway-side failure; retry shortly |
| `MODERATION_UNAVAILABLE` | 502 | Content moderation could not screen the prompt (image and video endpoints); retry shortly |
| `PROVIDER_UNAVAILABLE` | 503 / 504 | Upstream provider unavailable or timed out; retry later |



## Rate limits & response headers

Responses from the chat endpoints (`/v1/chat/completions`, `/v1/messages`, `/v1/responses`) carry the current limit state, on success as well as on `429`:

| Header | Meaning |
|--------|------|
| `RateLimit-Limit` / `RateLimit-Remaining` / `RateLimit-Reset` | Requests-per-minute limit, what is left, and seconds until the window resets |
| `X-RateLimit-Limit` / `X-RateLimit-Remaining` / `X-RateLimit-Reset` | The same three values under the `X-` spelling, for clients that read only that form |
| `X-TokenLimit-TPM` / `X-TokenLimit-TPM-Remaining` / `X-TokenLimit-TPM-Reset` | Tokens-per-minute ceiling, what is left, and seconds until reset |
| `X-TokenQuota-Day` / `X-TokenQuota-Day-Remaining` / `X-TokenQuota-Day-Reset` | Present when a daily token quota applies; the reset is a Unix timestamp in seconds |
| `X-Subscription-Quota-Day-Limit-Requests` / `-Limit-Tokens` / `-Remaining-Requests` / `-Remaining-Tokens` / `X-Subscription-Quota-Day-Reset` | Present on a subscription plan's daily-quota `429`; the reset is a Unix timestamp in seconds |
| `Retry-After` | Seconds to wait before retrying. Honor it instead of guessing a backoff |

Image, video and model-list endpoints are rate limited per key but do not return these headers.

## Base URLs

- `https://api.router.one` — Production
- OpenAI-compatible API / Codex CLI base URL: `https://api.router.one/v1`
- Claude Code / Anthropic-compatible endpoint: `https://api.router.one`

## Endpoint reference

### POST `/v1/chat/completions`

**Create Chat Completion**

- Operation ID: `createChatCompletion`
- Tags: Chat

Create a chat completion on the OpenAI Chat Completions-compatible endpoint: one base URL for 30+ models, streaming or not, `model: auto` for smart routing.

When `model` is `auto`, Router One selects from a server-owned candidate set under the active gateway policy.

#### Request body

- Required: Yes
- Content types: `application/json`

##### Fields

| Field | Type | Required | Description |
|---|---|---|---|
| `model` | string | Yes | Model ID. Set to `auto` to use the server-owned candidate set under gateway policy, or specify a model such as `openai/gpt-5.5` or `anthropic/claude-sonnet-5` (copy IDs from the model catalog). Most official bare names (for example `gpt-5.5` or `claude-sonnet-5`) are accepted as aliases of the catalog id; copy the catalog id from /models to be safe. Example: `auto` |
| `messages` | array<ChatMessage> | Yes | Chat messages, in chronological order. |
| `stream` | boolean | No | Whether to enable streaming response. When enabled, returns an SSE event stream. Default: `false` |
| `temperature` | number | No | Sampling temperature, range 0-2. Higher values (e.g. 0.8) make output more random; lower values (e.g. 0.2) make it more deterministic. Default: `1` |
| `max_tokens` | integer | No | Maximum number of tokens to generate. |
| `top_p` | number | No | Nucleus sampling parameter. The model considers tokens with the top `top_p` mass of the probability distribution. Default: `1` |
| `reasoning_effort` | string | No | Reasoning-effort hint such as `low`, `medium` or `high`. Forwarded to the model exactly as sent; Router One does not validate or remap the value, except on Claude Opus 5.5 (`claude-opus-5-5` or `anthropic/claude-opus-5.5`), where it is mapped to Anthropic's `output_config.effort`: `none` and `minimal` become `low`, `low` through `max` pass through, and other values are ignored. Support varies by model — a model without a reasoning-effort control ignores it or rejects the request with 400 invalid_request, so check the model page before relying on it. Example: `medium` |
| `stream_options` | object | No | Streaming response options. Only valid when `stream: true`. |
| `stream_options.include_usage` | boolean | No | Whether to include usage info in the final chunk of the streaming response. Default: `false` |
| `web_search_options` | object | No | Hosted web search, accepted on `google/gemini-3-flash` only: send `{}` (or the equivalent `tools: [{"type": "google_search"}]` with `tool_choice: "auto"`) and the model decides whether to search; the sources it grounded on come back as `url_citation` annotations (`choices[0].message.annotations`, or `choices[0].delta.annotations` when streaming), billed at the model's token rates. On every other model the field is forwarded as sent and the model's own validation applies. |
| `tools` | array<ChatTool> | No | Tool (function) declarations the model may call. Support varies by model — check the model's catalog entry before relying on it. On `google/gemini-3-flash` the array may also carry `{"type": "google_search"}` (hosted search; see `web_search_options`). |
| `tool_choice` | none \| auto \| required \| object | No | How the model picks a tool. `auto` lets the model decide, `none` disables tool calling, `required` forces some tool call, and an object naming one function forces that call. Exception: Claude Opus 5.5 (`claude-opus-5-5` or `anthropic/claude-opus-5.5`) does not accept forced tool use, so Router One sends `required` or a named function as `auto` for that model and it may answer in text; for JSON from that model use `output_config.format` on `/v1/messages`. |
| `response_format` | object | No | Response format hint forwarded to the model. `json_object` asks for a JSON reply; `json_schema` additionally sends a JSON Schema. Router One validates the request shape only — a `json_schema` request without `json_schema.name` or `json_schema.schema` is rejected with 400 invalid_request before reaching the model — and does not validate or repair the model's output. Schema enforcement is the model's; support varies by model, so test with your target model. For Claude ids this field is not a reliable way to get JSON — use `output_config.format` on `/v1/messages` instead. See the [structured outputs guide](https://router.one/llm-structured-outputs). |
| `response_format.type` | text \| json_object \| json_schema | Yes | `text` is the default. `json_object` asks for a JSON object; `json_schema` constrains the reply to `json_schema.schema` on models that honour it. |
| `response_format.json_schema` | object | No | Required when `type` is `json_schema`. Forwarded as sent, including `strict` and `description`. |

##### Examples

**Basic request**

```json
{
  "model": "auto",
  "messages": [
    {
      "role": "user",
      "content": "Hello"
    }
  ]
}
```

**With system prompt**

```json
{
  "model": "auto",
  "messages": [
    {
      "role": "system",
      "content": "You are a helpful assistant."
    },
    {
      "role": "user",
      "content": "Introduce Router One"
    }
  ],
  "temperature": 0.7,
  "max_tokens": 1024
}
```

**Streaming response**

```json
{
  "model": "auto",
  "stream": true,
  "messages": [
    {
      "role": "user",
      "content": "Write a short poem"
    }
  ]
}
```

**Structured output (json_schema)**

```json
{
  "model": "auto",
  "messages": [
    {
      "role": "user",
      "content": "Extract the city and date: meeting in Shanghai on 3 September."
    }
  ],
  "response_format": {
    "type": "json_schema",
    "json_schema": {
      "name": "meeting",
      "strict": true,
      "schema": {
        "type": "object",
        "properties": {
          "city": {
            "type": "string"
          },
          "date": {
            "type": "string"
          }
        },
        "required": [
          "city",
          "date"
        ],
        "additionalProperties": false
      }
    }
  }
}
```

#### Responses

| Status | Description | Schema |
|---|---|---|
| `200` | Successful chat completion. When `stream: false`, returns a JSON object; when `stream: true`, returns an SSE event stream. | ChatCompletionResponse |
| `400` | Invalid request — a model sent to an endpoint that does not serve it (the message says which path to use) or a malformed body, such as an incomplete response_format envelope | ErrorResponse |
| `401` | Authentication failure — invalid or missing API key | ErrorResponse |
| `402` | Insufficient balance or API key spend cap reached — billing_error, not a rate limit | ErrorResponse |
| `429` | Rate limit or quota exceeded — code RATE_LIMIT_EXCEEDED for requests per minute, TOKEN_QUOTA_EXCEEDED for the tokens-per-minute or daily token quota, SUBSCRIPTION_QUOTA_EXCEEDED for a subscription plan's daily quota; read Retry-After and back off. The X-RateLimit-Scope header names the limit: api_key (this key), subject (the account) or subject_model (the account's use of one model) are gateway limits, which can be raised on request via support@router.one; upstream_provider is the model vendor's limit | ErrorResponse |
| `500` | Internal server error | ErrorResponse |

##### Fields

| Field | Type | Required | Description |
|---|---|---|---|
| `id` | string | No | Unique identifier of the completion request |
| `object` | chat.completion | No | Object type, always `chat.completion` |
| `created` | integer | No | Creation Unix timestamp |
| `model` | string | No | ID of the model actually used |
| `choices` | array<object> | No |  |
| `usage` | object | No |  |
| `usage.prompt_tokens` | integer | No | Tokens consumed by input |
| `usage.completion_tokens` | integer | No | Tokens consumed by output |
| `usage.total_tokens` | integer | No | Total tokens consumed |

##### Examples

```json
{
  "id": "chatcmpl-abc123",
  "object": "chat.completion",
  "created": 1700000000,
  "model": "anthropic/claude-sonnet-4.6",
  "choices": [
    {
      "index": 0,
      "message": {
        "role": "assistant",
        "content": "Hello! How can I help you?"
      },
      "finish_reason": "stop"
    }
  ],
  "usage": {
    "prompt_tokens": 9,
    "completion_tokens": 12,
    "total_tokens": 21
  }
}
```

### POST `/v1/messages`

**Create Message**

- Operation ID: `createMessage`
- Tags: Chat

Create a Claude / Anthropic Messages compatible request — the shape Claude Code uses — for currently listed Claude-family models and DeepSeek ids. Streaming and non-streaming responses are supported; call other chat models via /v1/chat/completions. A model sent to an unsupported endpoint is rejected before any model runs: 400 invalid_request_error, model '<id>' must be called via /v1/chat/completions. Error bodies use the Anthropic envelope {"type":"error","error":{type,message,code},"request_id"} rather than the OpenAI {"error":{…}} shape used by the other endpoints.

Function tools are forwarded as sent (`tools` with `input_schema`, `tool_choice`; `tool_use` / `tool_result` blocks round-trip); the one exception is Claude Opus 5.5 (`claude-opus-5-5` or `anthropic/claude-opus-5.5`), which does not accept forced tool use, so a `tool_choice` of `any` or `tool` is sent as `auto` for that model and it may answer in text instead of calling the tool. Anthropic server tools: `web_search` and `code_execution` — including its `bash_*` / `text_editor_*` sub-tools and container reuse across turns — are accepted and metered at the model's standard token rate (container time is not passed through per call); `web_fetch`, `mcp_toolset` and `mcp_servers` are rejected before any model runs with 400 invalid_request_error.

#### Request body

- Required: Yes
- Content types: `application/json`

##### Fields

| Field | Type | Required | Description |
|---|---|---|---|
| `model` | string | Yes | Model ID. Set to `auto` for Router One routing, or specify a concrete model. Most official bare names (for example `gpt-5.5` or `claude-sonnet-5`) are accepted as aliases of the catalog id; copy the catalog id from /models to be safe. Example: `auto` |
| `messages` | array<AnthropicMessage> | Yes | Conversation messages in the Claude / Anthropic Messages format. |
| `system` | string | No | Optional system prompt. |
| `max_tokens` | integer | Yes | Maximum number of output tokens. |
| `stream` | boolean | No | Whether to enable streaming response. Default: `false` |
| `temperature` | number | No | Sampling temperature, range 0-2. Default: `1` |
| `tools` | array<object> | No | Anthropic tool declarations, forwarded as sent. Function tools carry `input_schema`; the server tools `web_search` and `code_execution` are accepted, while `web_fetch` and `mcp_toolset` are rejected with 400 invalid_request_error. |
| `tool_choice` | object | No | How the model may use the declared tools (`auto`, `any`, `tool`, `none`), forwarded as sent — except for Claude Opus 5.5 (`claude-opus-5-5` or `anthropic/claude-opus-5.5`), which does not accept forced tool use: `any` and `tool` are sent as `auto` for that model, so check `stop_reason` and the content block types. |

##### Examples

**Basic request**

```json
{
  "model": "auto",
  "max_tokens": 1024,
  "messages": [
    {
      "role": "user",
      "content": "Hello"
    }
  ]
}
```

**Streaming response**

```json
{
  "model": "auto",
  "max_tokens": 1024,
  "stream": true,
  "messages": [
    {
      "role": "user",
      "content": "Write a short product intro"
    }
  ]
}
```

#### Responses

| Status | Description | Schema |
|---|---|---|
| `200` | Successful Messages API compatible response. | MessageResponse |
| `400` | Invalid request — a model sent to an unsupported endpoint (call it via /v1/chat/completions), a malformed body, or a rejected server tool (web_fetch, mcp_toolset, mcp_servers) | AnthropicErrorResponse |
| `401` | Authentication failure — invalid or missing API key | AnthropicErrorResponse |
| `402` | Insufficient balance or API key spend cap reached — billing_error, not a rate limit | AnthropicErrorResponse |
| `429` | Rate limit or quota exceeded — code RATE_LIMIT_EXCEEDED for requests per minute, TOKEN_QUOTA_EXCEEDED for the tokens-per-minute or daily token quota, SUBSCRIPTION_QUOTA_EXCEEDED for a subscription plan's daily quota; read Retry-After and back off. The X-RateLimit-Scope header names the limit: api_key (this key), subject (the account) or subject_model (the account's use of one model) are gateway limits, which can be raised on request via support@router.one; upstream_provider is the model vendor's limit | AnthropicErrorResponse |
| `500` | Internal server error | AnthropicErrorResponse |

##### Fields

| Field | Type | Required | Description |
|---|---|---|---|
| `id` | string | No | Message ID. |
| `type` | message | No | Object type. |
| `role` | assistant | No | Response role. |
| `model` | string | No | ID of the model actually used. |
| `content` | array<AnthropicContentBlock> | No |  |
| `stop_reason` | string | No | Stop reason. |
| `usage` | object | No |  |
| `usage.input_tokens` | integer | No | Input tokens. |
| `usage.output_tokens` | integer | No | Output tokens. |

##### Examples

```json
{
  "id": "msg_abc123",
  "type": "message",
  "role": "assistant",
  "model": "anthropic/claude-sonnet-5",
  "content": [
    {
      "type": "text",
      "text": "Hello! How can I help you?"
    }
  ],
  "stop_reason": "end_turn",
  "usage": {
    "input_tokens": 9,
    "output_tokens": 12
  }
}
```

### POST `/v1/responses`

**Create Response**

- Operation ID: `createResponse`
- Tags: Chat

Create an OpenAI Responses API compatible request — the shape Codex CLI uses — served natively for currently listed GPT-family models, DeepSeek ids and Grok chat models. Text, instructions, streaming and function tools work as sent; custom tools, previous_response_id / conversation / prompt, hosted tools (file_search, code_interpreter, computer_use, mcp, web_search), file_id / file_url input parts and service_tier values are accepted on natively served Responses models and billed at the model's standard rate. Rejected with 400 invalid_request: the image_generation tool and image_generation_call items (use /v1/images/generations) and background: true. Claude-family model IDs are not served here: pinning one is rejected before any model runs with 400 invalid_request_error and the message model 'anthropic/claude-opus-5' must be called via /v1/messages or /v1/chat/completions; with model: auto the candidate set excludes Claude and another family serves the request.

#### Request body

- Required: Yes
- Content types: `application/json`

##### Fields

| Field | Type | Required | Description |
|---|---|---|---|
| `model` | string | Yes | Model ID. Set to `auto` for Router One routing. Most official bare names (for example `gpt-5.5` or `claude-sonnet-5`) are accepted as aliases of the catalog id; copy the catalog id from /models to be safe. Example: `auto` |
| `input` | string \| array<ResponseInputItem> | Yes |  |
| `instructions` | string | No | Optional system instructions. |
| `stream` | boolean | No | Whether to enable streaming response. Default: `false` |
| `temperature` | number | No | Sampling temperature, range 0-2. Default: `1` |
| `max_output_tokens` | integer | No | Maximum number of output tokens. |
| `tools` | array<object> | No | Tool declarations. Function tools are the Codex default. Custom tools and hosted tools (file_search, code_interpreter, computer_use, mcp, web_search) are accepted on models served natively over the Responses wire format and billed at the model's standard rate. The image_generation tool is rejected with 400 invalid_request — use /v1/images/generations. |
| `previous_response_id` | string | No | Continue from a prior response (server-side context reference). Accepted on models served natively over the Responses wire format; other models return 400 invalid_request. |
| `conversation` | string \| object | No | Conversation reference (id string or object). Same model rule as previous_response_id. |
| `prompt` | object | No | Prompt template reference (id plus variables). Same model rule as previous_response_id. |
| `prompt.id` | string | No | Prompt template id. |
| `prompt.variables` | object | No | Template variables. |
| `service_tier` | string | No | Forwarded as sent; every value is accepted and billed at the model's standard rate. |
| `store` | boolean | No | Forwarded as sent. |
| `background` | boolean | No | Not supported — background: true returns 400 invalid_request. The gateway does not run detached responses and has no GET /v1/responses/{id}; the request must complete within the HTTP connection. Default: `false` |

##### Examples

**Basic request**

```json
{
  "model": "openai/gpt-5.5",
  "input": "Introduce Router One in one sentence"
}
```

**With instructions**

```json
{
  "model": "openai/gpt-5.5",
  "instructions": "You are a concise technical documentation assistant.",
  "input": "Explain smart routing."
}
```

#### Responses

| Status | Description | Schema |
|---|---|---|
| `200` | Successful Responses API compatible response. | ResponsesResponse |
| `400` | Invalid request — an unsupported Responses feature (background, the image_generation tool) or a model this endpoint does not serve | ErrorResponse |
| `401` | Authentication failure — invalid or missing API key | ErrorResponse |
| `402` | Insufficient balance or API key spend cap reached — billing_error, not a rate limit | ErrorResponse |
| `429` | Rate limit or quota exceeded — code RATE_LIMIT_EXCEEDED for requests per minute, TOKEN_QUOTA_EXCEEDED for the tokens-per-minute or daily token quota, SUBSCRIPTION_QUOTA_EXCEEDED for a subscription plan's daily quota; read Retry-After and back off. The X-RateLimit-Scope header names the limit: api_key (this key), subject (the account) or subject_model (the account's use of one model) are gateway limits, which can be raised on request via support@router.one; upstream_provider is the model vendor's limit | ErrorResponse |
| `500` | Internal server error | ErrorResponse |

##### Fields

| Field | Type | Required | Description |
|---|---|---|---|
| `id` | string | No | Response ID. |
| `object` | response | No | Object type. |
| `created_at` | integer | No | Creation Unix timestamp. |
| `status` | string | No | Response status. |
| `model` | string | No | ID of the model actually used. |
| `output_text` | string | No | Aggregated text output. |
| `output` | array<ResponseInputItem> | No | Raw output items. |
| `usage` | object | No |  |
| `usage.input_tokens` | integer | No | Input tokens. |
| `usage.output_tokens` | integer | No | Output tokens. |
| `usage.total_tokens` | integer | No | Total tokens. |

##### Examples

```json
{
  "id": "resp_abc123",
  "object": "response",
  "created_at": 1700000000,
  "status": "completed",
  "model": "openai/gpt-5.5",
  "output_text": "Router One is a unified LLM API gateway.",
  "output": [
    {
      "role": "assistant",
      "content": [
        {
          "type": "output_text",
          "text": "Router One is a unified LLM API gateway."
        }
      ]
    }
  ],
  "usage": {
    "input_tokens": 12,
    "output_tokens": 10,
    "total_tokens": 22
  }
}
```

### GET `/v1/models`

**List Models**

- Operation ID: `listModels`
- Tags: Models

List the models this API key can call. OpenAI-compatible clients and coding CLIs (Codex CLI, Claude Code) fetch this once per session. Each entry in `data` carries the model `id` to send in a request plus its capabilities, context window and posted price fields; the extra `models` array repeats the same catalog with additional fields consumed by Codex CLI. Responses carry an `ETag` — send it back as `If-None-Match` and an unchanged catalog answers `304 Not Modified` with no body. Model IDs are case-sensitive; copy them from the response or from the model catalog.

#### Parameters

| Field | In | Type | Required | Description |
|---|---|---|---|---|
| `If-None-Match` | header | string | No | The `ETag` from a previous response. A match returns 304 Not Modified. |

#### Request body

This endpoint does not define a JSON request body.

#### Responses

| Status | Description | Schema |
|---|---|---|
| `200` | The model catalog for this key | object |
| `304` | Catalog unchanged since the ETag you sent; no body | None |
| `401` | Authentication failure — invalid or missing API key | ErrorResponse |

##### Fields

| Field | Type | Required | Description |
|---|---|---|---|
| `object` | list | No | Always `list`. |
| `data` | array<object> | No | One entry per model this key can call. |

##### Examples

```json
{
  "object": "list",
  "data": [
    {
      "id": "anthropic/claude-sonnet-5",
      "object": "model",
      "capabilities": [
        "chat",
        "streaming",
        "tool_calling",
        "vision"
      ],
      "max_tokens": 1048576,
      "category": "text",
      "pricing_mode": "token"
    }
  ]
}
```

### GET `/v1/balance`

**Get Balance**

- Operation ID: `getBalance`
- Tags: Account

Return the prepaid balance of the account that owns this API key, in USD. Built for server-side monitoring: poll it and top up before the balance is exhausted. Values are live (`Cache-Control: no-store`). Alert on `balance` — the amount you can spend right now, gift credit included. While requests are in flight the gateway holds an estimated cost in `reserved_balance`; when each request settles, the unused part of the hold returns to `balance`, so `balance` can dip briefly under concurrency while `total_balance` stays steady. This endpoint has its own rate limit of 60 requests per minute per key and does not consume your inference rate limit.

#### Request body

This endpoint does not define a JSON request body.

#### Responses

| Status | Description | Schema |
|---|---|---|
| `200` | The account balance | object |
| `401` | Authentication failure — invalid or missing API key | ErrorResponse |
| `429` | Rate limit exceeded — more than 60 requests per minute on this key; back off and retry | ErrorResponse |

##### Fields

| Field | Type | Required | Description |
|---|---|---|---|
| `object` | balance | Yes | Always `balance`. |
| `currency` | USD | Yes | Always `USD`. |
| `balance` | number | Yes | Spendable balance right now, in USD. Use this field for low-balance alerts. |
| `reserved_balance` | number | Yes | Amount currently held for in-flight requests, in USD. The unused part is released back to `balance` when each request settles. |
| `total_balance` | number | Yes | `balance` + `reserved_balance`, in USD. Use this for reconciliation. |

##### Examples

```json
{
  "object": "balance",
  "currency": "USD",
  "balance": 12.345678,
  "reserved_balance": 0.5,
  "total_balance": 12.845678
}
```

### POST `/v1/images/generations`

**Create Image Generation**

- Operation ID: `createImageGeneration`
- Tags: Images

Generate images from a text prompt. This is a synchronous endpoint; the request returns once generation completes. Typical generation takes 5-30 seconds — set a client HTTP timeout of at least 60 seconds.

The `data` array length in the response equals the number of images actually generated, and is what you are billed for. When `response_format` is `url`, the returned image URLs have an expiration (typically 1 hour); download or re-host them if you need them long-term.

#### Request body

- Required: Yes
- Content types: `application/json`

##### Fields

| Field | Type | Required | Description |
|---|---|---|---|
| `model` | string | Yes | Model ID. Choose a model that supports image generation; check the console model marketplace. Example: `gpt-image-2` |
| `prompt` | string | Yes | Text prompt for image generation. More specific and visually evocative prompts usually produce better results. Recommended length under 4000 characters. |
| `n` | integer | No | Number of images to generate in this request. Billed per image. Default: `1` |
| `size` | string | No | Image size as `WIDTHxHEIGHT` in pixels, e.g. `1024x1024`. Passed to the model as sent — Router One does not validate it, accepted sizes differ per model, and some models ignore it. A size the model rejects returns 400 `invalid request (upstream rejected with status 400)`. See the model page for its supported sizes. Example: `1024x1024` |
| `quality` | string | No | Quality hint passed to the model as sent. Router One does not validate it; accepted values, if any, differ per model, and models without a quality control ignore it. A value the model rejects returns 400 `invalid request (upstream rejected with status 400)`. Billing is per image at the model's catalog unit price. For Grok Imagine, the higher-fidelity tier is the separate model `grok-imagine-image-quality` rather than a quality value. |
| `response_format` | url \| b64_json | No | How the image is returned.<br>- `url` (default): a CDN URL valid for about 1 hour; download or re-host as needed<br>- `b64_json`: base64-encoded image bytes in the response; larger body, no extra download Default: `url` |

##### Examples

**Basic request**

```json
{
  "model": "gpt-image-2",
  "prompt": "An orange kitten wearing an astronaut helmet, floating in starry space, cinematic lighting"
}
```

**With size and output format**

```json
{
  "model": "gpt-image-2",
  "prompt": "Minimalist poster — black coffee cup on a yellow background",
  "n": 2,
  "size": "1024x1024",
  "response_format": "b64_json"
}
```

#### Responses

| Status | Description | Schema |
|---|---|---|
| `200` | Generation succeeded | ImageGenerationResponse |
| `400` | Invalid request — model not available, invalid field format, missing prompt, or prompt rejected by content moderation (code CONTENT_FILTERED) | ErrorResponse |
| `401` | Authentication failure — invalid or missing API key | ErrorResponse |
| `402` | Insufficient balance or API key spend cap reached — billing_error, not a rate limit | ErrorResponse |
| `429` | Rate limit exceeded | ErrorResponse |
| `500` | Internal server error | ErrorResponse |
| `502` | Content moderation unavailable — the prompt could not be screened, so the request is refused rather than run; retry shortly | ErrorResponse |
| `503` | Upstream provider unavailable — auth, quota or 5xx failures on the provider side are normalized to this status; retry later | ErrorResponse |

##### Fields

| Field | Type | Required | Description |
|---|---|---|---|
| `created` | integer | No | Unix timestamp (seconds) when generation completed |
| `data` | array<ImageData> | No | List of generated images; length equals the number actually generated. |

##### Examples

```json
{
  "created": 1700000000,
  "data": [
    {
      "url": "https://cdn.router.one/img/abc123.png",
      "revised_prompt": "An orange kitten wearing an astronaut helmet, floating in starry space, cinematic lighting"
    }
  ]
}
```

### POST `/v1/images/edits`

**Create Image Edit**

- Operation ID: `createImageEdit`
- Tags: Images

Transform reference images with a text instruction (image-to-image): style transfer, background replacement, subject changes, retouching, and similar edits. Like image generation this is a synchronous endpoint; typical generation takes 5-30 seconds — set a client HTTP timeout of at least 60 seconds.

Unlike the JSON endpoints, requests use `multipart/form-data` because the reference images are uploaded as files. Send one `image` field, or repeat it (`image[]` is also accepted) to pass multiple reference images when the chosen model supports that. Each file must be an image and is limited to 25 MB.

The response format and billing follow image generation: the `data` array length equals the number of images actually generated, and `url` results expire after about 1 hour — download or re-host them if you need them long-term.

#### Request body

- Required: Yes
- Content types: `multipart/form-data`

##### Fields

| Field | Type | Required | Description |
|---|---|---|---|
| `model` | string | Yes | Model ID. Choose a model that supports image-to-image editing; check the console model marketplace. Example: `gemini-3.1-flash-image-preview` |
| `prompt` | string | Yes | Text instruction describing how to transform the reference image(s) — e.g. "turn this photo into a watercolor painting" or "replace the background with a sunset beach". Recommended length under 4000 characters. |
| `image` | string (binary) | Yes | Reference image file (PNG / JPEG / WebP, etc.). Repeat the `image` field to upload multiple reference images for models that support it (`image[]` is also accepted). Up to 25 MB per file. |
| `n` | integer | No | Number of images to generate in this request. Billed per image. Default: `1` |
| `size` | string | No | Output image size as `WIDTHxHEIGHT` in pixels, e.g. `1024x1024`. Passed to the model as sent — Router One does not validate it, accepted sizes differ per model, and some models ignore it. A size the model rejects returns 400 `invalid request (upstream rejected with status 400)`. See the model page for its supported sizes. Example: `1024x1024` |
| `quality` | string | No | Quality hint passed to the model as sent. Router One does not validate it; accepted values, if any, differ per model, and models without a quality control ignore it. A value the model rejects returns 400 `invalid request (upstream rejected with status 400)`. Billing is per image at the model's catalog unit price. For Grok Imagine, the higher-fidelity tier is the separate model `grok-imagine-image-quality` rather than a quality value. |
| `response_format` | url \| b64_json | No | How the image is returned.<br>- `url` (default): a CDN URL valid for about 1 hour; download or re-host as needed<br>- `b64_json`: base64-encoded image bytes in the response; larger body, no extra download Default: `url` |

#### Responses

| Status | Description | Schema |
|---|---|---|
| `200` | Edit succeeded | ImageGenerationResponse |
| `400` | Invalid request — missing image, prompt, or model; body not multipart/form-data; the model does not support image-to-image editing; or prompt rejected by content moderation (code CONTENT_FILTERED) | ErrorResponse |
| `401` | Authentication failure — invalid or missing API key | ErrorResponse |
| `402` | Insufficient balance or API key spend cap reached — billing_error, not a rate limit | ErrorResponse |
| `429` | Rate limit exceeded | ErrorResponse |
| `500` | Internal server error | ErrorResponse |
| `502` | Content moderation unavailable — the prompt could not be screened, so the request is refused rather than run; retry shortly | ErrorResponse |
| `503` | Upstream provider unavailable — auth, quota or 5xx failures on the provider side are normalized to this status; retry later | ErrorResponse |

##### Fields

| Field | Type | Required | Description |
|---|---|---|---|
| `created` | integer | No | Unix timestamp (seconds) when generation completed |
| `data` | array<ImageData> | No | List of generated images; length equals the number actually generated. |

##### Examples

```json
{
  "created": 1700000000,
  "data": [
    {
      "url": "https://cdn.router.one/img/def456.png"
    }
  ]
}
```

### POST `/v1/videos/generations`

**Submit Video Generation**

- Operation ID: `submitVideoGeneration`
- Tags: Videos

Submit an async video generation task — returns `202 Accepted` with a `task_id` to poll; generation takes 30 seconds to several minutes depending on the model. The flow:

1. Call this endpoint to submit the task; on success, returns `202 Accepted` with a `task_id`.
2. Use the `task_id` with `GET /v1/videos/generations/{task_id}` to poll the status.
3. When `status` becomes `completed`, read the video `url` from the response.

**Recommended**: poll at intervals of at least 3 seconds; do not set an HTTP timeout for the whole generation flow — only set short timeouts (e.g. 30 s) for individual submit/poll requests.

**Clip length and resolution are fixed per model** and priced per clip; `duration` / `size` in the request body are ignored. The current lineup is on the [video generation page](https://router.one/veo-api-china).

**Reference image**: `image_url` is required by image-to-video models (exactly one image) and rejected by text-to-video models. It accepts an HTTP(S) URL up to 20 MB; after submission Router One downloads and re-hosts it securely, so even short-lived user-uploaded URLs work.

#### Request body

- Required: Yes
- Content types: `application/json`

##### Fields

| Field | Type | Required | Description |
|---|---|---|---|
| `model` | string | Yes | Model ID. Choose a model that supports video generation; check the console model marketplace. Example: `viduq3-turbo` |
| `prompt` | string | Yes | Text prompt for video generation, describing scene content, camera motion, style, etc. |
| `duration` | integer | No | Accepted but ignored by every currently listed video model: each model produces one fixed clip length (see the video generation page). The field is kept for forward compatibility. |
| `size` | string | No | Accepted but ignored by every currently listed video model: resolution is fixed per model and priced per clip. Kept for forward compatibility. Example: `720p` |
| `aspect_ratio` | 16:9 \| 9:16 \| 1:1 | No | Video aspect ratio. `16:9` for landscape, `9:16` for portrait short video, `1:1` for social-style square. Whether it is honored is decided per video model; no video model is currently listed, so check the model page before relying on it. |
| `negative_prompt` | string | No | Negative prompt — elements to avoid in the output. Optional; currently ignored by every listed video model. |
| `image_url` | string (uri) | No | Reference image URL. Required by image-to-video models (exactly one image); text-to-video models reject it with 400 `<model> is text-to-video only: image_url is not supported`. Must be HTTP(S) reachable; up to 20 MB. Router One downloads and securely re-hosts it. |
| `image_urls` | array<string (uri)> | No | Multi-reference image URLs. No currently listed model accepts more than one reference image — sending several returns 400 `<model> accepts exactly one image_url`. Kept for forward compatibility; same per-image constraints as `image_url`. |
| `input_reference` | string | No | Model-specific reference input identifier. Only used when the selected model explicitly requires it; it is treated as a reference image, so the same model rules as `image_url` apply. |

##### Examples

**Text-to-video**

```json
{
  "model": "viduq3-turbo",
  "prompt": "Waves lapping at the shore, dusk sunlight glittering on the water, slow motion",
  "aspect_ratio": "16:9"
}
```

**Image-to-video (one reference image)**

```json
{
  "model": "wan2.6-i2v",
  "prompt": "Camera slowly pushes in; the person in frame turns their head slightly",
  "image_url": "https://example.com/portrait.jpg"
}
```

#### Responses

| Status | Description | Schema |
|---|---|---|
| `202` | Task accepted; queued for generation | VideoSubmitResponse |
| `400` | Invalid request — model not available, reference image could not be fetched, the model rejects the reference-image shape (text-to-video models reject image_url; image-to-video models require exactly one), or prompt rejected by content moderation (code CONTENT_FILTERED) | ErrorResponse |
| `401` | Authentication failure — invalid or missing API key | ErrorResponse |
| `402` | Insufficient balance or API key spend cap reached — billing_error, not a rate limit | ErrorResponse |
| `429` | Rate limit exceeded | ErrorResponse |
| `502` | Gateway-side failure before or after the upstream call — content moderation unavailable (code MODERATION_UNAVAILABLE), or the upstream returned an empty response | ErrorResponse |
| `503` | Upstream provider unavailable — auth, quota or 5xx failures on the provider side are normalized to this status; retry later | ErrorResponse |
| `504` | Upstream timeout while submitting the task | ErrorResponse |

### GET `/v1/videos/generations/{task_id}`

**Get Video Generation Status**

- Operation ID: `getVideoGeneration`
- Tags: Videos

Poll a video generation task by `task_id` — `status` is `pending`, `processing`, `completed` (with the video `url`) or `failed` (with an `error`); poll at least 3 s apart.

- `pending` — task accepted, not yet started
- `processing` — generation in progress (`progress` is omitted by the currently listed models)
- `completed` — generation complete; read `url` for the video file
- `failed` — generation failed; read `error` for the reason

Poll at intervals of at least 3 seconds. Video file URLs have an expiration (typically 24 hours); download or re-host them if you need them long-term.

#### Parameters

| Field | In | Type | Required | Description |
|---|---|---|---|---|
| `task_id` | path | string | Yes | The task identifier returned by the submit endpoint. Treat as an opaque string and pass it back as-is — do not parse its internal structure. |

#### Request body

This endpoint does not define a JSON request body.

#### Responses

| Status | Description | Schema |
|---|---|---|
| `200` | Query succeeded | VideoStatusResponse |
| `401` | Authentication failure — invalid or missing API key | ErrorResponse |
| `404` | Task not found — unknown, expired, malformed, or submitted by another account (all return 404) | ErrorResponse |
| `503` | Upstream provider unavailable while polling; retry after the poll interval | ErrorResponse |
| `504` | Upstream timeout while polling | ErrorResponse |

##### Fields

| Field | Type | Required | Description |
|---|---|---|---|
| `task_id` | string | Yes | Task identifier; matches the `task_id` returned when submitting. |
| `status` | pending \| processing \| completed \| failed | Yes | Task status.<br>- `pending`: accepted, not yet started<br>- `processing`: generating (`progress` is omitted by the currently listed models)<br>- `completed`: complete; read `url` for the video<br>- `failed`: failed; read `error` for the reason |
| `url` | string | No | Generated video URL. Only returned when `status=completed`. The URL is typically valid for 24 hours — download or re-host promptly. |
| `error` | string | No | Human-readable failure description. Only returned when `status=failed`. |
| `progress` | integer | No | Generation progress percentage (0-100); some models may not return this. |

##### Examples

**Generating**

```json
{
  "task_id": "v_8f3a92c1d4e74b6ea0b5f1d29c7e8a01",
  "status": "processing"
}
```

**Completed**

```json
{
  "task_id": "v_8f3a92c1d4e74b6ea0b5f1d29c7e8a01",
  "status": "completed",
  "url": "https://cdn.router.one/video/abc123.mp4"
}
```

**Failed**

```json
{
  "task_id": "v_8f3a92c1d4e74b6ea0b5f1d29c7e8a01",
  "status": "failed",
  "error": "Content policy violation"
}
```

## Trust and methodology

- [Methodology](https://router.one/methodology) — how every public number on the site is measured.
- [Smart routing methodology](https://router.one/routing-methodology) — exact-model routing, same-model failover triggers, demotion of failing routes, and model="auto" tiers.
- [Pricing methodology](https://router.one/pricing-methodology) — no hidden markup on the pay-as-you-go token line; FX/channel fees shown at checkout.
- [Data retention](https://router.one/data-retention) — what Router One stores, what it does not store, and how long logs live.
- [Security](https://router.one/security) — transport security, API key handling, and upstream isolation.
- [SLA](https://router.one/sla) — availability language, fallback behavior, and enterprise contract scope.
- [China latency benchmark](https://router.one/benchmarks/china-latency) — public p50 and timeout snapshots for China access.

## Longer reference

- https://router.one/llms-full.txt

Last generated dynamically per request. When this Markdown page and the HTML docs differ, the OpenAPI schema and HTML docs are the source of truth.
