First steps
Evrona is compatible with the OpenAI API: if you already have code on the official SDK, change the address and the key and it will work.
- 01
- 02
Put money on the balance and issue an API key in the dashboard.
- 03
Insert the key into the snippet below and send the request.
curl https://evrona.ru/v1/chat/completions \
-H "Authorization: Bearer ev-YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "openai/gpt-5.4-mini",
"messages": [
{
"role": "user",
"content": "Hi there!"
}
]
}'Pick a model: its ID and the right endpoint drop into the code by themselves. The highlight shows the one and only place your key lands — issue it in the dashboard, on the keys page, and put it in place of the placeholder.
| Base URL / Endpoint | https://evrona.ru/v1 |
|---|---|
| Your API key | ev-YOUR_KEY |
| Model ID | openai/gpt-5.4-mini |
The base_url is the address above with the /v1 suffix appended. Which model answers is decided by the model parameter.
The Model ID is copied from the catalogue whole, without cutting the part before «/»: say, google/gemini-3.7-flash or anthropic/claude-sonnet-4.6.
How to connect an app
Three values are enough to set up any app with an OpenAI-compatible API (Claude Code and the Anthropic SDK fall outside this: they speak a protocol of their own, see the card below):
| Base URL / Endpoint | https://evrona.ru/v1 |
|---|---|
| Your API key | ev-YOUR_KEY |
| Model ID | openai/gpt-5.4-mini |
When the field is just «Base URL», the app will append /chat/completions on its own — there is no need to write it by hand. And check that the address has not turned into /v1/v1.
Checking the key without any app is easy: send the request from the First steps block above.
An unsupported_parameter error is what the gateway returns for strict JSON mode (response_format), for the retired functions format and for the audio output parameters (audio, modalities). Image input is open to models marked "Understands images": the frame travels as a content part of type image_url carrying a data:image/png;base64,… url. Remote links are never downloaded; a model without the capability answers unsupported_capability, and there is no audio input at all. None of these cases slips by silently — you never pay for an answer produced under rules other than yours. Function calling works: up to 128 tools per request, and the calls arrive both in plain responses and in the stream. Simple chat and streaming are available across the whole catalogue.
Step-by-step guides
Claude Code
Codex CLI- Aider
Qwen Code
OpenCode
Hermes Agent
Cursor
Cline
n8n
Telegram Bot
OpenClaw
LangChain
LlamaIndex
Code editors and extensions: Cursor, Cline, Roo Code, Kilo Code
Point the provider settings at "OpenAI Compatible" and fill in the three values above. Agentic modes, in which the extension reads and edits files by itself, are operational: the gateway supports function calling. In Cursor the address is typed into Settings → Models, the "Override OpenAI Base URL" field, and the key goes right there as well; your key covers chat and agent, while Tab and in-code suggestions stay on Cursor’s own models. If the model list did not pull in, type the Model ID by hand.
SillyTavern
Walk through API Connections → Chat Completion → Source: Custom (OpenAI-compatible). The Base URL goes into Custom Endpoint, with the key and Model ID below it. When chat answers but the status check fails, enable Bypass API status check.
n8n
Set up an OpenAI credential, write our address with /v1 into Base URL and paste the key. The model in the AI node is chosen by its full Model ID. The AI Agent node is fine as well: it rests on function calling, which the gateway supports.
Janitor AI
In the proxy settings fill in Proxy URL (that is the Base URL with /v1), API Key and Model. Janitor writes /chat/completions in by itself. Save the proxy configuration first and only then the character settings.
OpenCode
Describe the provider in ~/.config/opencode/opencode.json with the config below, then run opencode and pick a model with the /models command.
{
"$schema": "https://opencode.ai/config.json",
"provider": {
"Evrona": {
"npm": "@ai-sdk/openai-compatible",
"name": "Evrona",
"options": {
"baseURL": "https://evrona.ru/v1",
"apiKey": "ev-YOUR_KEY"
},
"models": {
"openai/gpt-5.4-mini": { "name": "GPT-5.4 Mini" },
"anthropic/claude-sonnet-4.6": { "name": "Claude Sonnet 4.6" }
}
}
}
}Anthropic SDK and Claude Code
They run over the native Anthropic endpoint — POST /v1/messages, where max_tokens is required. The gateway takes the key both from the Authorization: Bearer header and from x-api-key. Give the gateway address without /v1: Claude Code appends the path itself, and a stray /v1 ends in a 404. count_tokens hands back an estimate rather than an exact count; the cache_control field is accepted but passed over — marking the cache up by hand is not possible. Caching does switch itself on for models that carry a cached-input rate on the Models page: when a prompt opens the same way as the previous one, those tokens go at the discounted rate, and their count arrives in usage.cache_read_input_tokens. The step-by-step setup with environment variables sits in the Integrations section.
Text chat — the model’s answer
A model answer to a conversation. Handles stream=true (SSE). Request and response formats are the same as OpenAI’s.
To turn the stream on, pass "stream": true. Chunks go as data: {...}, and at the end comes data: [DONE].
What works: text chat together with tool calls via tools and tool_choice, image input on models that have the capability, creating and editing images, creating video. In chat, response_format, functions, audio and modalities give 400 as before: we will not swallow a parameter silently.
Fields of the chat request
This is the body of POST /v1/chat/completions. There are just two required fields, model and messages; the rest may be omitted, since each has a sensible default.
| Field name | Value type | What it is for |
|---|---|---|
modelalways needed | string | Taken from the catalogue whole — the prefix before the slash must not be cut. |
messagesalways needed | array | The conversation as a list, where every message carries a role (system, user, assistant, tool) and content. The model recalls nothing from earlier requests, so put all the history you need in here. |
stream | boolean | With true the answer arrives in a stream, as it is generated. Chunks come in as data: {…} lines, and data: [DONE] closes the stream. |
temperature | 0 … 2 | How much the answers differ: the lower it is, the more predictable and dry; the higher, the freer and more diverse. |
top_p | 0 … 1 | A second way to control variety. Turn only one of the two: either temperature or top_p, never both. |
max_tokens | ≥ 1 (max 32 000) | A ceiling on the answer length: anything above 32 000 the gateway trims to 32 000. The size of the reserve depends on it — see Rates and charges. |
max_completion_tokens | ≥ 1 (max 32 000) | The same field under the newer OpenAI name. When both arrive, max_completion_tokens is the one that applies. |
stop | string | string[] | No more than four strings at which generation stops. The string itself does not show up in the answer. |
stream_options | object | With {"include_usage": true} the token counter arrives in the last chunk of the stream. |
tools | array (≤ 128) | Descriptions of the functions available for the model to call. The calls themselves arrive in choices[].message.tool_calls. |
tool_choice | string | object | none — do not call at all, auto — leave the decision to the model, required — a call is obligatory, an object — call exactly that function. |
reasoning_effort | none | minimal | low | medium | high | xhigh | max | How long the model reasons before answering. On reasoning models a low level answers faster and cheaper, a high level more thoroughly; how strong the effect is depends on the model itself. Models without reasoning accept the parameter and answer as usual, while on Grok models it is not applied yet. A value outside the list gives 400. |
user | string | An end-user identifier of your own. We forward it untouched, and it has no effect on the price. |
functions, response_format, audio, modalities — the answer will be 400 with the code unsupported_parameter. The refusal is on purpose: quietly skipping a field and then billing you for an answer assembled under other rules is worse than an honest error.
What the answer holds
The shape is the same as OpenAI’s, so the official SDKs parse it without any changes.
| Field name | Value type | What it is for |
|---|---|---|
id | string | The request identifier — worth naming when you write to support. |
model | string | The ID of the model that gave the answer. |
choices[].message.content | string | null | The answer text; null when the model reached for a tool instead of writing. |
choices[].message.tool_calls | array | Function calls — if the model got as far as making any. |
choices[].finish_reason | string | What stopped the generation: stop — the model talked itself out, length — it reached max_tokens, tool_calls — it is asking for a tool. |
usage.prompt_tokens | number | Input tokens: they make up the first half of the bill. |
usage.prompt_tokens_details.cached_tokens | number | How many input tokens the cache served. They are already counted inside prompt_tokens, and on models with a cached-input rate they go at the discounted price. |
usage.completion_tokens | number | Output tokens: they are the second half of the bill. |
usage.total_tokens | number | The two halves added up. |
Images in a request
A model able to look at pictures takes them right inside the conversation: a screenshot of an error, a photo of a receipt, a mockup. No separate endpoint is needed — the frame travels as a part of the message next to the text, in the same POST /v1/chat/completions. On /v1/messages and /v1/responses it is exactly the same.
How to attach an image
In a user message, content turns into an array of parts: text is a part of type text, an image is a part of type image_url where url is a string shaped like data:image/png;base64,… — the image type plus its bytes in base64. The order of parts does not change: the model sees them exactly as you sent them. The detail field (auto, low, high) goes to the model as is. A message may consist of images alone, with no text at all.
{
"role": "user",
"content": [
{ "type": "text", "text": "What text is on this screenshot?" },
{
"type": "image_url",
"image_url": { "url": "data:image/png;base64,iVBORw0KGgo…", "detail": "auto" }
}
]
}The gateway does not pull remote links: an image behind http or https will return 400 invalid_request with a hint. Turn the file into base64 on your side — in any language that is a single line of code.
curl https://evrona.ru/v1/chat/completions \
-H "Authorization: Bearer ev-YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "anthropic/claude-sonnet-5",
"messages": [{ "role": "user", "content": [
{ "type": "text", "text": "What text is on this screenshot?" },
{ "type": "image_url", "image_url": { "url": "data:image/png;base64,'"$(base64 -w0 shot.png)"'" } }
]}]
}'What is allowed
- Formats: PNG, JPEG, WebP. The type is read from the first bytes of the file rather than from its name: a "png" with something else inside gets 400 right here and never reaches the model.
- Per image — no more than 5 MB of binary data; in base64 the same file swells by about a third.
- No more than 50 images per request, and every image of the conversation counts, not only those in the last message.
- A request body with images — no more than 24 MB. This cap outranks the image count: 50 of them get through only if they are small.
- A body larger than 2 MB is read only under a working key with a positive balance, and the upload is given 30 s — past that comes 408 request_timeout with a Retry-After header. Only a limited number of large uploads run at once: the next one will get 429 with Retry-After, so repeat it after the stated delay.
- Above 20 images in one request some models take only small ones — up to 2000 px on the longer side.
Which of the models look at pictures
The capability is confirmed by a live probe rather than by a promise: today 40 models of the catalogue accept images. The same ones carry the "Understands images" mark on the Models page, and GET /v1/workspace/rates returns them in the capabilities field. A model without the capability answers 400 unsupported_capability and is never charged.
What a picture ends up costing
An image is billed as the model’s input tokens, and how many there are is the model’s own call: usually on the order of 1 089 tokens for a 1024×1024 frame, several times more on some models. We charge exactly what the model returned in usage.prompt_tokens — the formula is the same as for text. A reserve with a margin is set aside for the request and the surplus returns to the balance (see Rates and charges). An estimate of the same order is handed back by POST /v1/messages/count_tokens.
Pictures coming from an agent
An image produced by a tool result — the agent opened an image file or grabbed a screenshot — gets through to the model as well. In the OpenAI protocol a tool result carries text alone, so the gateway sends such images on as a separate user turn right behind the series of results, and the order of the conversation does not suffer for it. There is nothing to configure.
On /v1/responses an image is an input_image part, and detail: original counts as high. On /v1/messages it is an image block with a base64 source; a url source is not pulled, just as in chat. document and file blocks still give 400: file input is not accepted yet.
When a rejection comes
400 invalid_request — the format is wrong, the bytes do not match the declared type, the size or count of images is exceeded, an image ended up in a system, developer or assistant message. 400 unsupported_capability — the model takes no images. 400 context_length_exceeded — the images together with the text do not fit the model context. 408 request_timeout — the body was not read in full within the allotted time. 413 payload_too_large — the body went past the limit. Not one of these rejections is billed.
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/workspace/spend.
Creating images
POST /v1/images/generations works synchronously, and its request and response are OpenAI Images-compatible. You are charged a fixed price for one image, multiplied by the count (n).
curl https://evrona.ru/v1/images/generations \
-H "Authorization: Bearer ev-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/workspace/spend.
The prompt may be at most 15 000 characters long. Generation does not take the fields image, images, mask, aspect_ratio, image_size, resolution: the answer will be 400 unsupported_parameter. If you need an image on the input, call POST /v1/images/edits instead.
Editing an existing picture
POST /v1/images/edits reworks an existing image from a text prompt: swap the background, add a detail, follow a style. The body goes as multipart/form-data, after the OpenAI Images API: one file or several, plus a prompt. What comes back is a base64 frame.
curl https://evrona.ru/v1/images/edits \
-H "Authorization: Bearer ev-YOUR_KEY" \
-F model=openai/gpt-image-2 \
-F prompt="Swap the background for a light gradient" \
-F image=@cat.pngIn response:
{
"created": 1757700000,
"data": [{ "b64_json": "iVBORw0KGgo…" }],
"usage": { "input_tokens": 116, "output_tokens": 1650, "total_tokens": 1766 }
}The format of the frame is the model’s to choose: one returns PNG, another JPEG. We neither promise nor re-encode it — read data[0].b64_json as image bytes and, if you need the type, detect it from the first bytes.
The usage field is far from always present: 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 shows up only when the model supplied it, so check whether the field is there rather than counting on it.
Form fields
| Field name | Value type | What it is for |
|---|---|---|
modelalways needed | string | An image-type model that can edit. A chat model will return 404 model_not_found, and an image model without the capability 400 unsupported_capability. |
promptalways needed | string (≤ 15 000) | What needs doing with the image. No longer than 15 000 characters. |
imagealways needed | file × 1 … 4 | A file: PNG, JPEG or WebP, judged by its first bytes, and no more than 5 MB each. One image goes into the image field, several into image[] (that is exactly how the OpenAI SDK sends them), up to 4 files per request in all. Gemini 3.1 Flash Image takes a single image only: several get 400 with param=image. |
n | 1 … 4 | How many frames to hand back; one by default. The live probe covers one frame per request — with more we have not yet measured how the models behave. |
size | auto | 1024x1024 | 1024x1536 | 1536x1024 | Frame size. With auto — which is also the default — the model picks the size, as a rule following the proportions of your photo. 1024x1536 will give a vertical frame, 1536x1024 a horizontal one, 1024x1024 a square one. Any other value will return 400. A model priced per image offers 1024x1024 alone. |
response_format | b64_json | The only value is b64_json, which is also the default. url will return 400 unsupported_parameter: we run no storage, so a link has nowhere to come from. |
mask, output_format, output_compression, partial_images, stream at true, background: transparent, input_fidelity: high. Each of them changes the bytes of the result or its price at the model, and swallowing them silently would mean returning something other than what was ordered. No model in this version supports a mask: send the whole image instead.
Taken, yet not passed to the model
quality, user, background at opaque or auto, input_fidelity at low. A request with these fields will go through, but the result and the price come out the same as without them.
Timings and parallel requests
An edit takes anywhere from one to several minutes. We wait for the model up to 570 seconds and then answer 504 upstream_timeout with no charge — so set your own client timeout no lower than 600 seconds. Parallel edit requests from one account: 2; the next one will get 429 with a Retry-After header. After 5 rejected requests in a row (400), editing for the account freezes for about a minute, so it is wiser to check the prompt length and field values before sending.
Rates and charges
The price is the same as generation on this 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 the charge went by. On a per-frame model the price of a frame is multiplied by the number of frames. A reserve with a margin is set aside for the request, the surplus returns, and a model failure costs nothing.
The Content-Length header is required: an upload without it (chunked) will return 400 invalid_request. When your client hands the file over as a stream, 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/workspace/spend (the id field) and in the dashboard; the OpenAI SDK exposes it as request_id.
Which of the models can edit
Editing is enabled on 2 models of the catalogue today. They carry the "Editing" mark on the Models page, and GET /v1/workspace/rates returns the image_edit capability in the capabilities field.
Creating video
Video is assembled asynchronously: the request files a job and hands back its id at once. The seconds parameter must be passed — it is the clip length, and the price is computed from it.
curl https://evrona.ru/v1/videos/generations \
-H "Authorization: Bearer ev-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}Ask for readiness every 5–10 seconds. status: processing — work is under way, succeeded — done, with the clip url in the response, failed — it did not work out, and the reason sits in error. Generation most often fits into 30 seconds to 3 minutes, yet now and then the provider takes longer, so do not drop the polling before 10 minutes. The url leads into the provider’s storage and lives for 24 hours after completion — download the file within that span.
curl https://evrona.ru/v1/jobs/JOB_ID \
-H "Authorization: Bearer ev-YOUR_KEY"
# {"id": "9b2f…", "status": "succeeded", "url": "https://…/clip.mp4"}The price of a request is the per-second rate multiplied by seconds. The money goes into a reserve when the job is filed and is debited only once the clip is ready; if generation fails, the reserve comes back in full.
Models and allowed durations
| ID of the model | Duration (seconds) | Frame size | Sound |
|---|---|---|---|
alibaba/wan-2.6 | 2–10 | 720p | with sound |
bytedance/seedance-2.0-fast | 4–15 | 720p | without sound |
bytedance/seedance-2.0 | 4–15 | 720p | without sound |
google/veo-3.1-fast | 4–8 | 1080p | without sound |
google/veo-3.1 | 4–8 | 1080p | without sound |
You need a model of type “video” — GET /v1/models lists them. Per-second rates are on the models page.
A clip is assembled from text only. A photo as the first frame is not something we can do yet: a request with any of the fields image, images, image_url, input_reference, first_frame, last_frame, reference_images will get 400 unsupported_parameter, and nothing is charged.
Endpoint reference
Models
An inventory of available models in OpenAI format. Disabled ones never make the list.
Text
A model answer to a conversation. Handles stream=true (SSE). Request and response formats are the same as OpenAI’s.
The native Anthropic protocol: Claude Code and the Anthropic SDK run against it unchanged. Auth follows the native clients — the x-api-key header, though Bearer is accepted too. Streaming (SSE) and tool calls are in place; max_tokens must be passed.
Gauges the input token count without calling the model: an estimate rather than an exact count, and it spends nothing from the balance.
The OpenAI Responses API format: Codex CLI and the OpenAI SDK run against it unchanged. We keep no state — the client sends the history, previous_response_id is unsupported, responses are not kept by id, so GET and DELETE answer 404. Function tools and streaming (SSE) are in place.
Media
Images synchronously, by the OpenAI Images rules. The frame comes back as base64.
Changes an existing image from a prompt: multipart/form-data going in, a base64 frame coming out.
Files a video generation job. The tariff is counted per second and the seconds field must be passed; the answer is a job with status processing.
The state of a generation job. processing — work is under way, succeeded — done, with the clip url in the response, failed — it did not work out, and nothing is charged.
Account
The read-only account API: balance, retail prices, usage detail and analytics for a key. It is unpacked in the Account API section.
Rates and charges
How the billing works
- Money goes onto the balance in advance: there are no month-end invoices and no subscriptions here.
- Each request costs exactly its own amount: the price comes out of the model and the number of tokens.
- The balance goes down immediately after the answer. If the money ran short, the request comes back with 402: the account never goes negative.
Where the amount comes from
Every model carries its own price per 1 million tokens, and it differs for input (your request) and output (the model’s answer). Output almost always costs more than input. Current prices for all models are gathered in the Models and pricing catalogue.
in = tokens_in / 1 000 000 × price_in
out = tokens_out / 1 000 000 × price_out
───────────────────────────────────────────
sum = in + outBoth halves are rounded up to a millionth of a dollar. The token counts arrive from the model’s own answer — that very usage field you can see in your code.
A sample calculation
model — openai/gpt-5.4-mini · input at $0.1188 for 1M tokens · output at $0.6336 for 1M tokens
The prices above are not invented: they come from the live catalogue — the same one that feeds the bill.
The discounted cache rate
When a request opens with the same text as the previous one — a system prompt, instructions, a long context — the model pulls that opening from its cache, and we bill those tokens at a discounted rate. Nothing needs switching on: the cache fires by itself as soon as 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, costs not a cent extra: it is billed just as it would be without a cache.
in = (tokens_in − tokens_cached) / 1 000 000 × price_in
+ tokens_cached / 1 000 000 × rate_cache_inThe 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/workspace/spend and in the CSV export. The cached-input rate is listed on the Models page and in GET /v1/workspace/rates. Not every model gets the discount: where no rate is listed, the cache is billed at the regular input price.
The reserve while the request is running
While the model is typing, the length of the answer is unknown to anyone. So even before we call the model, the worst case is set aside on your balance:
reserve = tokens_in / 1 000 000 × price_in
+ max_tokens / 1 000 000 × price_outIf there is not enough for the reserve, the request is turned down at once, never reaching the model. And once the answer is in, the actual cost is charged and the remainder of the reserve returns to the balance in the same instant.
max_tokens: 4 000Keep max_tokens near the answer length you expect: the higher it is, the larger the reserve and the less balance remains for parallel requests. When max_tokens is not set, the reserve is computed against the 32,000-token ceiling.
Cases with no charge at all
An error on the model’s side (502, 504) costs nothing, and the reserve returns in full. A turned-down request — wrong key, empty balance, an unsupported field, an oversized body — is free as well, since it never made it to the model. And token estimation via /v1/messages/count_tokens is free at all times.
Where the spend is visible
In the dashboard. The key page keeps a log of requests — model, tokens and cost for each — and next to it a CSV export and a daily chart. The spend section holds a summary for the period with a breakdown by model. We never store the text of requests or answers, so the statistics hold nothing but these counters.
Account API — balance, rates and spend per key
A read-only API for the account: pull balance, retail prices and key usage straight from your own backend, without opening the dashboard. The base URL and ready-made snippets are gathered in the "Account management" section of the dashboard.
Authorize with the very key you call models with: the Authorization: Bearer header. GET is the only method allowed; the key has to be active — paused and revoked ones answer 401. Do not place the key in a URL, and certainly not in browser code. Statistics are counted for the key that made the request, while balance and prices are shared across the whole account.
How much the account holds on balance right now, in dollars; active reserves are already subtracted.
Retail rates of the models from GET /v1/models: per 1M tokens (input, cached input, output) or per generation unit — a second in the case of video. What a model can do is listed machine-readably in the capabilities field: vision — the model accepts image input, image_edit — it can edit images. Provider cost and margin are never handed out.
The key’s requests line by line: model, operation, status, tokens with the cache-served share listed apart, the number of images in the input (images_in), charge, latency and price snapshots taken at request time. Sorted freshest first.
A summary for the key over the period: totals, breakdowns by model and by day (UTC), gateway errors grouped by code.
Query-string parameters
None of the parameters is required. A value outside the allowed range gives 400 invalid_request, with the parameter name arriving in param.
| Field name | Value type | Default value | What it is for |
|---|---|---|---|
daysspend · insights | 1 … 90 | 30 | How many days back from the current moment the window reaches. |
limitspend | 1 … 100 | 50 | How many detail records arrive on one page. |
pagespend | 0 … 200 | 0 | The page number, counted from zero. Whether a next one exists is reported by has_more in the response. |
Sample requests
curl https://evrona.ru/v1/workspace/balance \
-H "Authorization: Bearer ev-YOUR_KEY"
# {"object": "workspace.balance", "balance_usd": 4.981234, "currency": "USD"}curl "https://evrona.ru/v1/workspace/spend?days=7&limit=25" \
-H "Authorization: Bearer ev-YOUR_KEY"In 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.00065,
"price_input_usd_per_1m_tokens": 0.1188,
"price_cached_input_usd_per_1m_tokens": 0.0168,
"price_output_usd_per_1m_tokens": 0.6336,
"price_generation_usd": null,
"latency_ms": 812
}
]
}The /v1/workspace/insights response is a single object with:
totals · models[] · daily[] · errors.by_code[]
The detail feed holds nothing but metadata. Prompt and response bodies are not stored in the first place, so there is no way to hand them out — not over the API, not by any other route. IPs and client data stay unexposed as well.
Possible errors
The error envelope is the same across the whole gateway — it is unpacked in the Request errors section. In this API you may run into:
401 invalid_api_key · 400 invalid_request · 429 rate_limited
Limits and caps
The limits guard your balance as much as the gateway: a stray loop in your code must not burn the money up in a minute.
| What is limited | Cap |
|---|---|
| Call rate from a single key | a burst of 60, then 10 a second |
| Request body volume: chat and /v1/messages | 2 MB |
| Body volume of a request with images: chat, /v1/messages, /v1/responses, image editing | 24 MB |
| Request body volume: images, video | 256 KB |
| Prompt length: messages and tool schemas counted together | 400 000 chars |
| Prompt length for pictures: creating and editing | 15 000 chars |
| Image edits running at once per account | 2 |
| Ceiling on answer tokens (max_tokens) | 32 000 tokens |
| Tools per request | 128 |
| Stop strings per request | 4 |
| Money limit on a key: day, week, month | up to you |
| Key schedule: which hours and days it is in force | up to you |
Going over the rate brings 429 rate_limited: wait and retry — the key is not blocked and not penalised for it. A body larger than 2 MB is read only under a working key with a positive balance: the upload is given 30 s, and only a limited number of large uploads run at once — an extra one will get 429 with a Retry-After header. The money limit and the schedule are yours to set, in the dashboard on the key page. Guessing at wrong keys from one address we throttle on top of that.
Characters are counted, not bytes: a Cyrillic letter is one, an emoji usually two.
How patient the gateway is with the model. In chat — up to 280 seconds before the answer starts and up to 240 seconds of silence in the middle. For image requests — up to 570 seconds. If the answer has not begun within that span, 504 upstream_timeout comes back and nothing is charged. Without stream the answer arrives in one piece, so on long answers stream is better switched on. If the answer broke off midway, or your client closed the connection ahead of time, the request lands in the log with the status aborted: you pay for the processed request and for the part of the answer that got through. Set the timeout on your side above these numbers.
The /uptime page shows availability and average response time of every model by hour.
Gateway error codes
All errors share one shape, the same as OpenAI’s, so the SDKs parse them without your help:
{
"error": {
"message": "No enabled model matches that name. Request GET /v1/models to see what is available.",
"type": "invalid_request_error",
"code": "model_not_found",
"param": "model"
}
}Rely on code: it is machine-readable and stays as it is. The message text is written for a human and may be rewritten over time. When the error sits in a specific request field, param points at it.
| HTTP status | code | What triggers it | How to fix it |
|---|---|---|---|
| 400 | invalid_request | The body could not be parsed: broken JSON, a required field left out, or a value that went past the allowed range. | Check against the Chat request fields section: what exactly did not fit is named in param. |
| 400 | unsupported_parameter | The request holds a field the gateway cannot handle yet. | Take the field out of the request; the incompatible ones are listed under Chat request fields. |
| 400 | unsupported_capability | The request asks the model for something it cannot do: taking an image on the input or editing an image. | Take a model with the right mark on the Models page, or with the capability in GET /v1/workspace/rates. The request was not billed. |
| 400 | context_length_exceeded | The request will not fit the model context: conversation, images and tool schemas count together. | Trim the conversation, drop some of the images, or switch to a model with a longer context: its size is listed on the Models page. |
| 401 | invalid_api_key | The key is not the right one, has been revoked, or was not sent at all. | Make sure the key was copied in full, including the ev- prefix, and has not been revoked. |
| 402 | insufficient_balance | There is no money left on the balance; you can top it up in the dashboard. | Top the balance up. A smaller max_tokens helps too: the reserve comes out leaner. |
| 403 | key_schedule_blocked | This API key is outside its allowed schedule. Adjust it at the dashboard (/console/keys). | Edit this key’s schedule in the dashboard, or reach for another key. |
| 404 | model_not_found | There is no such model, or it is disabled. GET /v1/models gives the current list. | Copy the ID from GET /v1/models whole, without cutting off the prefix before the slash. |
| 404 | job_not_found | No generation job was found under that id. | Compare the id with the one the job-creation response returned. |
| 404 | not_found | There is nothing behind this address. We do not keep /v1/responses, so fetching or deleting them by id will not work. | Check the address. Keep the conversation on your side and send the full input in every request. |
| 408 | request_timeout | The request body was not read to the end within the allotted span. | Repeat the request; the Retry-After header tells you when. On a slow link, send smaller images. |
| 413 | payload_too_large | The request body does not fit the limit. | Trim the conversation, or spread it over several requests. |
| 429 | rate_limited | This key is sending too many requests; wait and retry. Throughput evens out on its own. | Wait and retry. A pause that gets longer with every attempt does the job. |
| 429 | spend_limit_exceeded | The spend limit for this API key is reached. Raise or remove it at the dashboard (/console/keys). | Raise the limit or drop it altogether — in the dashboard, on the key page. |
| 500 | internal_error | Something broke on our end. | Send the request again. If the error does not go away, write to support and quote the id from the response. |
| 502 | provider_not_configured | There is no working route to this model’s provider. | Switch to another model and let us know: this error is not supposed to occur. |
| 502 | upstream_error | A breakdown at the model provider. We do not charge for such a request. | Send it again, or pick a different model. Nothing was charged for it. |
| 504 | upstream_timeout | The model’s provider ran out of time to answer. | Send it again. On long answers switch stream on: a stream breaks only when the provider goes quiet for more than 4 minutes in a row. |
We take no money for anything in this table. A request that never made it to the model, and a request the model answered with an error, are both uncharged, and the reserve goes back to the balance in full.
Questions and answers
How do I move to a different model?
Swap the value of the model parameter. The rest of your code can stay untouched: the request will reach the right provider by itself.
Can I use the official OpenAI SDK?
Yes. Put our address with /v1 into base_url, supply our key, and all the remaining code stays as it was.
What makes up the cost?
Of input and output tokens, at the model’s rate as of the request. The formula and worked examples sit in Rates and charges.
Is the number of requests capped?
Yes. Every key carries a rate limit, and the money spend limits are yours to set. All the numbers are gathered in the Restrictions section.
Write to support at support@evrona.ru or check the general FAQ.
Found a mistake in the docs? Let us know.