The router API
Request fields, authentication, responses and error formats for the /v1 API and public model catalog.
Authentication#
Use an active API key from your workspace for the /v1 API. Bearer tokens and the Anthropic X-Api-Key header are accepted. The two /public/models catalog endpoints require no API key.
| Field | Description |
|---|---|
Authorization | Bearer YOUR_API_KEY |
Content-Type | application/json for JSON requests. |
X-Api-Key | Alternative API-key header; Authorization takes precedence when both are present. |
Chat#
POST/v1/chat/completionsGenerate a chat response
Send a conversation in the OpenAI-compatible message format. Set stream to true for server-sent events.
| Field | Type | Description |
|---|---|---|
model | string · required | Exact model ID from the catalog. |
messages | array · required | Conversation messages with role and content, including tool calls and results where supported. |
max_tokens | integer | Maximum output-token budget. |
stream | boolean | Whether to stream output as SSE. |
tools / tool_choice | array / object | Client-side tool definitions and selection. |
response_format | object | Structured-output request where supported by the route. |
reasoning_effort / reasoning.effort | string | Equivalent effort fields. Equal values are accepted; conflicting values return 400. No effort default or value clamp is added. reasoning: null is treated as omitted. |
stop | string / string[] / null | Null or [] means no constraint. A string or nonempty array requires a compatible Chat or Messages route; Responses cannot silently discard it. |
min_discount_percent | number | Minimum input and output discount versus verified list price; 0–99.99. |
ranking | enum | balance (default), speed or discount. |
zdr | boolean | Require verified zero-data-retention routes. Adds to the account setting; false cannot override it. No eligible route returns 503. |
curl 'https://your-router-domain/v1/chat/completions' \
-H 'Authorization: Bearer YOUR_API_KEY' \
-H 'Content-Type: application/json' \
-d '{
"model": "deepseek-v4-flash",
"messages": [
{
"role": "user",
"content": "Hello!"
}
],
"max_tokens": 128
}'Response: OpenAI Chat Completion object with choices, usage and settled billing metadata. Stream ends with [DONE].
Text completions#
POST/v1/completionsComplete a text prompt
Send one string in prompt, or a single-element string array. The shared gateway applies the same key limits, automatic routing and billing. Responses use text_completion with choices[].text; streaming ends with [DONE]. echo: true includes your prompt once before the output.
curl 'https://your-router-domain/v1/completions' \
-H 'Authorization: Bearer YOUR_API_KEY' \
-H 'Content-Type: application/json' \
-d '{
"model": "deepseek-v4-flash",
"prompt": "Hello!",
"max_tokens": 128
}'The output budget defaults to 128 tokens. Batched prompts, token IDs, multiple samples, legacy logprobs and suffix insertion are unsupported. Request history records the underlying Chat protocol.
Responses#
POST/v1/responsesCreate a stateless response
| Field | Type | Description |
|---|---|---|
model | string · required | Exact model ID. |
input | string / array | Text or supported conversation items. |
store | false · required | Responses are stateless. Stored-response access is unsupported. |
instructions | string | System instructions for this request. |
max_output_tokens | integer / null | Maximum output-token budget. Null is treated as omitted; existing budget rules still apply. |
reasoning / reasoning_effort | object or null / string | reasoning: null is omitted. Top-level reasoning_effort aliases reasoning.effort; equal values are accepted and conflicts return 400. Responses routes receive nested effort without an added default. |
stream | boolean | Stream named Responses API events. |
tools | array | Supported client-side function and custom local tools. |
min_discount_percent | number | Minimum input and output discount versus verified list price; 0–99.99. |
ranking | enum | balance (default), speed or discount. |
zdr | boolean | Require verified zero-data-retention routes. Adds to the account setting; false cannot override it. No eligible route returns 503. |
curl 'https://your-router-domain/v1/responses' \
-H 'Authorization: Bearer YOUR_API_KEY' \
-H 'Content-Type: application/json' \
-d '{
"model": "deepseek-v4-flash",
"input": "Hello!",
"store": false,
"max_output_tokens": 128,
"stream": true
}'Response: Responses object; streamed settlement appears in the final response.completed or response.incomplete event.
Messages#
POST/v1/messagesAnthropic-compatible messages
| Field | Type | Description |
|---|---|---|
model | string · required | An exact catalog model ID. |
messages | array · required | User and assistant conversation turns, including client tools and tool results. |
max_tokens | integer · required | Maximum output-token budget. |
system | string / array | System instruction content. |
stream | boolean | Return Anthropic-compatible SSE events. |
stop_sequences | string[] | Sequences that should end generation. [] adds no constraint; null is invalid. A nonempty array needs a Chat or Messages route and is never silently dropped for Responses. |
thinking | object | Reasoning hints for models with compatible support. |
min_discount_percent | number | Minimum input and output discount versus verified list price; 0–99.99. |
ranking | enum | balance (default), speed or discount. |
zdr | boolean | Require verified zero-data-retention routes. Adds to the account setting; false cannot override it. No eligible route returns 503. |
curl 'https://your-router-domain/v1/messages' \
-H 'Authorization: Bearer YOUR_API_KEY' \
-H 'Content-Type: application/json' \
-d '{
"model": "deepseek-v4-flash",
"max_tokens": 1024,
"messages": [
{
"role": "user",
"content": "Hello!"
}
]
}'Response: Anthropic Message shape with content blocks and usage. The final streamed message_delta contains settlement metadata. A matched stop_sequence is preserved in JSON and SSE; Chat conversions use finish_reason: stop.
Direct image input#
Chat Completions, Responses and Messages accept raw images in ordinary user messages when a compatible route is available. Check capabilities.vision in GET /v1/models. Each anonymous route on the model detail page separately identifies its input support and price. A lower-priced text-only route is never a fallback for an image request.
| Field | Description |
|---|---|
Chat · messages[].content[] | Use type: image_url with image_url: {url, detail?} in a user message. |
Responses · input[].content[] | Use type: input_image with image_url: string and optional detail in a user message. Keep store: false. |
Messages · messages[].content[] | Use type: image with source: {type: url, url} or source: {type: base64, media_type, data}. Messages has no detail field. |
URL / base64 | Complete HTTP(S) URLs without embedded credentials, or canonical base64 data URLs. Base64 MIME labels: image/png, image/jpeg, image/webp, image/gif. Image bytes and animation are validated by the serving provider. |
detail · Chat / Responses | low, high, auto or original; omission uses auto for reservation. Any explicit detail cannot be converted to Messages, which has no equivalent field. |
Request limits | The full UTF-8 JSON body, including base64, is limited to 2 MiB. There is no separate image-count limit; the body limit and conservative input bound both apply. |
{
"chat": {
"type": "image_url",
"image_url": {
"url": "https://example.com/image.png",
"detail": "low"
}
},
"responses": {
"type": "input_image",
"image_url": "https://example.com/image.png",
"detail": "low"
},
"messages": {
"type": "image",
"source": {
"type": "url",
"url": "https://example.com/image.png"
}
}
}Place these parts alongside text in the corresponding user content array. For Messages base64 input, set source.type to base64, media_type to a supported image MIME and data to the canonical base64 string without a data-URL prefix. External image URLs are forwarded to the compatible provider; router does not download them. Temporary Router uploads can also be referenced as described below. Image output and images nested inside tool results or non-user messages are not supported. Additional native image fields cannot be silently converted across protocols.
Image requests skip optional text techniques, exact response caching and automatic model routing. An experiment that changes the request or explicitly enables techniques returns 400 incompatible_image_input; unchanged experiment attribution remains available.
Temporary image uploads#
Store a temporary image, then use its returned ID in an ordinary user image block. The limits and handle rules below are Router's contract. This is not the OpenAI or Anthropic Files API, and does not accept general documents, audio or video.
POST/v1/uploadsUpload one image
| Field | Type | Description |
|---|---|---|
file | binary · required | Exactly one multipart/form-data file, with no other fields. PNG, JPEG, WebP or GIF; matching MIME and image signature; 1 byte to 10 MiB. Complete multipart body at most 10 MiB plus 64 KiB. |
Authentication | inference-scoped Bearer or X-Api-Key, or a same-origin signed-in browser session. | |
Expiry and quotas | New uploads expire after one hour; earlier uploads retain their recorded expiry. 32 stored objects and 64 MiB per account, including pending writes and queued deletions. Rolling limits: 20 admitted uploads per minute and 120 per day, including admitted storage failures. Tombstones remain for 24 hours after physical deletion; deleting does not immediately free storage or reset rate counters. |
curl "$ROUTER_BASE_URL/v1/uploads" \
-H "Authorization: Bearer $ROUTER_API_KEY" \
-F "file=@image.png;type=image/png"Set ROUTER_BASE_URL to this deployment's origin without /v1. HTTP 201 returns id, bytes, content_type and expires_at. The ID is rtr_upload_ followed by 32 lowercase hexadecimal characters. Uploading does not call a model. The one-hour lifetime follows the reference's public DPA statement; this does not establish how its storage behaves at runtime.
{
"chat": {
"type": "image_url",
"image_url": {
"url": "router-upload://rtr_upload_0123456789abcdef0123456789abcdef"
}
},
"responses": {
"type": "input_image",
"file_id": "rtr_upload_0123456789abcdef0123456789abcdef"
},
"messages": {
"type": "image",
"source": {
"type": "file",
"file_id": "rtr_upload_0123456789abcdef0123456789abcdef"
}
}
}Replace the example ID with your upload's id. Responses and Messages use their standard image-file field shapes; the router-upload:// Chat URL is a Router extension. IDs belong to this account and are resolved locally before forwarding. Vendor file IDs are not accepted. Choose one source per image; Responses permits image_url: null alongside its file_id. Unsupported extra fields on upload blocks are rejected. Existing image capacity, price, detail and input-budget rules still apply; a stored image does not enable a text-only route. The complete inference JSON body remains limited to 2 MiB.
DELETE/v1/uploads/{id}Delete your upload
Requires the same authentication as upload. Returns HTTP 204 without a body; invalid, expired, deleted and foreign IDs return 404. Deletion or expiry blocks new reads, while physical cleanup runs asynchronously. Bytes already downloaded by a provider cannot be recalled.
| Field | Description |
|---|---|
400 invalid_upload | Malformed multipart body or anything other than one file field. |
413 upload_too_large | Empty file, file above 10 MiB, or multipart envelope above its bounded limit. |
415 invalid_content_type / unsupported_file_type | Wrong request content type, unrecognized image signature, or MIME mismatch. |
429 upload_rate_limit / upload_quota_exceeded | Rolling upload admission or retained-storage quota exceeded. |
503 uploads_unavailable | Storage or signing configuration is missing or temporarily unavailable. |
Account data protection#
GET/api/data-protectionRead the account restriction
Requires an account:read key or a signed-in session. Returns zero_data_retention, updated_at and verified_zdr_capacity: false.
PATCH/api/data-protectionSet the account restriction
Only a same-origin signed-in browser session may change this setting. Send {"zero_data_retention": true} or false. API keys receive 403 session_required; malformed settings receive 400 invalid_data_protection. Manage it in the data protection settings.
503 zdr_capacity_unavailable, even with request zdr: false. It blocks new uploads and uploaded-image access with 403 data_protection_restriction, clears response-cache entries and schedules existing uploads for deletion. It does not erase saved separate experiment prompts or pre-existing browser drafts. It cancels Prompt Studio runs and clears their saved prompts, answers and results; billing records remain. It cannot recall requests already sent to a provider. Background cleanup is not an immediate physical-erasure guarantee.Models#
GET/public/modelsBrowse the catalog without an API key
Returns a models array of visible models with price-eligible configured supply. This does not guarantee credentials, health or capacity at inference time. Prices are decimal USD-per-million strings. No account data or internal serving-supplier identities are included.
| Field | Type | Description |
|---|---|---|
zdr | boolean · optional | Only the literal query value true enables the filter. No verified ZDR capacity is configured, so zdr=true returns an empty models array. |
discount_percent | string | A signed percentage; unknown discount is represented as 0.00 on this compatibility endpoint. Null reference prices identify the unknown case. |
supports_reasoning | boolean · optional | Omitted together with reasoning_capability_mode when the capability is unverified. Absence is not false. |
GET /public/models
GET /public/models?zdr=true0.00 compatibility value is not a verified zero discount when reference input/output prices are null. The existing UI endpoint /api/public/models keeps unknown discount as null and also includes display fields and anonymous route details.GET/public/models/{model_id}Read one public model without an API key
Use an exact, case-sensitive canonical ID or a listed alias. Aliases with a slash are supported, and the response contains the canonical ID. IDs are not lowercased or trimmed. The optional zdr=true filter also applies here.
GET /public/models/deepseek-v4-flashSuccess returns one model object. Unknown, hidden, unavailable or ZDR-excluded models return HTTP 404 with {"detail":"The model is not currently available."}. A catalog failure returns HTTP 503 with {"detail":"The catalog is temporarily unavailable."}. Both public endpoints use Cache-Control: public, max-age=0, must-revalidate.
GET/v1/modelsList available models
Read model identifiers, current catalog prices, aliases, capability flags and context limits. Requires an API key.
curl 'https://your-router-domain/v1/models' \
-H 'Authorization: Bearer YOUR_API_KEY'GET/v1/models/compareEstimate a workload across models
Requires usage:read. This read-only calculation uses configured customer catalog prices. It does not run inference or reserve supply.
| Field | Type | Description |
|---|---|---|
models | string · required | One to ten comma-separated model IDs or aliases. Unknown or disallowed models return found: false. |
prompt_tokens / completion_tokens | integer | Tokens per request, each 0–10,000,000. Default 0; estimates assume uncached input. |
requests | integer | Number of identical requests. Default 1. Unsupported totals return 400. |
curl 'https://your-router-domain/v1/models/compare?models=deepseek-v4-flash&prompt_tokens=1000&completion_tokens=250&requests=100' \
-H 'Authorization: Bearer YOUR_API_KEY'Returns object: model.comparison, currency: USD, and data sorted by estimated billed_usd. Each found model includes input_per_m, output_per_m, list_usd, saved_usd and anonymous suppliers. Provider identifies the model maker; supplier entries show only Provider numbers and customer rates. List and savings estimates are null without a verified reference.
GET/v1/models/supplyCheck supply under a price ceiling
| Field | Type | Description |
|---|---|---|
model | string · required | The model to check. |
min_discount_percent | number | The required minimum discount from list. |
zdr | boolean | Restrict to zero-retention sources. |
curl 'https://your-router-domain/v1/models/supply?model=deepseek-v4-flash&min_discount_percent=30' \
-H 'Authorization: Bearer YOUR_API_KEY'Candidate counts describe current pricing. They are not a reservation, a health check or a guarantee of fulfillment.
Price changes#
GET/v1/pricing/changesRead changes to the configured catalog
Requires usage:read. Returns the latest change for each allowed model at or after since, including that timestamp. Initial observations are updated entries with null previous prices. Removed entries have a null model and null current price fields.
| Field | Type | Description |
|---|---|---|
since | RFC 3339 · required | Inclusive timestamp with an explicit timezone, such as 2026-09-06T00:00:00Z. |
limit | integer | 1–100 records per page. Default 100. |
cursor | string | Opaque next_cursor from the previous page. Omit to begin a new query. |
curl 'https://your-router-domain/v1/pricing/changes?since=2026-09-06T00%3A00%3A00Z&limit=100' \
-H 'Authorization: Bearer YOUR_API_KEY'The response includes data, has_more, next_cursor, pricing_version and pricing_updated_at. Entries contain model_id, change_type, changed_at, model, previous_pricing and current_pricing. Decimal prices are strings in USD per million tokens. This endpoint gives the latest change per model, rather than every intermediate observation.
Usage#
GET/v1/usage/requestsRead request history
Requires usage:read. Read model, request status, token usage, settled ledger cost and latency. Supplier identities stay private. Requests still in flight have status pending and no settled amount.
| Field | Type | Description |
|---|---|---|
start_at / end_at | timestamp | Inclusive start, exclusive end. Offsetless timestamps are UTC; use explicit offsets for local dates. |
api_key_id | UUID | Only this account’s key, including revoked keys. An unknown or foreign key returns 404. |
limit | integer | 1–100 records, default 100. Records are newest first. |
cursor | string | Use next_cursor from the preceding page with the same account, date bounds and key filter. Newer requests do not enter that pagination window. |
curl 'https://your-router-domain/v1/usage/requests?limit=25' \
-H 'Authorization: Bearer YOUR_API_KEY'New requests retain the published standard input/output token prices at admission. Settled requests compare those prices with the actual ledger charge. This baseline does not estimate cache savings or alternative models. Historical requests without a recorded price return null for provider_list_price_usd and savings_usd. A negative saving is preserved.
GET/v1/usage/dailyRead daily usage
Requires usage:read. Returns a usage.daily summary and daily_spend rows, filling empty days with zeros. Failed and pending requests are counted separately; only settled ledger entries contribute to spend and billed tokens.
| Field | Type | Description |
|---|---|---|
start_at / end_at | timestamp | Inclusive start and exclusive end, with a maximum duration of 366 days. Defaults to the most recent 30 calendar days, including today. |
api_key_id | UUID | Filter all counts and amounts to one key in this account. |
timezone_offset_minutes | integer | Fixed minutes east of UTC, from −840 to 840; default 0. For example, 480 groups by UTC+8 calendar days. |
start / end | date · legacy | Inclusive UTC YYYY-MM-DD dates. Cannot be mixed with timestamp bounds or a nonzero timezone offset. |
curl 'https://your-router-domain/v1/usage/daily?timezone_offset_minutes=480' \
-H 'Authorization: Bearer YOUR_API_KEY'Account and balance#
GET/v1/accountRead your account
Requires account:read. Returns object: account, email, role, active_key_count, last_key_use_at and enabled_technique_count. Active keys are unrevoked and unexpired. A workspace key has a fixed organization_id; sessions can select it with the organization_id query parameter. Workspace reads identify the responsible member by email and platform role, and include workspace.id, workspace.name and workspace.role (owner or member). workspace_name is the actual name; funded_by_owner is true when the member differs from the wallet owner. Personal reads omit workspace metadata.
curl 'https://your-router-domain/v1/account' \
-H 'Authorization: Bearer YOUR_API_KEY'GET/v1/account/balanceRead wallet and reservation amounts
Requires account:read. Returns object: account.balance, currency: USD, and numeric balance_usd, available_usd and reserved_usd from one balance snapshot. Workspace reads show the owner-funded wallet; auto-recharge is false and threshold_usd, recharge_amount_usd and card are null, keeping the owner's personal payment configuration private. Personal reads return the account's actual saved-card and auto-recharge state. Responses are not cached.
curl 'https://your-router-domain/v1/account/balance' \
-H 'Authorization: Bearer YOUR_API_KEY'GET/v1/account/usageRead account usage and recorded savings
Requires account:read. The days parameter is an integer from 1 to 90, defaulting to 7. The response covers that many trailing 24-hour periods and includes request counts, settled spend and the five models with the highest spend. window_start is inclusive; window_end is exclusive. USD values are numbers, with exact integer values in amounts_nanos.
Savings use the published standard token prices recorded with each request. If any settled request lacks a price, list_usd, saved_usd and savings_percent are null; priced_request_count and savings_status describe that coverage. Savings can be negative.
curl 'https://your-router-domain/v1/account/usage?days=7' \
-H 'Authorization: Bearer YOUR_API_KEY'GET/v1/account/savingsRead savings windows and optimization settings
Requires account:read. Returns 1-day, 7-day and 30-day usage windows with a shared end time, current techniques and up to 100 experiments. experiments_has_more indicates additional experiments. Experiment avg_saved remains null: a price comparison does not establish savings caused by a technique or experiment.
curl 'https://your-router-domain/v1/account/savings' \
-H 'Authorization: Bearer YOUR_API_KEY'Report channels#
Choose Email, Slack, Discord or Telegram in Reports. Connections belong to your personal account, and summaries exclude Router team activity. Explicitly connect a destination before enabling a schedule. An unavailable platform cannot start a connection.
GET/v1/channelsList connected channels
Requires account:read or a browser session. Returns object: list, data and available_events. available_events contains the nine supported event definitions as id, name and description; events contains each channel’s saved selection. Reading options does not subscribe a channel. A channel includes its id, platform, workspace_name, integration_active, report_frequency and report_hour in UTC. workspace_name labels the external destination, not a Router organization. integration_active reports configuration eligibility; Email also requires the matching verified address. This does not prove delivery.
curl 'https://your-router-domain/v1/channels' \
-H 'Authorization: Bearer YOUR_API_KEY'GET/v1/channels/{channel_id}Read one channel
Requires account:read or a browser session. Unknown, foreign or disconnected channels return 404.
PATCH/v1/channels/{channel_id}Change frequency or event preferences
Requires account:write or a same-origin browser session. Omitted fields stay unchanged. At least one field is required; an empty object is invalid. Recipient addresses and report hours cannot be set here.
| Field | Type | Description |
|---|---|---|
report_frequency | string · optional | daily, weekly, monthly or off. Off disables scheduled digests; a manual report can still summarize the previous day. |
events | array · optional | Choose unique IDs from available_events. [] disables optional events. Unknown or duplicate IDs reject the whole update. This is independent of report_frequency; Off affects only scheduled digests. |
{"report_frequency":"weekly","events":[]}POST/v1/channels/{channel_id}/send-reportSend a report now
Requires account:write or a same-origin browser session. This is an explicit message action. No request body is needed. It summarizes the previous complete UTC day, Monday-based week or calendar month; off uses the previous day. Sending is limited to once per channel per minute and 20 times per UTC day, including admitted no-activity results and failed delivery attempts. Reconnecting does not reset these limits. Manual sending does not consume the next scheduled digest.
Returns object: channel.report, sent, cadence and window_label. A successful no-activity window returns sent: false with a message. Missing configuration returns 503. Provider acceptance is not proof of delivery to an inbox or that a person read the message. A limit returns 429 rate_limited with Retry-After; delivery retries return 503 report_pending. Terminal failures also return 503. An unknown acknowledgement is not automatically resent.
GET / POST/api/channels/emailPersonal email connection
Router extension; browser session required. GET returns available, email_verified, email and channel. A same-origin POST requires consent: true and report_frequency; the destination is the account’s current verified email. No arbitrary to address is accepted. An unverified address returns 403 email_verification_required, and missing mail configuration returns 503 email_unavailable. First connection returns 201 with a Channel; an already connected identical configuration returns 200. API keys cannot connect an email channel.
DELETE/api/channels/email/{id}Disconnect in the workspace
Same-origin browser session required. Returns object: channel.disconnected and id. Disconnecting stops future eligible submissions; already submitted email cannot be recalled. There is no public /v1 channel-create or channel-delete operation.
GET / POST/api/channels/{platform}Connect Slack, Discord or Telegram
This browser-only extension accepts slack, discord or telegram. GET returns available, channel and optional pending state. available means that connection configuration is present; a saved channel independently reports sending eligibility through integration_active. POST requires consent: true and return_to set to /dashboard/reports or /dashboard/notifications. It returns url, flow_id and expires_at.
Open the returned Slack or Discord authorization URL to select a destination. For Telegram, open the Bot link, press Start, then read connection status again. Confirm a ready recipient with POST /api/channels/telegram/confirm using flow_id and consent: true. Confirmation requires the same unexpired Router session that began the connection. Clients cannot submit a chat ID or webhook URL.
Connections start with the schedule Off and never send a test message. Use DELETE /api/channels/{platform}/{id} from the signed-in browser to disconnect. This cancels unsent work and clears the local credential; it does not delete a remote webhook or message.
Wallet top-up#
POST/v1/billing/topupCreate a Stripe-hosted checkout
Requires a valid API key with account:write, sent as a Bearer token or X-Api-Key. A browser session alone is insufficient. The customer completes payment on Stripe; this request does not charge or credit the wallet.
| Field | Type | Description |
|---|---|---|
amount_usd | number · required | USD 5–1000 inclusive, with at most two decimal places. Excess precision is rejected without rounding. |
Idempotency-Key | header · optional | 16–100 letters, digits, underscores or hyphens. Reuse for retries of the same top-up and amount. Without it, each HTTP request creates a fresh checkout. |
A successful response has object: billing.topup, amount_usd, checkout_url, session_id, min_usd: 5 and max_usd: 1000. checkout_url is the Stripe-hosted link. Success and cancel return URLs are set by the server; custom URLs are not supported.
curl "$ROUTER_BASE_URL/billing/topup" \
-H "Authorization: Bearer $ROUTER_API_KEY" \
-H 'Content-Type: application/json' \
-H 'Idempotency-Key: topup-example-0001' \
-d '{"amount_usd":25.50}'Set ROUTER_BASE_URL to your API base URL ending in /v1. Replace the example idempotency key for each new top-up intent.
API key management#
GET/v1/keysRead key prefixes and policies
Requires account:read. Returns object: list with data entries; use include_revoked=true to include revoked keys. Secrets and hashes are never returned.
curl 'https://your-router-domain/v1/keys' \
-H 'Authorization: Bearer YOUR_API_KEY'POST/v1/keysCreate a scoped API key
Requires account:write. Returns object: api_key and a one-time key secret. A key caller can grant only its own scope subset and must have no policy restrictions. A signed-in session can create an independently restricted key.
| Field | Description |
|---|---|
name | Required, 1–64 characters. |
scopes | inference, usage:read, account:read, account:write. Defaults to inference and usage:read. |
allowed_models / allowed_ip_cidrs | Arrays of allowed models or IPv4/IPv6 addresses/CIDRs. [] means no restriction; null is invalid. |
expires_at | Future ISO timestamp, or null for no expiry. |
rate_limit_per_minute / daily_request_limit / concurrency_limit | Positive integers or null for unlimited. |
monthly_spend_limit_usd | 0–1,000,000 USD, at most 9 decimal places; null unlimited, zero blocks positive-cost inference. |
DELETE/v1/keys/{key_id}Revoke an owned key
Requires account:write. Self-revocation is allowed. Subsequent authentication with the revoked key fails.
PATCH/api/keys/{id}Edit a policy in the signed-in workspace
Browser session and same-origin request required. Only included fields change; null clears a numeric limit or expiry, [] clears model/IP restrictions. Returns key metadata and policy without a secret.
Routing controls#
These fields belong to router. They are validated and removed before the request reaches a provider.
| Field | Type | Description |
|---|---|---|
min_discount_percent | number | Minimum input and output discount versus verified list price; 0–99.99. |
ranking | enum | balance (default), speed or discount. |
zdr | boolean | Require verified zero-data-retention routes. Adds to the account setting; false cannot override it. No eligible route returns 503. |
Price and privacy determine eligibility. Ranking determines the order of eligible routes. The model ID never selects a private provider.
Errors#
| Field | Description |
|---|---|
400 | Invalid input, routing value or protocol feature. |
401 / 403 | Invalid/expired credentials, forbidden scope/model/IP, or unauthorized origin. |
402 | Insufficient balance. |
404 | Unknown model or inaccessible record. |
429 | Rate, concurrency, daily quota, or key monthly budget restriction. |
502 / 503 / 504 | Upstream failure, no eligible capacity, or timeout. |
Failed and unsettled requests do not expose a settled billed amount. Retain the request ID when reporting an issue.