Documentation
Quickstart
- 1. Sign up
- 2. Top up your balance and create an API key in the dashboard.
- 3. Paste your key into the snippet below and run the request.
curl https://apimira.com/v1/chat/completions \
-H "Authorization: Bearer am-YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "openai/gpt-5.4-mini",
"messages": [
{
"role": "user",
"content": "Hello!"
}
]
}'Pick a model — its ID drops into the code together with the right endpoint. The highlight marks the one and only place your key goes: create it in the dashboard under API keys and replace the placeholder.
| Base URL / Endpoint | https://apimira.com/v1 |
|---|---|
| API key | am-YOUR_KEY |
| Model ID | openai/gpt-5.4-mini |
The base_url is the address above with a /v1 suffix. The model is set via the model parameter.
Copy the Model ID from the catalog in full, including the part before «/»: for example google/gemini-3.7-flash or anthropic/claude-sonnet-4.6.
Connect your apps
Any app with an OpenAI-compatible API connects with three values (Claude Code and the Anthropic SDK are the exception — they speak a different protocol; see the card below):
| Base URL / Endpoint | https://apimira.com/v1 |
|---|---|
| API key | am-YOUR_KEY |
| Model ID | openai/gpt-5.4-mini |
If the field is just «Base URL», the app appends /chat/completions itself — do not add it. And make sure the address does not end up as /v1/v1.
To test the key without an app, run the request from the Quickstart block above.
What the gateway accepts and what it rejects
The gateway rejects strict JSON mode (response_format), the retired functions format and the audio output parameters (audio, modalities) with an unsupported_parameter error. Image input does work on models marked "Sees images": the image travels as a content part of type image_url with a data:image/png;base64,… url. Remote links are never fetched, a model without the capability answers unsupported_capability, and audio input is not supported at all. Neither case is a silent drop: you never pay for an answer produced under different rules than you asked for. Function calling does work: up to 128 tools per request, and calls arrive both in plain responses and in the stream. Plain chat and streaming work across the whole catalogue.
Code editors: Cursor, Cline, Roo Code, Kilo Code
Pick "OpenAI Compatible" in the provider settings and fill in the three values above. Agentic modes, where the extension reads and edits files on its own, do work: the gateway supports function calling. In Cursor the address goes to Settings → Models, the "Override OpenAI Base URL" field, with the key right below it; your own key covers chat and agent, while Tab and inline suggestions stay on Cursor's own models. If the model list does not load, type the Model ID manually.
SillyTavern
API Connections → Chat Completion → Source: Custom (OpenAI-compatible). Put the Base URL into Custom Endpoint, then the key and Model ID. If chat works but the status check fails, enable Bypass API status check.
n8n
Create an OpenAI credential, put our address with /v1 into Base URL and paste the key. Pick the model by its full Model ID in the AI node. The AI Agent node works too: it relies on function calling, and the gateway supports it.
Janitor AI
In the proxy settings set Proxy URL (Base URL with /v1), API Key and Model. Janitor appends /chat/completions itself. Save the proxy configuration first, then the character settings.
OpenCode
Open ~/.config/opencode/opencode.json, add the provider with the config below, run opencode and pick a model via /models.
{
"$schema": "https://opencode.ai/config.json",
"provider": {
"ApiMira": {
"npm": "@ai-sdk/openai-compatible",
"name": "ApiMira",
"options": {
"baseURL": "https://apimira.com/v1",
"apiKey": "am-YOUR_KEY"
},
"models": {
"openai/gpt-5.4-mini": { "name": "GPT-5.4 Mini" },
"anthropic/claude-sonnet-4.6": { "name": "Claude Sonnet 4.6" }
}
}
}
}Claude Code and Anthropic SDK
They connect over the native Anthropic endpoint — POST /v1/messages, and max_tokens is required. The gateway accepts the key both via the Authorization: Bearer header and via x-api-key. Set the gateway address without /v1 — Claude Code appends the path itself, and a stray /v1 turns into a 404. count_tokens returns an estimate rather than an exact count, and the cache_control field is accepted but ignored: manual cache markup is not supported. Caching still works automatically for models that list a cached-input rate on the Models page: when a prompt starts the same way as the previous one, those tokens are billed at the discounted rate, and their count arrives in usage.cache_read_input_tokens. See the Tools section for the full setup with environment variables.
Chat — generate a response
POST /v1/chat/completions
Generates a model response. Supports stream=true (SSE). Request and response formats match OpenAI.
Streaming: pass "stream": true. Chunks arrive as data: {...} and end with data: [DONE].
Supported: text chat (including tool calls via tools and tool_choice), image input on models that have the capability, image generation and editing, video generation. In chat, response_format, functions, audio and modalities still answer 400 — a parameter is never dropped silently.
Request parameters
Body of POST /v1/chat/completions. Only model and messages are required — everything else has a sensible default.
| Field | Type | What it does |
|---|---|---|
| modelrequired | string | Model ID from the catalogue — in full, including the prefix before the slash. |
| messagesrequired | array | The conversation as a list: every message has a role (system, user, assistant, tool) and content. The model remembers nothing between requests — send the history you need here. |
| stream | boolean | true — the answer arrives as it is generated. Chunks come as data: {…} lines, the stream ends with data: [DONE]. |
| temperature | 0 … 2 | How varied the answers are: lower is more predictable and dry, higher is freer and more diverse. |
| top_p | 0 … 1 | Another way to control variety. Change one or the other — temperature or top_p, not both. |
| max_tokens | ≥ 1 (max 32 000) | Cap on the answer length; anything above 32 000 is trimmed to 32 000. It also sets the size of the hold — see Pricing and charges. |
| max_completion_tokens | ≥ 1 (max 32 000) | The same thing under the newer OpenAI name. If both arrive, max_completion_tokens wins. |
| stop | string | string[] | Up to four strings that cut generation short. The string itself never appears in the answer. |
| stream_options | object | {"include_usage": true} adds the token counter to the last chunk of the stream. |
| tools | array (≤ 128) | Descriptions of functions the model may call. Calls arrive in choices[].message.tool_calls. |
| tool_choice | string | object | none — never call, auto — the model decides, required — must call something, an object — call that specific function. |
| user | string | Your own end-user identifier. Passed through untouched and does not affect the price. |
These parameters are rejected
functions, response_format, audio, modalities — answered with 400 and the code unsupported_parameter. The refusal is deliberate: silently ignoring a parameter and then billing you for an answer produced under different rules is worse than an honest error.
What comes back
The shape matches OpenAI, so the official SDKs parse it with no changes.
| Field | Type | What it does |
|---|---|---|
| id | string | Request identifier. Worth quoting when you contact support. |
| model | string | ID of the model that answered. |
| choices[].message.content | string | null | The answer text. null when the model called a tool instead of writing. |
| choices[].message.tool_calls | array | Function calls, if the model made any. |
| choices[].finish_reason | string | Why generation stopped: stop — the model finished, length — it hit max_tokens, tool_calls — it wants a tool. |
| usage.prompt_tokens | number | Input tokens — the first half of the bill. |
| usage.prompt_tokens_details.cached_tokens | number | How many input tokens came from the cache — they are already part of prompt_tokens; for models with a cached-input rate they are billed at the discounted rate. |
| usage.completion_tokens | number | Output tokens — the second half of the bill. |
| usage.total_tokens | number | Input plus output. |
Image input
A model that can look at pictures accepts them right in the conversation: a screenshot of an error, a photo of a receipt, a mockup. No separate endpoint is needed — the image travels as a part of the message next to the text, through the same POST /v1/chat/completions. The same works on /v1/messages and /v1/responses.
How to send an image
In a user message, content becomes an array of parts: text is a part of type text, an image is a part of type image_url whose url is a string shaped like data:image/png;base64,… — the image type and its bytes in base64. The order of parts is preserved: the model sees them in the order you sent them. The detail field (auto, low, high) is passed to the model as is. A message may consist of images only, with no text.
{
"role": "user",
"content": [
{ "type": "text", "text": "What does this screenshot say?" },
{
"type": "image_url",
"image_url": { "url": "data:image/png;base64,iVBORw0KGgo…", "detail": "auto" }
}
]
}The gateway never fetches remote links: an image behind http or https returns 400 invalid_request with a hint. Encode the file to base64 yourself — that is one line in any language.
curl https://apimira.com/v1/chat/completions \
-H "Authorization: Bearer am-YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "anthropic/claude-sonnet-5",
"messages": [{ "role": "user", "content": [
{ "type": "text", "text": "What does this screenshot say?" },
{ "type": "image_url", "image_url": { "url": "data:image/png;base64,'"$(base64 -w0 shot.png)"'" } }
]}]
}'Limits
- — Formats: PNG, JPEG, WebP. The type is checked against the first bytes of the file, not against its name: a "png" with something else inside returns 400 here, before the model.
- — One image — up to 5 MB of binary data; in base64 the same file takes about a third more.
- — Up to 50 images per request — every image in the conversation counts, not just the ones in the last message.
- — A request body with images — up to 24 MB. This limit outranks the number of images: 50 of them only fit when they are small.
- — A body over 2 MB is read only for a working key with a positive balance, and the upload has 30 s to finish — after that 408 request_timeout with a Retry-After header. The number of large uploads in flight is capped: the next one gets 429 with Retry-After, so retry it after the stated delay.
- — More than 20 images in one request: some models then accept only small ones — up to 2000 px on the long side.
Which models see images
The capability is confirmed by a live probe rather than by a promise: today 38 models of the catalogue accept images. The same ones carry the "Sees images" mark on the Models page, and GET /v1/account/pricing returns them in the capabilities field. A model without the capability answers 400 unsupported_capability and is never charged.
openai/gpt-6-astra · openai/gpt-5.6-sol · openai/gpt-5.6-terra · openai/gpt-5.6-luna · openai/gpt-5.5 · openai/gpt-5.4-mini · openai/gpt-5.3-codex · anthropic/claude-fable-5.1 · anthropic/claude-fable-5 · anthropic/claude-fable-5-compressed-context · anthropic/claude-opus-5 · anthropic/claude-opus-4.8 · anthropic/claude-opus-4.7 · anthropic/claude-opus-4.6 · anthropic/claude-opus-4.5 · anthropic/claude-sonnet-4.6 · anthropic/claude-sonnet-4.5 · anthropic/claude-haiku-4.5 · google/gemini-3.7-flash · google/gemini-3.6-flash · google/gemini-3.1-pro-preview · google/gemini-3.1-flash-lite · google/gemini-3-flash-preview · google/gemini-2.5-pro · x-ai/grok-4.6 · x-ai/grok-4.5 · x-ai/grok-4.3 · x-ai/grok-build-0.1 · z-ai/glm-5.3 · z-ai/glm-5.3-flash · z-ai/glm-5.1 · moonshotai/kimi-k3 · moonshotai/kimi-k2.7-code · moonshotai/kimi-k2.5 · qwen/qwen3.8-max · xiaomi/mimo-v2.5-pro · xiaomi/mimo-v2.5 · minimax/minimax-m3
What an image costs
An image is billed as input tokens of the model. How many is up to the model: usually around 1 089 tokens for a 1024×1024 frame, and several times more on some models. You are charged exactly what the model returned in usage.prompt_tokens — the same formula as for text. A hold with a margin is placed for the request and the remainder returns to the balance (see Pricing and charges). An estimate of the same order comes back from POST /v1/messages/count_tokens.
Images from agents
An image produced by a tool result — the agent read an image file or took a screenshot — reaches the model as well. In the OpenAI protocol a tool result carries text only, so the gateway forwards such images as a separate user turn right after the series of results; the order of the conversation is preserved. Nothing to configure.
On /v1/responses an image is an input_image part; detail: original is treated as high. On /v1/messages it is an image block with a base64 source; a url source is not fetched, exactly as in chat. document and file blocks still answer 400: file input is not accepted yet.
Rejections
400 invalid_request — wrong format, bytes that do not match the declared type, an image over the size or count limit, an image in a system, developer or assistant message. 400 unsupported_capability — the model does not accept images. 400 context_length_exceeded — the images plus the text do not fit the model context. 408 request_timeout — the body did not arrive in full in time. 413 payload_too_large — the body is over the limit. None of these rejections is billed.
Privacy
Image bytes are never stored: they live in memory for the duration of the request and go to the model. Statistics keep their number only — the images_in field in GET /v1/account/usage.
Image generation
POST /v1/images/generations
POST /v1/images/generations — synchronous, request and response are OpenAI Images-compatible. A fixed price per image is charged, multiplied by the count (n).
curl https://apimira.com/v1/images/generations \
-H "Authorization: Bearer am-YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "openai/gpt-image-2",
"prompt": "a red cat wearing glasses",
"n": 1
}'The model must be an image model (see GET /v1/models). The response is a data array, each item carrying b64_json: the frame as base64. We never hand out a link, even when the model returns one — the frame arrives as bytes. Token-priced models also return usage: exactly the numbers you were charged for. The x-request-id response header is the id of the charge record, so the request can be found in GET /v1/account/usage.
Image editing
POST /v1/images/edits
POST /v1/images/edits changes an existing image from a text prompt: replace the background, add a detail, follow a style. The body is multipart/form-data, as in the OpenAI Images API: a file (or several) plus a prompt. The response is a base64 frame.
curl https://apimira.com/v1/images/edits \
-H "Authorization: Bearer am-YOUR_KEY" \
-F model=openai/gpt-image-2 \
-F prompt="Replace the background with a light gradient" \
-F image=@cat.pngResponse:
{
"created": 1757700000,
"data": [{ "b64_json": "iVBORw0KGgo…" }],
"usage": { "input_tokens": 116, "output_tokens": 1650, "total_tokens": 1766 }
}The model picks the format of the frame: one returns PNG, another JPEG. We neither promise nor convert it — read data[0].b64_json as image bytes and, if the type matters, detect it from the first bytes.
The usage field is not always there: a per-frame model returns none at all — you pay for the frame, not for tokens. The input_tokens_details and output_tokens_details breakdown appears only when the model provided it: check for the field instead of relying on it.
Request fields
| Field | Type | What it does |
|---|---|---|
| modelrequired | string | An image model that supports editing. A chat model answers 404 model_not_found, an image model without the capability answers 400 unsupported_capability. |
| promptrequired | string (≤ 4000) | What to do with the image. |
| imagerequired | file × 1 … 4 | A file: PNG, JPEG or WebP by its first bytes, up to 5 MB each. One image goes as image, several as image[] (that is how the OpenAI SDK sends them), up to 4 files per request. |
| n | 1 … 4 | How many frames to return; one by default. A live probe covered one frame per request — we have not measured how the models behave with more. |
| size | 1024x1024 | Only 1024x1024, or leave the field out and the model picks the size. Other values return 400: the price of a frame depends on its size. |
| response_format | b64_json | Only b64_json, which is also the default. url returns 400 unsupported_parameter: we have no storage, so there is no link to hand out. |
Answered with 400 naming the field
mask, output_format, output_compression, partial_images, stream set to true, background: transparent, input_fidelity: high. All of them change the bytes of the result or its price at the model — swallowing them silently would mean handing back something other than what was ordered. A mask is not supported on any model in this version: send the whole image instead.
Accepted and not forwarded to the model
quality, user, background set to opaque or auto, input_fidelity set to low. A request carrying them works, but the result and the price stay as they would be without them.
Pricing and charges
Pricing matches generation on the same model; there is no separate tariff for editing. On a token-priced model input and output are counted at its rates, and usage in the response shows exactly the numbers you were charged for. On a per-frame model the price of a frame is multiplied by the number of frames. A hold with a margin is placed for the request and the remainder returns; a model failure costs nothing.
The Content-Length header is required: an upload without it (chunked) returns 400 invalid_request. If your client streams the file, read it into memory or set the size yourself.
The response carries an x-request-id header — the id of the charge record. It finds the request in GET /v1/account/usage (the id field) and in the dashboard; the OpenAI SDK exposes it as request_id.
Which models edit images
Editing is enabled on 2 models of the catalogue today. They carry the "Editing" mark on the Models page, and GET /v1/account/pricing returns the image_edit capability in the capabilities field.
openai/gpt-image-2 · google/gemini-3.1-flash-image-preview
Video generation
POST /v1/videos/generations
Video is generated asynchronously: the request creates a job and returns its id right away. The seconds parameter is required — it sets the clip length and drives the price.
curl https://apimira.com/v1/videos/generations \
-H "Authorization: Bearer am-YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "alibaba/wan-2.6",
"prompt": "a paper boat drifting on a calm pond",
"seconds": 5
}'
# {"id": "9b2f…", "status": "processing", "created": 1755648000}GET /v1/jobs/{id}
Poll for readiness every 5–10 seconds. status: processing — still running, succeeded — done (the response carries the clip url), failed — did not work out (see error). Generation takes 30 seconds to 3 minutes. The url points to the provider’s storage — download the file right away.
curl https://apimira.com/v1/jobs/JOB_ID \
-H "Authorization: Bearer am-YOUR_KEY"
# {"id": "9b2f…", "status": "succeeded", "url": "https://…/clip.mp4"}The charge is price per second × seconds. Money is reserved when the job is created and debited only once the clip is ready; if generation fails, the reserve is returned in full.
Models and durations
| Model | Seconds | Resolution | Audio |
|---|---|---|---|
| alibaba/wan-2.6 | 2–10 | 720p | yes |
| bytedance/seedance-2.0-fast | 4–15 | 720p | no |
| bytedance/seedance-2.0 | 4–15 | 720p | no |
| google/veo-3.1-fast | 4–8 | 1080p | no |
| google/veo-3.1 | 4–8 | 1080p | no |
The model must be of type “video” (see GET /v1/models). Per-second prices are on the models page.
Endpoints
POST /v1/chat/completions
Generates a model response. Supports stream=true (SSE). Request and response formats match OpenAI.
GET /v1/models
Lists available models in OpenAI format. Disabled models are not returned.
POST /v1/images/generations
Synchronous image generation (OpenAI Images-compatible). The frame arrives as base64.
POST /v1/images/edits
Edits an existing image from a prompt: multipart/form-data in, a base64 frame out.
POST /v1/videos/generations
Creates a video generation job. Billed per second; the seconds parameter is required. Returns a job with status processing.
GET /v1/jobs/{id}
Generation job status. processing — still running, succeeded — done (clip url in the response), failed — did not work out (nothing is charged).
POST /v1/messages
Native Anthropic protocol — compatible with Claude Code and the Anthropic SDK unchanged. Auth works like native Anthropic clients: the x-api-key header (Bearer is accepted too). Supports streaming (SSE) and tool calls; max_tokens is required.
POST /v1/messages/count_tokens
Estimates input token count without calling the model — not an exact number, and it does not spend your balance.
POST /v1/responses
OpenAI Responses API format — compatible with Codex CLI and the OpenAI SDK unchanged. Stateless: the client sends the history, previous_response_id is not supported, responses are not stored by id (GET and DELETE return 404). Function tools and streaming (SSE) are supported.
GET /v1/account/{balance,pricing,usage,analytics}
Read-only account API: balance, retail prices, key usage detail and analytics. See the Account API section for details.
Pricing and charges
How it works
- — You top the balance up in advance — no month-end invoices and no subscriptions.
- — Every request is charged its own cost, which depends on the model and on the number of tokens.
- — The balance drops right after the answer. When it runs out, requests answer with 402 — the account never goes negative.
How the cost is calculated
Each model has its own price, quoted per 1 million tokens — separately for input (your request) and output (the model's answer). Output is almost always the dearer half. Current prices for every model live in the Models and pricing catalogue.
input = input_tokens / 1 000 000 × input_price output = output_tokens / 1 000 000 × output_price ──────────────────────────────────────────────────── total = input + output
Each half is rounded up to one millionth of a dollar. The token counts come from the model's own answer — the very usage field you see in your code.
Worked example
model: openai/gpt-5.4-mini input price: $0.12 per 1M tokens output price: $0.64 per 1M tokens request: 1 200 input tokens, 800 output tokens input 1 200 / 1 000 000 × $0.12 = $0.000144 output 800 / 1 000 000 × $0.64 = $0.000512 ──────────────────────────────────────────────── charged: = $0.000656
The prices above are not made up — they are pulled from the live catalogue, the same one the bill uses.
Cache discount
When a request starts with the same text as the previous one — a system prompt, instructions, a long context — the model serves that part from its cache, and we bill those tokens at a discounted rate. There is nothing to switch on: the cache kicks in by itself when an identical prefix arrives back to back and is long enough (roughly a thousand tokens or more). The first request, the one that writes the cache, does not cost extra: it is billed exactly as it would be without a cache.
input = (input_tokens − cached_tokens) / 1 000 000 × input_price
+ cached_tokens / 1 000 000 × cached_input_priceThe number of tokens served from cache is in the response: usage.prompt_tokens_details.cached_tokens, or usage.cache_read_input_tokens on the /v1/messages endpoint. The same figures appear in the dashboard, in GET /v1/account/usage and in the CSV export. The cached-input rate is listed on the Models page and in GET /v1/account/pricing. Not every model gets the discount: where no rate is listed, the cache is billed at the regular input price.
The hold while a request runs
While the model is answering, nobody knows how long the answer will be. So before we call the model, the worst case is held on your balance:
hold = input_tokens / 1 000 000 × input_price
+ max_tokens / 1 000 000 × output_priceIf the hold does not fit, the request is refused straight away, before the model is called. Once the answer arrives, the real cost is charged and the rest of the hold returns to the balance the same moment.
same request, max_tokens: 4 000 held: $0.002704 actually charged: $0.000656 returned: $0.002048
Set max_tokens close to the answer length you expect: the bigger it is, the bigger the hold and the less balance is left for parallel requests. With no max_tokens the hold is computed against the 32,000-token ceiling.
When you are not charged
An error on the model's side (502, 504) costs nothing and the hold returns in full. A refused request — wrong key, empty balance, unsupported parameter, oversized body — is free too: it never reached the model. Token estimation via /v1/messages/count_tokens is always free.
Where to watch your spend
In the dashboard. The key page shows a log of requests with model, tokens and cost for each, a CSV export and a daily chart. The Usage section shows a summary for the period and a breakdown by model. We never store the text of requests or answers — the statistics hold only these counters.
Account API — balance, prices, and key statistics
A read-only API for your account: balance, retail prices, and key usage from your own backend — no dashboard needed. The dashboard section "Account management" collects the base URL and ready-made snippets.
Authorize with the same key you use for model requests: the Authorization: Bearer header. GET only; the key must be active (paused and revoked keys get 401). Never put the key in a URL or browser code. Statistics are scoped to the key making the request; balance and prices are account-wide.
GET /v1/account/balance60/min
Current account balance in dollars (active holds already deducted).
GET /v1/account/pricing30/min
Retail prices of the models from GET /v1/models: per 1M tokens (input, cached input, output) or per generation unit (per second for video). The capabilities field names what a model can do, machine-readably: vision — accepts image input, image_edit — edits images. Provider costs and margins are never exposed.
GET /v1/account/usage20/min
Per-request detail for the key: model, operation, status, tokens (with the share served from cache listed separately), the number of images in the input (images_in), charge, latency, and price snapshots taken at request time. Newest first.
GET /v1/account/analytics10/min
Key aggregates for the period: totals, per-model and per-day (UTC) breakdowns, gateway errors by code.
Query parameters
All parameters are optional. Out-of-range values return 400 invalid_request with the parameter name in param.
| Field | Type | Default | What it does |
|---|---|---|---|
| daysusage · analytics | 1 … 90 | 30 | Look-back window in days from the current moment. |
| limitusage | 1 … 100 | 50 | Detail page size. |
| pageusage | 0 … 200 | 0 | Zero-based page number. has_more in the response tells you whether there is a next one. |
Examples
curl https://apimira.com/v1/account/balance \
-H "Authorization: Bearer am-YOUR_KEY"
# {"object": "account.balance", "balance_usd": 4.981234, "currency": "USD"}curl "https://apimira.com/v1/account/usage?days=7&limit=25" \ -H "Authorization: Bearer am-YOUR_KEY"
Response:
{
"object": "list",
"days": 7,
"limit": 25,
"page": 0,
"has_more": false,
"data": [
{
"id": "9b2f…",
"created_at": "2026-08-21T09:58:12.000Z",
"model": "openai/gpt-5.4-mini",
"operation": "chat",
"status": "success",
"tokens_in": 1200,
"cached_tokens_in": 0,
"images_in": 0,
"tokens_out": 800,
"cost_usd": 0.000656,
"price_input_usd_per_1m_tokens": 0.12,
"price_cached_input_usd_per_1m_tokens": 0.0168,
"price_output_usd_per_1m_tokens": 0.64,
"price_generation_usd": null,
"latency_ms": 812
}
]
}The /v1/account/analytics response is a single object with:
totals · models[] · daily[] · errors.by_code[]
Privacy
The detail feed contains metadata only. Prompt and response bodies are never stored at all, so they cannot be returned by the API or anything else. IPs and client data are not exposed either.
Errors
The error envelope is shared across the gateway (see the Errors section). You may encounter:
401 invalid_api_key · 400 invalid_request · 429 rate_limited
Limits and restrictions
The limits protect you as much as the gateway: a stray loop in your code should not burn the balance in a minute.
| Restriction | Value |
|---|---|
| Request rate per key | 60 in a burst, then 10 per second |
| Request body size: chat and /v1/messages | 2 MB |
| Request body size with images: chat, /v1/messages, /v1/responses, image editing | 24 MB |
| Request body size: images, video, music | 256 KB |
| Prompt length — all messages and tool schemas together | 400 000 characters |
| Maximum tokens in the answer (max_tokens) | 32 000 tokens |
| Tools in a single request | 128 |
| Stop sequences | 4 |
| Spend limit per key: day, week, month | you set it |
| Key schedule: the hours and days it works | you set it |
Going over the rate gives 429 rate_limited: wait and retry, the key is neither blocked nor penalised. A body over 2 MB is read only for a working key with a positive balance: the upload has 30 s, and the number of large uploads in flight is capped — an extra one gets 429 with a Retry-After header. Spend limits and the schedule are yours to set in the dashboard on the key page. Guessing at wrong keys from one address is slowed down on top of that.
Error codes
Every error arrives in the same shape as OpenAI's, so the SDKs parse it for you:
{
"error": {
"message": "The model `openai/gpt-9` does not exist or is disabled.",
"type": "invalid_request_error",
"code": "model_not_found",
"param": "model"
}
}Branch on code: it is machine-readable and stable. The message text is written for a human and may change. The param field points at the specific request field when the error is in one.
| HTTP | code | When it happens | What to do |
|---|---|---|---|
| 400 | invalid_request | The body did not parse: wrong JSON, a missing required field, or a value outside the allowed range. | Check the Request parameters section: the param field names exactly what did not fit. |
| 400 | unsupported_parameter | The request carries a parameter the gateway does not support yet. | Drop the parameter — the incompatible ones are listed under Request parameters. |
| 400 | unsupported_capability | The model cannot do what the request asks for: image input or image editing. | Take a model with the right mark on the Models page, or with the capability in GET /v1/account/pricing. The request was not billed. |
| 400 | context_length_exceeded | The request does not fit the model context — conversation, images and tool schemas together. | Shorten the conversation or the number of images, or take a model with a larger context — it is listed on the Models page. |
| 401 | invalid_api_key | The key is wrong, revoked or missing. | Make sure the key was copied in full, including the am- prefix, and has not been revoked. |
| 402 | insufficient_balance | Your balance is empty — top it up in the dashboard. | Top up the balance. Lowering max_tokens helps too — it shrinks the hold. |
| 403 | key_schedule_blocked | This API key is outside its allowed schedule. Adjust it at the dashboard (/app/keys). | Change the key's schedule in the dashboard, or use another key. |
| 404 | model_not_found | The model does not exist or is disabled. See GET /v1/models. | Take the ID from GET /v1/models in full, including the prefix before the slash. |
| 404 | job_not_found | No generation job with that id. | Check the id returned when the job was created. |
| 404 | not_found | Nothing lives at this address. /v1/responses are not stored, so they cannot be fetched or deleted by id. | Check the URL. Keep the conversation on the client and send the full input with every request. |
| 408 | request_timeout | The request body did not arrive in full in the allotted time. | Retry: the Retry-After header says after how long. On a slow link, make the images smaller. |
| 413 | payload_too_large | Request body is too large. | Shorten the conversation or split it across several requests. |
| 429 | rate_limited | Too many requests from this key — wait and retry. Throughput levels out automatically. | Wait and retry. A pause that grows with each attempt works well. |
| 429 | spend_limit_exceeded | The spend limit for this API key is reached. Raise or remove it at the dashboard (/app/keys). | Raise or remove the limit in the dashboard, on the key page. |
| 500 | internal_error | A failure on our side. | Retry. If it keeps happening, contact support and quote the id from the response. |
| 502 | provider_not_configured | The model has no working route to a provider. | Pick another model and tell us: this error should not happen. |
| 502 | upstream_error | The model provider failed. You are not charged for such a request. | Retry, or try a different model. You were not charged. |
| 504 | upstream_timeout | The model's provider did not answer in time. | Retry. For long answers turn on stream — a stream does not run into the timeout. |
Nothing in this table is billed. If the request never reached the model, or the model answered with an error, no money is taken and the hold returns to the balance in full.
Common questions
How do I switch models?
Change the model parameter. The rest of your code stays the same — the request goes to the right provider.
Does the official OpenAI SDK work?
Yes. Point base_url at our address with /v1 and use our key — the rest of the code stays unchanged.
How is the cost calculated?
By input and output tokens, at the model's price at the time of the request. The formula and worked examples are in Pricing and charges.
Are there request limits?
Yes. A rate limit on every key, plus spend limits in money that you set yourself. All the numbers are in the Limits section.