{"openapi":"3.1.0","info":{"title":"Router One API","description":"# Router One API\n\nRouter 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.\n\n> Calling LLMs directly is a black box. Calling LLMs through Router One gives you a ledger, a trace, and guardrails.\n\n---\n\n## Quick Start\n\nGet started with the Router One API in three steps:\n\n### 1. Get an API Key\n\nSign in to the [Router One console](https://router.one) and create an API key (format `sk-xxx`).\n\n### 2. Send your first request\n\n```bash\ncurl https://api.router.one/v1/chat/completions \\\n  -H \"Authorization: Bearer sk-your-api-key\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"model\": \"auto\",\n    \"messages\": [{\"role\": \"user\", \"content\": \"Hello\"}]\n  }'\n```\n\nSet `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.\n\n### 3. Handle the response\n\n```json\n{\n  \"id\": \"chatcmpl-abc123\",\n  \"object\": \"chat.completion\",\n  \"model\": \"anthropic/claude-sonnet-4.6\",\n  \"choices\": [{\n    \"index\": 0,\n    \"message\": { \"role\": \"assistant\", \"content\": \"Hello! How can I help you?\" },\n    \"finish_reason\": \"stop\"\n  }],\n  \"usage\": { \"prompt_tokens\": 9, \"completion_tokens\": 12, \"total_tokens\": 21 }\n}\n```\n\n`model` in the response is the catalog id that actually served the request — a concrete id even when the request said `auto`.\n\n---\n\n## Authentication\n\nAll API requests carry a Bearer Token in the `Authorization` header:\n\n```\nAuthorization: Bearer sk-your-api-key\n```\n\nAPI 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.\n\n---\n\n## Base URL\n\n| Environment | URL |\n|------|------|\n| **Production** | `https://api.router.one` |\n\n---\n\n## Request Format\n\n- All requests use **JSON** (`Content-Type: application/json`)\n- The API is fully compatible with the **OpenAI Chat Completions** format — existing code only needs to swap `base_url`\n- Both streaming (SSE) and non-streaming response modes are supported\n\n---\n\n## Error Handling\n\nThe API returns standard HTTP status codes; error responses contain a structured error object:\n\n```json\n{\n  \"error\": {\n    \"message\": \"invalid api key\",\n    \"type\": \"authentication_error\",\n    \"code\": \"AUTH_INVALID_API_KEY\",\n    \"request_id\": \"290dd478f91d8aec68f7535e871376eb\"\n  }\n}\n```\n\n| Status | Meaning | What to do |\n|--------|------|----------|\n| `401` | Invalid or missing API key | Check the Authorization header |\n| `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 |\n| `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 |\n| `500` | Internal server error | Retry shortly; contact support if it persists |\n\nError 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\":\"…\"}`.\n\n### Error codes\n\n`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:\n\n| Code | Status | Meaning |\n|------|------|------|\n| `INVALID_REQUEST` | 400 | Malformed body, a model that is not available, or a model that is not served on this endpoint |\n| `CONTENT_FILTERED` | 400 | Prompt rejected by content moderation (image and video endpoints) |\n| `AUTH_INVALID_API_KEY` | 401 | Invalid or missing API key |\n| `INSUFFICIENT_BALANCE` | 402 | Wallet balance exhausted |\n| `API_KEY_SPEND_CAP_EXCEEDED` | 402 | This key's maxSpend is used up — a per-key budget cap, not a rate limit |\n| `AUTH_FORBIDDEN` | 403 | The key may not perform this action |\n| `RESOURCE_NOT_FOUND` | 404 | Unknown path or resource |\n| `RATE_LIMIT_EXCEEDED` | 429 | Requests-per-minute limit hit |\n| `TOKEN_QUOTA_EXCEEDED` | 429 | Tokens-per-minute or daily token quota hit |\n| `SUBSCRIPTION_QUOTA_EXCEEDED` | 429 | Subscription plan daily quota hit |\n| `INTERNAL_ERROR` | 500 | Gateway-side failure; retry shortly |\n| `MODERATION_UNAVAILABLE` | 502 | Content moderation could not screen the prompt (image and video endpoints); retry shortly |\n| `PROVIDER_UNAVAILABLE` | 503 / 504 | Upstream provider unavailable or timed out; retry later |\n\n---\n\n## Rate limits & response headers\n\nResponses from the chat endpoints (`/v1/chat/completions`, `/v1/messages`, `/v1/responses`) carry the current limit state, on success as well as on `429`:\n\n| Header | Meaning |\n|--------|------|\n| `RateLimit-Limit` / `RateLimit-Remaining` / `RateLimit-Reset` | Requests-per-minute limit, what is left, and seconds until the window resets |\n| `X-RateLimit-Limit` / `X-RateLimit-Remaining` / `X-RateLimit-Reset` | The same three values under the `X-` spelling, for clients that read only that form |\n| `X-TokenLimit-TPM` / `X-TokenLimit-TPM-Remaining` / `X-TokenLimit-TPM-Reset` | Tokens-per-minute ceiling, what is left, and seconds until reset |\n| `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 |\n| `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 |\n| `Retry-After` | Seconds to wait before retrying. Honor it instead of guessing a backoff |\n\nImage, video and model-list endpoints are rate limited per key but do not return these headers.","version":"1.0.0","contact":{"name":"Router One","url":"https://router.one"}},"servers":[{"url":"https://api.router.one","description":"Production"}],"tags":[{"name":"Chat","description":"Conversation and text generation endpoints. Supports OpenAI Chat Completions, OpenAI Responses, and Claude / Anthropic Messages compatible requests. Set `model` to `auto` to enable smart routing."},{"name":"Models","description":"Model catalog endpoint. Returns the model IDs this key may call, with their capabilities and posted prices — the same catalog the [Router One console](https://router.one) model marketplace shows."},{"name":"Account","description":"Account endpoints. Check the prepaid balance of the account that owns this API key — built for server-side balance monitoring, so you can top up before the balance runs out."},{"name":"Images","description":"Image generation and image editing endpoints. Generate images from text prompts, or transform reference images with text instructions (image-to-image); optionally return URL or base64. All image models are billed per generated image.\n\nThe model marketplace in the [Router One console](https://router.one) lists which models currently support image generation and image-to-image editing."},{"name":"Videos","description":"Video generation endpoint. Video generation can take a while (tens of seconds to several minutes), so it uses an async task pattern: call the submit endpoint to get a `task_id`, then poll the status endpoint for progress and the final result URL.\n\nSupports text-to-video and image-to-video. Available models and parameter ranges are listed in the console model marketplace."}],"security":[{"BearerAuth":[]}],"paths":{"/v1/chat/completions":{"post":{"operationId":"createChatCompletion","summary":"Create Chat Completion","description":"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.\n\nWhen `model` is `auto`, Router One selects from a server-owned candidate set under the active gateway policy.\n\nA long `stream: false` call is held open (as of 2026-10-09): after about 25 seconds the gateway commits HTTP 200 and writes JSON leading whitespace until the completion is ready, so a failure after that point arrives as HTTP 200 with a top-level `error` object — check for `error` before reading `choices`; use `stream: true` for generations that take minutes.","tags":["Chat"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChatCompletionRequest"},"examples":{"basic":{"summary":"Basic request","value":{"model":"auto","messages":[{"role":"user","content":"Hello"}]}},"with-system-prompt":{"summary":"With system prompt","value":{"model":"auto","messages":[{"role":"system","content":"You are a helpful assistant."},{"role":"user","content":"Introduce Router One"}],"temperature":0.7,"max_tokens":1024}},"streaming":{"summary":"Streaming response","value":{"model":"auto","stream":true,"messages":[{"role":"user","content":"Write a short poem"}]}},"json-schema":{"summary":"Structured output (json_schema)","value":{"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":{"200":{"description":"Successful chat completion. When `stream: false`, returns a JSON object; when `stream: true`, returns an SSE event stream.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChatCompletionResponse"},"example":{"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}}},"text/event-stream":{"schema":{"type":"string","description":"SSE event stream. Each event starts with `data: ` and contains a JSON object. The stream ends with `data: [DONE]`."},"example":"data: {\"id\":\"chatcmpl-abc123\",\"object\":\"chat.completion.chunk\",\"created\":1700000000,\"model\":\"openai/gpt-5.5\",\"choices\":[{\"index\":0,\"delta\":{\"role\":\"assistant\",\"content\":\"Hel\"},\"finish_reason\":null}]}\n\ndata: {\"id\":\"chatcmpl-abc123\",\"object\":\"chat.completion.chunk\",\"created\":1700000000,\"model\":\"openai/gpt-5.5\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"lo\"},\"finish_reason\":null}]}\n\ndata: {\"id\":\"chatcmpl-abc123\",\"object\":\"chat.completion.chunk\",\"created\":1700000000,\"model\":\"openai/gpt-5.5\",\"choices\":[{\"index\":0,\"delta\":{},\"finish_reason\":\"stop\"}]}\n\ndata: [DONE]\n"}}},"400":{"description":"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","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"error":{"message":"response_format.json_schema.name is required","type":"invalid_request_error","code":"INVALID_REQUEST","request_id":"290dd478f91d8aec68f7535e871376eb"}}}}},"401":{"description":"Authentication failure — invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"error":{"message":"invalid api key","type":"authentication_error","code":"AUTH_INVALID_API_KEY","request_id":"290dd478f91d8aec68f7535e871376eb"}}}}},"402":{"description":"Insufficient balance or API key spend cap reached — billing_error, not a rate limit","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"insufficient_balance":{"summary":"Wallet balance exhausted","value":{"error":{"message":"insufficient balance: top up at https://router.one/deposit","type":"billing_error","code":"INSUFFICIENT_BALANCE","request_id":"290dd478f91d8aec68f7535e871376eb"}}},"api_key_spend_cap":{"summary":"Per-key maxSpend reached","value":{"error":{"message":"api key spend cap reached: raise or clear this key's maxSpend at https://router.one/dashboard — this is a per-key budget cap, not a rate limit, and topping up the wallet does not lift it","type":"billing_error","code":"API_KEY_SPEND_CAP_EXCEEDED","request_id":"290dd478f91d8aec68f7535e871376eb"}}}}}}},"429":{"description":"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. A 429 that has a row in Dashboard → Logs came back from upstream after the gateway's retries. A gateway limit refuses the request before any model is called, so its 429 has no row, carries an X-RateLimit-Scope of api_key (this key), subject (the account) or subject_model (the account's use of one model), and recurs at the same rate; those limits can be raised on request via support@router.one, and upstream_provider is the model vendor's limit. A streamed request that hit an upstream limit before any output can also come back without a row, so a bursty 429 without a row that clears on its own points upstream too","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"error":{"message":"rate limit exceeded","type":"rate_limit_error","code":"RATE_LIMIT_EXCEEDED","request_id":"290dd478f91d8aec68f7535e871376eb"}}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"error":{"message":"internal error","type":"api_error","code":"INTERNAL_ERROR","request_id":"290dd478f91d8aec68f7535e871376eb"}}}}}}}},"/v1/messages":{"post":{"operationId":"createMessage","summary":"Create Message","description":"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}} rather than the OpenAI {\"error\":{…}} shape used by the other endpoints; errors the gateway returns itself (for example 401 and 429) also carry a top-level request_id, and every response carries an X-Request-ID header.\n\nFunction tools are forwarded as sent (`tools` with `input_schema`, `tool_choice`; `tool_use` / `tool_result` blocks round-trip), except that for Claude Opus 5.5 (`claude-opus-5-5` or `anthropic/claude-opus-5.5`) a `tool_choice` of `any` or `tool` is sent as `auto`, so that model may answer in text instead of calling the tool; whether a model accepts a forced `tool_choice` is up to its vendor (see Anthropic's tool-use docs, checked 2026-10-09). 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 posted token rates (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.\n\nA long `stream: false` call is held open (as of 2026-10-09): after about 25 seconds the gateway commits HTTP 200 and writes JSON leading whitespace until the Message is ready, so a failure after that point arrives as HTTP 200 with the error envelope above — check `type` before reading `content`; use `stream: true` for generations that take minutes.","tags":["Chat"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageRequest"},"examples":{"basic":{"summary":"Basic request","value":{"model":"auto","max_tokens":1024,"messages":[{"role":"user","content":"Hello"}]}},"streaming":{"summary":"Streaming response","value":{"model":"auto","max_tokens":1024,"stream":true,"messages":[{"role":"user","content":"Write a short product intro"}]}}}}}},"responses":{"200":{"description":"Successful Messages API compatible response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageResponse"},"example":{"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}}}}},"400":{"description":"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)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AnthropicErrorResponse"},"example":{"type":"error","error":{"type":"invalid_request_error","message":"model 'openai/gpt-5.5' must be called via /v1/chat/completions","code":"INVALID_REQUEST"}}}}},"401":{"description":"Authentication failure — invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AnthropicErrorResponse"},"example":{"type":"error","error":{"type":"authentication_error","message":"invalid api key","code":"AUTH_INVALID_API_KEY"},"request_id":"86bc1f64e4bcdfc3cc516e51cc419418"}}}},"402":{"description":"Insufficient balance or API key spend cap reached — billing_error, not a rate limit","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AnthropicErrorResponse"},"examples":{"insufficient_balance":{"summary":"Wallet balance exhausted","value":{"type":"error","error":{"type":"billing_error","message":"insufficient balance: top up at https://router.one/deposit"}}},"api_key_spend_cap":{"summary":"Per-key maxSpend reached","value":{"type":"error","error":{"type":"billing_error","message":"api key spend cap reached: raise or clear this key's maxSpend at https://router.one/dashboard — this is a per-key budget cap, not a rate limit, and topping up the wallet does not lift it"}}}}}}},"429":{"description":"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. A 429 that has a row in Dashboard → Logs came back from upstream after the gateway's retries. A gateway limit refuses the request before any model is called, so its 429 has no row, carries an X-RateLimit-Scope of api_key (this key), subject (the account) or subject_model (the account's use of one model), and recurs at the same rate; those limits can be raised on request via support@router.one, and upstream_provider is the model vendor's limit","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AnthropicErrorResponse"},"example":{"type":"error","error":{"type":"rate_limit_error","message":"rate limit exceeded","code":"RATE_LIMIT_EXCEEDED"},"request_id":"86bc1f64e4bcdfc3cc516e51cc419418"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AnthropicErrorResponse"},"example":{"type":"error","error":{"type":"api_error","message":"internal error","code":"INTERNAL_ERROR"},"request_id":"86bc1f64e4bcdfc3cc516e51cc419418"}}}}}}},"/v1/responses":{"post":{"operationId":"createResponse","summary":"Create Response","description":"Create an OpenAI Responses API 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) and file_id / file_url input parts are accepted on natively served Responses models and billed at the model's posted rates. 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.\n\nA long `stream: false` call is held open (as of 2026-10-09): after about 25 seconds the gateway commits HTTP 200 and writes JSON leading whitespace until the response is ready, so a failure after that point arrives as HTTP 200 with a top-level `error` object — check for `error` before reading `output`; use `stream: true` for generations that take minutes.","tags":["Chat"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ResponsesRequest"},"examples":{"basic":{"summary":"Basic request","value":{"model":"openai/gpt-5.5","input":"Introduce Router One in one sentence"}},"withInstructions":{"summary":"With instructions","value":{"model":"openai/gpt-5.5","instructions":"You are a concise technical documentation assistant.","input":"Explain smart routing."}}}}}},"responses":{"200":{"description":"Successful Responses API compatible response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ResponsesResponse"},"example":{"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}}}}},"400":{"description":"Invalid request — an unsupported Responses feature (background, the image_generation tool) or a model this endpoint does not serve","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"error":{"message":"model 'anthropic/claude-opus-5' must be called via /v1/messages or /v1/chat/completions","type":"invalid_request_error","code":"INVALID_REQUEST","request_id":"290dd478f91d8aec68f7535e871376eb"}}}}},"401":{"description":"Authentication failure — invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"error":{"message":"invalid api key","type":"authentication_error","code":"AUTH_INVALID_API_KEY","request_id":"290dd478f91d8aec68f7535e871376eb"}}}}},"402":{"description":"Insufficient balance or API key spend cap reached — billing_error, not a rate limit","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"insufficient_balance":{"summary":"Wallet balance exhausted","value":{"error":{"message":"insufficient balance: top up at https://router.one/deposit","type":"billing_error","code":"INSUFFICIENT_BALANCE","request_id":"290dd478f91d8aec68f7535e871376eb"}}},"api_key_spend_cap":{"summary":"Per-key maxSpend reached","value":{"error":{"message":"api key spend cap reached: raise or clear this key's maxSpend at https://router.one/dashboard — this is a per-key budget cap, not a rate limit, and topping up the wallet does not lift it","type":"billing_error","code":"API_KEY_SPEND_CAP_EXCEEDED","request_id":"290dd478f91d8aec68f7535e871376eb"}}}}}}},"429":{"description":"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. A 429 that has a row in Dashboard → Logs came back from upstream after the gateway's retries. A gateway limit refuses the request before any model is called, so its 429 has no row, carries an X-RateLimit-Scope of api_key (this key), subject (the account) or subject_model (the account's use of one model), and recurs at the same rate; those limits can be raised on request via support@router.one, and upstream_provider is the model vendor's limit","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"error":{"message":"rate limit exceeded","type":"rate_limit_error","code":"RATE_LIMIT_EXCEEDED","request_id":"290dd478f91d8aec68f7535e871376eb"}}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"error":{"message":"internal error","type":"api_error","code":"INTERNAL_ERROR","request_id":"290dd478f91d8aec68f7535e871376eb"}}}}}}}},"/v1/models":{"get":{"operationId":"listModels","summary":"List Models","description":"List the models this API key can call, each with the model `id` to send in a request plus its capabilities, context window and posted price fields. Clients that build a model list from the gateway call it — for example, Claude Code when gateway model discovery is turned on (CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY=1, per Claude Code's env-vars and gateway-protocol docs, Claude Code 2.1.295, checked 2026-10-09). The extra `models` array repeats the same catalog in the field layout of Codex CLI's model catalog. 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.","tags":["Models"],"parameters":[{"name":"If-None-Match","in":"header","required":false,"description":"The `ETag` from a previous response. A match returns 304 Not Modified.","schema":{"type":"string"}}],"responses":{"200":{"description":"The model catalog for this key","content":{"application/json":{"schema":{"type":"object","properties":{"object":{"type":"string","enum":["list"],"description":"Always `list`."},"data":{"type":"array","description":"One entry per model this key can call.","items":{"type":"object","properties":{"id":{"type":"string","description":"Model ID to send in the `model` field. Case-sensitive."},"object":{"type":"string","enum":["model"]}}}}}},"example":{"object":"list","data":[{"id":"anthropic/claude-sonnet-5","object":"model","capabilities":["chat","streaming","tool_calling","vision"],"max_tokens":1048576,"category":"text","pricing_mode":"token"}]}}}},"304":{"description":"Catalog unchanged since the ETag you sent; no body"},"401":{"description":"Authentication failure — invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"error":{"message":"invalid api key","type":"authentication_error","code":"AUTH_INVALID_API_KEY","request_id":"290dd478f91d8aec68f7535e871376eb"}}}}}}}},"/v1/balance":{"get":{"operationId":"getBalance","summary":"Get Balance","description":"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.","tags":["Account"],"responses":{"200":{"description":"The account balance","content":{"application/json":{"schema":{"type":"object","required":["object","currency","balance","reserved_balance","total_balance"],"properties":{"object":{"type":"string","enum":["balance"],"description":"Always `balance`."},"currency":{"type":"string","enum":["USD"],"description":"Always `USD`."},"balance":{"type":"number","description":"Spendable balance right now, in USD. Use this field for low-balance alerts."},"reserved_balance":{"type":"number","description":"Amount currently held for in-flight requests, in USD. The unused part is released back to `balance` when each request settles."},"total_balance":{"type":"number","description":"`balance` + `reserved_balance`, in USD. Use this for reconciliation."}}},"example":{"object":"balance","currency":"USD","balance":12.345678,"reserved_balance":0.5,"total_balance":12.845678}}}},"401":{"description":"Authentication failure — invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"error":{"message":"invalid api key","type":"authentication_error","code":"AUTH_INVALID_API_KEY","request_id":"290dd478f91d8aec68f7535e871376eb"}}}}},"429":{"description":"Rate limit exceeded — more than 60 requests per minute on this key; back off and retry","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"error":{"message":"rate limit exceeded","type":"rate_limit_error","code":"RATE_LIMIT_EXCEEDED","request_id":"290dd478f91d8aec68f7535e871376eb"}}}}}}}},"/v1/images/generations":{"post":{"operationId":"createImageGeneration","summary":"Create Image Generation","description":"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.\n\nThe `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.","tags":["Images"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ImageGenerationRequest"},"examples":{"basic":{"summary":"Basic request","value":{"model":"gpt-image-2","prompt":"An orange kitten wearing an astronaut helmet, floating in starry space, cinematic lighting"}},"with-params":{"summary":"With size and output format","value":{"model":"gpt-image-2","prompt":"Minimalist poster — black coffee cup on a yellow background","n":2,"size":"1024x1024","response_format":"b64_json"}}}}}},"responses":{"200":{"description":"Generation succeeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ImageGenerationResponse"},"example":{"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"}]}}}},"400":{"description":"Invalid request — model not available, invalid field format, missing prompt, or prompt rejected by content moderation (code CONTENT_FILTERED)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"error":{"message":"requested model is not available","type":"invalid_request_error","code":"INVALID_REQUEST","request_id":"290dd478f91d8aec68f7535e871376eb"}}}}},"401":{"description":"Authentication failure — invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"error":{"message":"invalid api key","type":"authentication_error","code":"AUTH_INVALID_API_KEY","request_id":"290dd478f91d8aec68f7535e871376eb"}}}}},"402":{"description":"Insufficient balance or API key spend cap reached — billing_error, not a rate limit","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"insufficient_balance":{"summary":"Wallet balance exhausted","value":{"error":{"message":"insufficient balance: top up at https://router.one/deposit","type":"billing_error","code":"INSUFFICIENT_BALANCE","request_id":"290dd478f91d8aec68f7535e871376eb"}}},"api_key_spend_cap":{"summary":"Per-key maxSpend reached","value":{"error":{"message":"api key spend cap reached: raise or clear this key's maxSpend at https://router.one/dashboard — this is a per-key budget cap, not a rate limit, and topping up the wallet does not lift it","type":"billing_error","code":"API_KEY_SPEND_CAP_EXCEEDED","request_id":"290dd478f91d8aec68f7535e871376eb"}}}}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"error":{"message":"rate limit exceeded","type":"rate_limit_error","code":"RATE_LIMIT_EXCEEDED","request_id":"290dd478f91d8aec68f7535e871376eb"}}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"error":{"message":"internal error","type":"api_error","code":"INTERNAL_ERROR","request_id":"290dd478f91d8aec68f7535e871376eb"}}}}},"502":{"description":"Content moderation unavailable — the prompt could not be screened, so the request is refused rather than run; retry shortly","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"error":{"message":"content moderation unavailable","type":"service_unavailable","code":"MODERATION_UNAVAILABLE","request_id":"290dd478f91d8aec68f7535e871376eb"}}}}},"503":{"description":"Upstream provider unavailable — auth, quota or 5xx failures on the provider side are normalized to this status; retry later","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"error":{"message":"provider is currently unavailable","type":"service_unavailable","code":"PROVIDER_UNAVAILABLE","request_id":"290dd478f91d8aec68f7535e871376eb"}}}}}}}},"/v1/images/edits":{"post":{"operationId":"createImageEdit","summary":"Create Image Edit","description":"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.\n\nUnlike 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.\n\nThe 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.","tags":["Images"],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/ImageEditRequest"}}}},"responses":{"200":{"description":"Edit succeeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ImageGenerationResponse"},"example":{"created":1700000000,"data":[{"url":"https://cdn.router.one/img/def456.png"}]}}}},"400":{"description":"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)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"error":{"message":"invalid request body: image is required","type":"invalid_request_error","code":"INVALID_REQUEST","request_id":"290dd478f91d8aec68f7535e871376eb"}}}}},"401":{"description":"Authentication failure — invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"error":{"message":"invalid api key","type":"authentication_error","code":"AUTH_INVALID_API_KEY","request_id":"290dd478f91d8aec68f7535e871376eb"}}}}},"402":{"description":"Insufficient balance or API key spend cap reached — billing_error, not a rate limit","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"insufficient_balance":{"summary":"Wallet balance exhausted","value":{"error":{"message":"insufficient balance: top up at https://router.one/deposit","type":"billing_error","code":"INSUFFICIENT_BALANCE","request_id":"290dd478f91d8aec68f7535e871376eb"}}},"api_key_spend_cap":{"summary":"Per-key maxSpend reached","value":{"error":{"message":"api key spend cap reached: raise or clear this key's maxSpend at https://router.one/dashboard — this is a per-key budget cap, not a rate limit, and topping up the wallet does not lift it","type":"billing_error","code":"API_KEY_SPEND_CAP_EXCEEDED","request_id":"290dd478f91d8aec68f7535e871376eb"}}}}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"error":{"message":"rate limit exceeded","type":"rate_limit_error","code":"RATE_LIMIT_EXCEEDED","request_id":"290dd478f91d8aec68f7535e871376eb"}}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"error":{"message":"internal error","type":"api_error","code":"INTERNAL_ERROR","request_id":"290dd478f91d8aec68f7535e871376eb"}}}}},"502":{"description":"Content moderation unavailable — the prompt could not be screened, so the request is refused rather than run; retry shortly","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"error":{"message":"content moderation unavailable","type":"service_unavailable","code":"MODERATION_UNAVAILABLE","request_id":"290dd478f91d8aec68f7535e871376eb"}}}}},"503":{"description":"Upstream provider unavailable — auth, quota or 5xx failures on the provider side are normalized to this status; retry later","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"error":{"message":"provider is currently unavailable","type":"service_unavailable","code":"PROVIDER_UNAVAILABLE","request_id":"290dd478f91d8aec68f7535e871376eb"}}}}}}}},"/v1/videos/generations":{"post":{"operationId":"submitVideoGeneration","summary":"Submit Video Generation","description":"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:\n\n1. Call this endpoint to submit the task; on success, returns `202 Accepted` with a `task_id`.\n2. Use the `task_id` with `GET /v1/videos/generations/{task_id}` to poll the status.\n3. When `status` becomes `completed`, read the video `url` from the response.\n\n**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.\n\n**Clip length and resolution are fixed per model** and priced per clip; `duration` / `size` in the request body are ignored. The [video generation page](https://router.one/veo-api-china) shows whether a video model is listed — none is as of 2026-10-01.\n\n**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.","tags":["Videos"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VideoGenerationRequest"},"examples":{"text-to-video":{"summary":"Text-to-video","value":{"model":"<text-to-video-model-id>","prompt":"Waves lapping at the shore, dusk sunlight glittering on the water, slow motion","aspect_ratio":"16:9"}},"image-to-video":{"summary":"Image-to-video (one reference image)","value":{"model":"<image-to-video-model-id>","prompt":"Camera slowly pushes in; the person in frame turns their head slightly","image_url":"https://example.com/portrait.jpg"}}}}}},"responses":{"202":{"description":"Task accepted; queued for generation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/VideoSubmitResponse"},"example":{"task_id":"v_8f3a92c1d4e74b6ea0b5f1d29c7e8a01","status":"pending"}}}},"400":{"description":"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)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"error":{"message":"reference image exceeds 20MB","type":"invalid_request_error","code":"INVALID_REQUEST","request_id":"290dd478f91d8aec68f7535e871376eb"}}}}},"401":{"description":"Authentication failure — invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"error":{"message":"invalid api key","type":"authentication_error","code":"AUTH_INVALID_API_KEY","request_id":"290dd478f91d8aec68f7535e871376eb"}}}}},"402":{"description":"Insufficient balance or API key spend cap reached — billing_error, not a rate limit","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"insufficient_balance":{"summary":"Wallet balance exhausted","value":{"error":{"message":"insufficient balance: top up at https://router.one/deposit","type":"billing_error","code":"INSUFFICIENT_BALANCE","request_id":"290dd478f91d8aec68f7535e871376eb"}}},"api_key_spend_cap":{"summary":"Per-key maxSpend reached","value":{"error":{"message":"api key spend cap reached: raise or clear this key's maxSpend at https://router.one/dashboard — this is a per-key budget cap, not a rate limit, and topping up the wallet does not lift it","type":"billing_error","code":"API_KEY_SPEND_CAP_EXCEEDED","request_id":"290dd478f91d8aec68f7535e871376eb"}}}}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"error":{"message":"rate limit exceeded","type":"rate_limit_error","code":"RATE_LIMIT_EXCEEDED","request_id":"290dd478f91d8aec68f7535e871376eb"}}}}},"502":{"description":"Gateway-side failure before or after the upstream call — content moderation unavailable (code MODERATION_UNAVAILABLE), or the upstream returned an empty response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"error":{"message":"empty response from upstream","type":"api_error","code":"INTERNAL_ERROR","request_id":"290dd478f91d8aec68f7535e871376eb"}}}}},"503":{"description":"Upstream provider unavailable — auth, quota or 5xx failures on the provider side are normalized to this status; retry later","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"error":{"message":"provider is currently unavailable","type":"service_unavailable","code":"PROVIDER_UNAVAILABLE","request_id":"290dd478f91d8aec68f7535e871376eb"}}}}},"504":{"description":"Upstream timeout while submitting the task","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"error":{"message":"upstream timeout","type":"service_unavailable","code":"PROVIDER_UNAVAILABLE","request_id":"290dd478f91d8aec68f7535e871376eb"}}}}}}}},"/v1/videos/generations/{task_id}":{"get":{"operationId":"getVideoGeneration","summary":"Get Video Generation Status","description":"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.\n\n- `pending` — task accepted, not yet started\n- `processing` — generation in progress (`progress` is omitted by the currently listed models)\n- `completed` — generation complete; read `url` for the video file\n- `failed` — generation failed; read `error` for the reason\n\nPoll 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.","tags":["Videos"],"parameters":[{"name":"task_id","in":"path","required":true,"description":"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.","schema":{"type":"string"},"example":"v_8f3a92c1d4e74b6ea0b5f1d29c7e8a01"}],"responses":{"200":{"description":"Query succeeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/VideoStatusResponse"},"examples":{"processing":{"summary":"Generating","value":{"task_id":"v_8f3a92c1d4e74b6ea0b5f1d29c7e8a01","status":"processing"}},"completed":{"summary":"Completed","value":{"task_id":"v_8f3a92c1d4e74b6ea0b5f1d29c7e8a01","status":"completed","url":"https://cdn.router.one/video/abc123.mp4"}},"failed":{"summary":"Failed","value":{"task_id":"v_8f3a92c1d4e74b6ea0b5f1d29c7e8a01","status":"failed","error":"Content policy violation"}}}}}},"401":{"description":"Authentication failure — invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Task not found — unknown, expired, malformed, or submitted by another account (all return 404)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"error":{"message":"task not found","type":"invalid_request_error","code":"INVALID_REQUEST","request_id":"290dd478f91d8aec68f7535e871376eb"}}}}},"503":{"description":"Upstream provider unavailable while polling; retry after the poll interval","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"error":{"message":"provider is currently unavailable","type":"service_unavailable","code":"PROVIDER_UNAVAILABLE","request_id":"290dd478f91d8aec68f7535e871376eb"}}}}},"504":{"description":"Upstream timeout while polling","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"error":{"message":"upstream timeout","type":"service_unavailable","code":"PROVIDER_UNAVAILABLE","request_id":"290dd478f91d8aec68f7535e871376eb"}}}}}}}}},"components":{"securitySchemes":{"BearerAuth":{"type":"http","scheme":"bearer","description":"Authenticate with your API Key. Get your API Key in the Router One console; the format is `sk-xxx`."}},"schemas":{"ChatCompletionRequest":{"type":"object","required":["model","messages"],"properties":{"model":{"type":"string","description":"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":{"type":"array","description":"Chat messages, in chronological order.","items":{"$ref":"#/components/schemas/ChatMessage"},"minItems":1},"stream":{"type":"boolean","description":"Whether to enable streaming response. When enabled, returns an SSE event stream.","default":false},"temperature":{"type":"number","description":"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.","minimum":0,"maximum":2,"default":1},"max_tokens":{"type":"integer","description":"Maximum number of tokens to generate. On reasoning models, thinking tokens count toward this limit, so leave headroom.","minimum":1},"max_completion_tokens":{"type":"integer","description":"OpenAI's current name for `max_tokens`, accepted with the same meaning; send either one. When both are sent, `max_completion_tokens` wins.","minimum":1},"top_p":{"type":"number","description":"Nucleus sampling parameter. The model considers tokens with the top `top_p` mass of the probability distribution.","minimum":0,"maximum":1,"default":1},"reasoning_effort":{"type":"string","description":"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":{"type":"object","description":"Streaming response options. Only valid when `stream: true`.","properties":{"include_usage":{"type":"boolean","description":"Whether to include usage info in the final chunk of the streaming response.","default":false}}},"web_search_options":{"type":"object","description":"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.","properties":{}},"tools":{"type":"array","description":"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`).","items":{"$ref":"#/components/schemas/ChatTool"}},"tool_choice":{"description":"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. Whether a model accepts a forced choice is up to its vendor; for Claude Opus 5.5 (`claude-opus-5-5` or `anthropic/claude-opus-5.5`) Router One sends `required` or a named function as `auto`, so that model may answer in text; for JSON from that model use `output_config.format` on `/v1/messages`.","oneOf":[{"type":"string","enum":["none","auto","required"],"description":"Preset strategy."},{"type":"object","required":["type","function"],"description":"Force one specific function.","properties":{"type":{"type":"string","enum":["function"],"description":"Always `function`."},"function":{"type":"object","required":["name"],"properties":{"name":{"type":"string","description":"Name of the function to force."}}}}}]},"response_format":{"type":"object","description":"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).","required":["type"],"properties":{"type":{"type":"string","enum":["text","json_object","json_schema"],"description":"`text` is the default. `json_object` asks for a JSON object; `json_schema` constrains the reply to `json_schema.schema` on models that honor it."},"json_schema":{"type":"object","description":"Required when `type` is `json_schema`. Forwarded as sent, including `strict` and `description`.","required":["name","schema"],"properties":{"name":{"type":"string","description":"Identifier for the schema. Missing name → 400 `response_format.json_schema.name is required`."},"description":{"type":"string","description":"Optional hint to the model about what the schema is for."},"schema":{"type":"object","description":"JSON Schema object the reply should follow. Missing schema → 400 `response_format.json_schema.schema is required`."},"strict":{"type":"boolean","description":"Ask the model to follow the schema exactly. Honored only by models that support strict mode."}}}}}}},"ChatMessage":{"type":"object","required":["role","content"],"properties":{"role":{"type":"string","enum":["system","user","assistant","tool"],"description":"Message role. `system` for system prompt, `user` for user messages, `assistant` for assistant replies, `tool` for a tool result returned to the model."},"content":{"oneOf":[{"type":"string","description":"Plain text message content"},{"type":"array","description":"Multimodal content (text + images)","items":{"oneOf":[{"type":"object","required":["type","text"],"properties":{"type":{"type":"string","enum":["text"],"description":"Content type"},"text":{"type":"string","description":"Text content"}}},{"type":"object","required":["type","image_url"],"properties":{"type":{"type":"string","enum":["image_url"],"description":"Content type"},"image_url":{"type":"object","required":["url"],"properties":{"url":{"type":"string","description":"Image URL or base64-encoded image data"}}}}}]}}],"description":"Message content. Either a string or a multimodal content array."},"name":{"type":"string","description":"Optional participant name."},"tool_calls":{"type":"array","description":"Tool calls requested by the assistant. Returned on the assistant message when `finish_reason` is `tool_calls`; append that message unchanged before you send the results back.","items":{"$ref":"#/components/schemas/ChatToolCall"}},"tool_call_id":{"type":"string","description":"ID of the tool call this message answers. Required on `tool` messages."},"refusal":{"type":"string","nullable":true,"description":"Refusal text, OpenAI-style `message.refusal`. Only present on the assistant message of a response when the model explicitly declined; `content` may be an empty string alongside it. Ignored when sent in a request."}}},"ChatTool":{"type":"object","required":["type"],"properties":{"type":{"type":"string","enum":["function","google_search"],"description":"Tool type. `function` (client-side tool; the `function` object is required) on every tool-capable model. `google_search` (hosted search, no `function` object) is accepted on `google/gemini-3-flash` only."},"function":{"type":"object","required":["name"],"properties":{"name":{"type":"string","description":"Function name the model returns when it decides to call this tool.","example":"get_weather"},"description":{"type":"string","description":"What the function does. The model relies on this to decide when to call it."},"parameters":{"type":"object","description":"Function arguments described as a JSON Schema object."}}}}},"ChatToolCall":{"type":"object","required":["id","type","function"],"properties":{"id":{"type":"string","description":"Tool call ID. Send it back as `tool_call_id` on the `tool` message that carries the result."},"type":{"type":"string","enum":["function"],"description":"Tool call type, always `function`."},"function":{"type":"object","properties":{"name":{"type":"string","description":"Name of the function to run."},"arguments":{"type":"string","description":"Call arguments as a JSON string. Parse and validate it against your schema before executing."}}}}},"ChatCompletionResponse":{"type":"object","properties":{"id":{"type":"string","description":"Unique identifier of the completion request"},"object":{"type":"string","enum":["chat.completion"],"description":"Object type, always `chat.completion`"},"created":{"type":"integer","description":"Creation Unix timestamp"},"model":{"type":"string","description":"ID of the model actually used"},"choices":{"type":"array","items":{"type":"object","properties":{"index":{"type":"integer","description":"Choice index"},"message":{"$ref":"#/components/schemas/ChatMessage"},"finish_reason":{"type":"string","enum":["stop","length","content_filter","tool_calls","refusal"],"description":"Stop reason. Common values: `stop` for normal end, `length` for max_tokens reached, `content_filter` for a content-policy filter, `tool_calls` when the model requested tool calls instead of answering, `refusal` when the model explicitly declined (for example a Claude-family model ending with stop_reason refusal) — `message.refusal` may carry the refusal text and `content` may be empty. A model family's native stop reason may also be passed through as sent."}}}},"usage":{"type":"object","properties":{"prompt_tokens":{"type":"integer","description":"Tokens consumed by input"},"completion_tokens":{"type":"integer","description":"Tokens consumed by output"},"total_tokens":{"type":"integer","description":"Total tokens consumed"}}}}},"ErrorResponse":{"type":"object","properties":{"error":{"type":"object","properties":{"message":{"type":"string","description":"Error message"},"type":{"type":"string","description":"Error type"},"code":{"type":"string","description":"Machine-readable error code (UPPER_SNAKE_CASE), see the code table in the introduction"},"request_id":{"type":"string","description":"Gateway request id — include it when contacting support"}}}}},"AnthropicErrorResponse":{"type":"object","description":"Error envelope on /v1/messages — the Anthropic Messages shape, not the OpenAI {error} shape","properties":{"type":{"type":"string","description":"Always \"error\""},"error":{"type":"object","properties":{"type":{"type":"string","description":"Error type (authentication_error, billing_error, rate_limit_error, invalid_request_error, api_error, service_unavailable)"},"message":{"type":"string","description":"Error message"},"code":{"type":"string","description":"Machine-readable code (UPPER_SNAKE_CASE); omitted on some billing errors"}}},"request_id":{"type":"string","description":"Gateway request id (top-level, as in the Anthropic API); present on gateway-layer errors such as 401 and rate-limit 429"}}},"ImageGenerationRequest":{"type":"object","required":["model","prompt"],"properties":{"model":{"type":"string","description":"Model ID. Choose a model that supports image generation; check the console model marketplace.","example":"gpt-image-2"},"prompt":{"type":"string","description":"Text prompt for image generation. More specific and visually evocative prompts usually produce better results. Recommended length under 4000 characters.","maxLength":4000},"n":{"type":"integer","description":"Number of images to generate in this request. Billed per image.","minimum":1,"maximum":10,"default":1},"size":{"type":"string","description":"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":{"type":"string","description":"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":{"type":"string","enum":["url","b64_json"],"description":"How the image is returned.\n- `url` (default): a CDN URL valid for about 1 hour; download or re-host as needed\n- `b64_json`: base64-encoded image bytes in the response; larger body, no extra download","default":"url"}}},"ImageEditRequest":{"type":"object","required":["model","prompt","image"],"properties":{"model":{"type":"string","description":"Model ID. Choose a model that supports image-to-image editing; check the console model marketplace.","example":"gemini-3.1-flash-image-preview"},"prompt":{"type":"string","description":"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.","maxLength":4000},"image":{"type":"string","format":"binary","description":"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":{"type":"integer","description":"Number of images to generate in this request. Billed per image.","minimum":1,"maximum":10,"default":1},"size":{"type":"string","description":"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":{"type":"string","description":"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":{"type":"string","enum":["url","b64_json"],"description":"How the image is returned.\n- `url` (default): a CDN URL valid for about 1 hour; download or re-host as needed\n- `b64_json`: base64-encoded image bytes in the response; larger body, no extra download","default":"url"}}},"ImageGenerationResponse":{"type":"object","properties":{"created":{"type":"integer","description":"Unix timestamp (seconds) when generation completed"},"data":{"type":"array","description":"List of generated images; length equals the number actually generated.","items":{"$ref":"#/components/schemas/ImageData"}}}},"ImageData":{"type":"object","properties":{"url":{"type":"string","nullable":true,"description":"Image URL. Only returned when `response_format=url`."},"b64_json":{"type":"string","nullable":true,"description":"Base64-encoded image bytes. Only returned when `response_format=b64_json`."},"revised_prompt":{"type":"string","nullable":true,"description":"The model's rewritten version of the original prompt (some models auto-refine prompts). May be null."}}},"VideoGenerationRequest":{"type":"object","required":["model","prompt"],"properties":{"model":{"type":"string","description":"Model ID. Choose a model that supports video generation; check the console model marketplace.","example":"<video-model-id-from-/models>"},"prompt":{"type":"string","description":"Text prompt for video generation, describing scene content, camera motion, style, etc."},"duration":{"type":"integer","description":"Ignored by the video models listed before 2026-09-05, each of which produced one fixed clip length (see the video generation page); no video model is listed as of 2026-10-01. Kept for forward compatibility."},"size":{"type":"string","description":"Ignored by the video models listed before 2026-09-05 (resolution was fixed per model and priced per clip); no video model is listed as of 2026-10-01. Kept for forward compatibility.","example":"720p"},"aspect_ratio":{"type":"string","enum":["16:9","9:16","1:1"],"description":"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":{"type":"string","description":"Negative prompt — elements to avoid in the output. Optional; the video models listed before 2026-09-05 ignored it, and no video model is listed as of 2026-10-01."},"image_url":{"type":"string","format":"uri","description":"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":{"type":"array","items":{"type":"string","format":"uri"},"maxItems":3,"description":"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":{"type":"string","description":"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."}}},"VideoSubmitResponse":{"type":"object","required":["task_id","status"],"properties":{"task_id":{"type":"string","description":"Task identifier; treat as an opaque string. Pass it back as-is to the status endpoint — **do not parse its internal structure**."},"status":{"type":"string","enum":["pending"],"description":"Initial status after submission; always `pending`."}}},"VideoStatusResponse":{"type":"object","required":["task_id","status"],"properties":{"task_id":{"type":"string","description":"Task identifier; matches the `task_id` returned when submitting."},"status":{"type":"string","enum":["pending","processing","completed","failed"],"description":"Task status.\n- `pending`: accepted, not yet started\n- `processing`: generating (`progress` is omitted by the currently listed models)\n- `completed`: complete; read `url` for the video\n- `failed`: failed; read `error` for the reason"},"url":{"type":"string","nullable":true,"description":"Generated video URL. Only returned when `status=completed`. The URL is typically valid for 24 hours — download or re-host promptly."},"error":{"type":"string","nullable":true,"description":"Human-readable failure description. Only returned when `status=failed`."},"progress":{"type":"integer","nullable":true,"minimum":0,"maximum":100,"description":"Generation progress percentage (0-100); some models may not return this."}}},"MessageRequest":{"type":"object","required":["model","messages","max_tokens"],"properties":{"model":{"type":"string","description":"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":{"type":"array","description":"Conversation messages in the Claude / Anthropic Messages format.","items":{"$ref":"#/components/schemas/AnthropicMessage"},"minItems":1},"system":{"type":"string","description":"Optional system prompt."},"max_tokens":{"type":"integer","description":"Maximum number of output tokens.","minimum":1},"stream":{"type":"boolean","description":"Whether to enable streaming response.","default":false},"temperature":{"type":"number","description":"Sampling temperature, range 0-2.","minimum":0,"maximum":2,"default":1},"tools":{"type":"array","description":"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.","items":{"type":"object","required":["name"],"properties":{"name":{"type":"string","description":"Tool name."},"description":{"type":"string","description":"What the tool does — the model reads this to decide when to call it."},"input_schema":{"type":"object","description":"JSON Schema for the tool input (function tools)."}}}},"tool_choice":{"type":"object","description":"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`), for which `any` and `tool` are sent as `auto`, so check `stop_reason` and the content block types; whether a model accepts a forced choice is up to its vendor."}}},"AnthropicMessage":{"type":"object","required":["role","content"],"properties":{"role":{"type":"string","enum":["user","assistant"],"description":"Message role."},"content":{"oneOf":[{"type":"string","description":"Plain text content."},{"type":"array","description":"List of content blocks.","items":{"$ref":"#/components/schemas/AnthropicContentBlock"}}]}}},"AnthropicContentBlock":{"type":"object","required":["type"],"properties":{"type":{"type":"string","enum":["text","image","tool_use","tool_result"],"description":"Content block type. `tool_use` / `tool_result` blocks round-trip as sent."},"text":{"type":"string","description":"Text content when `type=text`."},"source":{"type":"object","description":"Image source when `type=image`.","properties":{"type":{"type":"string","example":"url"},"url":{"type":"string","example":"https://example.com/image.png"}}},"id":{"type":"string","description":"Tool call id when `type=tool_use`."},"name":{"type":"string","description":"Tool name when `type=tool_use`."},"input":{"type":"object","description":"Tool input when `type=tool_use`."},"tool_use_id":{"type":"string","description":"Id of the `tool_use` block this result answers, when `type=tool_result`."},"content":{"description":"Tool result when `type=tool_result` — a string or a list of content blocks.","oneOf":[{"type":"string"},{"type":"array","items":{"type":"object"}}]}}},"MessageResponse":{"type":"object","properties":{"id":{"type":"string","description":"Message ID."},"type":{"type":"string","enum":["message"],"description":"Object type."},"role":{"type":"string","enum":["assistant"],"description":"Response role."},"model":{"type":"string","description":"ID of the model actually used."},"content":{"type":"array","items":{"$ref":"#/components/schemas/AnthropicContentBlock"}},"stop_reason":{"type":"string","description":"Stop reason."},"usage":{"type":"object","properties":{"input_tokens":{"type":"integer","description":"Input tokens."},"output_tokens":{"type":"integer","description":"Output tokens."}}}}},"ResponsesRequest":{"type":"object","required":["model","input"],"properties":{"model":{"type":"string","description":"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":{"oneOf":[{"type":"string","description":"Input text."},{"type":"array","description":"List of Responses API input items. input_file parts carrying file_id or file_url are accepted on models served natively over the Responses wire format.","items":{"$ref":"#/components/schemas/ResponseInputItem"}}]},"instructions":{"type":"string","description":"Optional system instructions."},"stream":{"type":"boolean","description":"Whether to enable streaming response.","default":false},"temperature":{"type":"number","description":"Sampling temperature, range 0-2.","minimum":0,"maximum":2,"default":1},"max_output_tokens":{"type":"integer","description":"Maximum number of output tokens.","minimum":1},"tools":{"type":"array","description":"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 posted rates. The image_generation tool is rejected with 400 invalid_request — use /v1/images/generations.","items":{"type":"object","description":"One tool declaration in Responses API shape (type plus the tool's own fields)."}},"previous_response_id":{"type":"string","description":"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":{"description":"Conversation reference (id string or object). Same model rule as previous_response_id.","oneOf":[{"type":"string","description":"Conversation id."},{"type":"object","description":"Conversation object with an id field.","properties":{"id":{"type":"string"}}}]},"prompt":{"type":"object","description":"Prompt template reference (id plus variables). Same model rule as previous_response_id.","properties":{"id":{"type":"string","description":"Prompt template id."},"variables":{"type":"object","description":"Template variables."}}},"service_tier":{"type":"string","description":"Optional OpenAI processing-tier field. Router One bills each request at the model's posted rates (https://router.one/pricing) and may normalize this field."},"store":{"type":"boolean","description":"Forwarded as sent."},"background":{"type":"boolean","description":"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}}},"ResponseInputItem":{"type":"object","required":["role","content"],"properties":{"role":{"type":"string","enum":["system","developer","user","assistant"],"description":"Input item role. `developer` items (the Codex CLI default) are passed through as sent."},"content":{"oneOf":[{"type":"string","description":"Plain text content."},{"type":"array","description":"List of content blocks.","items":{"$ref":"#/components/schemas/ResponseContentPart"}}]}}},"ResponseContentPart":{"type":"object","required":["type"],"properties":{"type":{"type":"string","enum":["input_text","output_text","input_image"],"description":"Content part type."},"text":{"type":"string","description":"Text content."},"image_url":{"type":"string","description":"Image URL."}}},"ResponsesResponse":{"type":"object","properties":{"id":{"type":"string","description":"Response ID."},"object":{"type":"string","enum":["response"],"description":"Object type."},"created_at":{"type":"integer","description":"Creation Unix timestamp."},"status":{"type":"string","description":"Response status."},"model":{"type":"string","description":"ID of the model actually used."},"output_text":{"type":"string","description":"Aggregated text output."},"output":{"type":"array","description":"Raw output items.","items":{"$ref":"#/components/schemas/ResponseInputItem"}},"usage":{"type":"object","properties":{"input_tokens":{"type":"integer","description":"Input tokens."},"output_tokens":{"type":"integer","description":"Output tokens."},"total_tokens":{"type":"integer","description":"Total tokens."}}}}}}},"x-ext-urls":{}}