# polyapi > OpenAI-compatible API to GPT, Claude, Gemini, DeepSeek, GLM, Kimi and other language models, prepaid and billed in Russian rubles per token. One API key and one base URL for every model; switch models with the `model` string. Any OpenAI SDK or tool that lets you set a base URL works unchanged. Key facts for agents: - Base URL: `https://api.polyapi.ru/v1` - Auth: `Authorization: Bearer `. Keys look like `sk-` followed by 64 hex characters. Get one at https://polyapi.ru/login (sign-in by an emailed code). - Model IDs are `vendor/model`; the bare name works too (`gpt-6-sol` is `openai/gpt-6-sol`). E.g. `openai/gpt-6-sol`, `anthropic/claude-sonnet-5`, `google/gemini-3.8-flash`, `deepseek/deepseek-v4.1-flash`. Never guess an ID: read the live catalog. - Models: 317 in the catalog, chat, embedding and image together (`models` plus `image_models` of the catalog below). - Chat and responses take text, and images for models that take them (`image_url` in chat, `input_image` in responses: a URL or a data URL, in user messages and tool outputs); an image to any other model is rejected with `400`. Streaming (`"stream": true`), tool/function calling and `response_format` (`json_object`, `json_schema`) work. Audio and file inputs there are rejected with `400`; images are made by tasks, `POST /v1/tasks`. Request body up to 8 MiB; for tasks up to 32 MiB. - Limits per key by default: 600 requests per minute, 64 concurrent requests. Each gateway server counts them on its own, so they stop runaway loops rather than meter traffic; the real ceiling may be higher. A key can have its own spending cap. - Chat, responses and embeddings errors use the OpenAI error format; tasks have their own error object (below). Every generation response carries an `x-request-id` header: quote it to support. A request that fails before the answer starts is not charged. - `400` `invalid_request` (`param` names the field), `context_length_exceeded`, `content_policy_violation`: fix the request, do not retry as is. - `401` `invalid_api_key`: no key, wrong key or revoked. - `404` `model_not_found`: wrong model ID or a model of another `kind`. - `413` `request_too_large`: body over 8 MiB. Tasks: `payload_too_large`, body over 32 MiB (a data URL in a reference: up to 10 MiB). - `429` `rate_limit_exceeded`: slow down and retry. `429` `insufficient_quota`: balance or key cap exhausted, top up or raise the cap; do not retry. - `/v1/tasks`: the error is `{ "error": { "code", "retry", "message" } }` (also `param`, `retry_after`, `required`, `balance`), and a failed task carries the same object in its `error`. `retry` decides: `as_is` repeat the same request, `change_input` fix it, `no` do not repeat. `content_policy_violation` carries `stage`: `input` (the prompt or references were refused, `change_input`) or `output` (the made result was refused, `as_is`; the task already made it once more, with a new `seed` if one was set, and `output[].seed`, if the model reports it, is the one used); `category`, only when the provider named one: `sexual`, `minors`, `violence`, `hate`, `self_harm`, `illegal`, `public_figure`, `copyright`. An `attempt.failed` event of such a refusal carries the same `stage` and `category`. Codes: `invalid_request`, `reference_too_large`, `reference_unsupported`, `payload_too_large`, `unauthorized`, `insufficient_balance`, `not_found`, `task_purged`, `task_id_conflict`, `rate_limited`, `content_policy_violation`, `upstream_timeout`, `upstream_unavailable`, `upstream_capacity`, `upstream_error`, `result_unavailable`, `task_expired`, `capacity_exceeded`, `key_limit_reached`, `method_not_allowed`, `internal_error`. HTTP statuses: `402` `insufficient_balance` (top up) and `key_limit_reached` (the key's own spending cap, `retry` `as_is`: repeat after the owner raises the key's limit); `405` `method_not_allowed` (`retry` `no`); `503` `capacity_exceeded` (the model is temporarily unavailable: repeat after `retry_after`) or `upstream_unavailable` (no route for the model, service paused, tasks off, the process overloaded or a task read past its time; wait `Retry-After`, 1 s in the last two cases, `retry` `as_is`). `POST` with the `id` of a purged task: `410` `task_purged` for the same request, `409` `task_id_conflict` for another. In `POST /v1/tasks/quote` `sufficient` covers only the account balance (not the key's cap, the limits or the model's availability), so a create may still answer `402` `key_limit_reached`, `429` or `503`; the quote itself counts against the key's requests per minute and takes a slot of the process, so it can answer `429` `rate_limited` or `503` `upstream_unavailable` too; a malformed `id` or `wait` in a quote body is `400`. - `502` `upstream_unavailable` or `upstream_error`, `503` `service_unavailable`, `504` `upstream_timeout` (no first byte in 300 s): retry later. - Reliability: a model may have several providers (`providers` in the catalog). If one fails before the first byte (timeout, 429, 5xx), the request moves to another automatically. The price stays the same: one price per model, whichever provider answers. - Privacy: request and response texts are not stored, only metadata for billing (time, model, key, tokens, cost). ## API - [OpenAPI 3.1 spec (JSON)](https://polyapi.ru/openapi.json): the endpoints below with the request fields the gateway accepts, what it refuses and every error code. - [Model catalog (JSON, no auth)](https://polyapi.ru/api/catalog): every model with its display name, `kind` (`chat` or `embedding`), context length, `price` in RUB per 1M tokens (`in`, `cached`, `out`; `out` is 0 for embeddings) and `providers`, how many providers serve it; `image_models`: image models with `prices` in RUB per image, each row naming the parameters it depends on (`resolution`, `quality`, and for some models the frame `variant` and the number of references `references`, as the rows in `GET /v1/tasks/models` show), `frames` (`aspect_ratio`, `resolution`, exact `size`) and `max_references`. The price is what you are charged. Cached for 60 s, CORS open. - `GET https://api.polyapi.ru/v1/models` (with a key): the same catalog in OpenAI list format. - `POST https://api.polyapi.ru/v1/chat/completions`: Chat Completions, as in the OpenAI API. - `POST https://api.polyapi.ru/v1/responses`: Responses API without server-side state: `previous_response_id`, `conversation` and `background` are refused, send the full input every time. - `POST https://api.polyapi.ru/v1/embeddings`: embeddings, for models of `kind` `embedding` (Qwen3 Embedding 8B and 4B, OpenAI text-embedding-3 large and small, Gemini Embedding 2). - `POST https://api.polyapi.ru/v1/tasks`: create a task, today `kind` `image/generate` (not the OpenAI image format: plain HTTP, no SDK). Body: `{ "id"?, "kind", "model", "input": { "prompt", "count"?, "aspect_ratio"?, "resolution"?, "size"?, "format"?, "quality"?, "seed"?, "references"? }, "options"?, "metadata"?, "wait"? }`. `model` is required, there is no default. `id` is your UUID: repeat the same request with the same `id` after a cut connection or any 5xx and you get the same task, never a second charge; without `id` a repeat makes a second task. `wait` is 0 to 60 seconds (default 60): the request holds that long; if the task is not done the answer is `202`. - `GET https://api.polyapi.ru/v1/tasks/{id}`: the task. One object for every state: `id`, `kind`, `model`, `status` (`queued`, `in_progress`, `completed`, `failed`), `events`, `output` (only `completed`: parts with `type`, `url`, `media_type`, `width`, `height`, `bytes`, `expires_at`, `retained_until`, `seed` if the model knows it; a part past its retention has no `url` and `expired: true`), `error` (only `failed`), `usage` (`reserved` while running, `cost` when done; money is `{ "amount": "42.50", "currency": "rub" }`), `deadline_at` and `poll_after` while running. Branch on `status` only. While not done, poll after `poll_after` seconds (also the `Retry-After` header); at `deadline_at` the task has finished at the latest. Files are kept 30 days; a link lives at least an hour and is re-signed on every `GET`. - `GET https://api.polyapi.ru/v1/tasks` (your tasks, newest first; filters `kind`, `status`, `created_after`, `created_before` (both exclusive), `metadata[key]`, `limit`, `cursor`), `POST https://api.polyapi.ru/v1/tasks/quote` (price and validation of a request, nothing is created or charged), `GET https://api.polyapi.ru/v1/tasks/models` (task models with fields, allowed values, frames and price tables; every frame has `aspect_ratio`, `resolution` and `size`, a request sends the pair or `size`). ## Quickstart curl: ```sh curl https://api.polyapi.ru/v1/chat/completions \ -H "Authorization: Bearer $OPENAI_API_KEY" \ -H "Content-Type: application/json" \ -d '{"model":"openai/gpt-6-sol","messages":[{"role":"user","content":"Hello"}]}' ``` Python (`pip install openai`): ```python from openai import OpenAI client = OpenAI(base_url="https://api.polyapi.ru/v1", api_key="sk-...") r = client.chat.completions.create( model="openai/gpt-6-sol", messages=[{"role": "user", "content": "Hello"}], ) print(r.choices[0].message.content) ``` Node.js (`npm i openai`): ```js import OpenAI from "openai"; const client = new OpenAI({ baseURL: "https://api.polyapi.ru/v1", apiKey: "sk-..." }); const r = await client.chat.completions.create({ model: "openai/gpt-6-sol", messages: [{ role: "user", content: "Hello" }], }); ``` Environment for SDKs and tools that read the standard variables (Codex CLI, most OpenAI SDKs): ```sh export OPENAI_BASE_URL="https://api.polyapi.ru/v1" export OPENAI_API_KEY="sk-..." ``` n8n: Credentials → OpenAI API, set API Key and Base URL `https://api.polyapi.ru/v1`. Migrating from OpenAI: change `base_url` and the key. Model names work as is (`gpt-6-sol` is `openai/gpt-6-sol`). The rest of the code stays. ## Billing - Prices are in rubles per 1 million tokens, input and output separately, cached input cheaper. One price per model, whichever provider answers. The catalog above is the source of truth; prices can change, the catalog always shows the current ones. - Prepaid balance, top-up from 100 ₽ in the cabinet by card (Mir, Visa, Mastercard), SBP or T-Pay through T-Bank. When the balance runs out, requests return `429 insufficient_quota`. - The unspent paid balance is refundable on request; bonus credit is not. ## Account - [Cabinet](https://polyapi.ru/overview): keys with spending caps and reissue, projects, per-request log with model, key, tokens and cost and CSV export. - Support: support@polyapi.ru. ## Legal (Russian) - [Public offer](https://polyapi.ru/legal/offer) - [Refunds and cancellation](https://polyapi.ru/legal/refund) - [Service delivery and geography](https://polyapi.ru/legal/delivery) - [Privacy policy](https://polyapi.ru/legal/privacy) - [Personal data consent](https://polyapi.ru/legal/pd) - [Payment security](https://polyapi.ru/legal/security) - [Contacts and company details](https://polyapi.ru/legal/contacts) ## Optional - [Codex CLI setup (Russian)](https://polyapi.ru/codex) - [OpenAI models (Russian)](https://polyapi.ru/openai-api) and [Claude models (Russian)](https://polyapi.ru/claude-api): prices and IDs. - [API docs (Russian)](https://polyapi.ru/docs): quickstart, authentication, models, Chat Completions, Responses, Embeddings, tasks (create, poll, repeat with an `id`, list, quote, models, error codes), streaming, function calling, every error code, limits, Cursor and Codex setup, samples in curl, Python, Node.js. - [Model catalog (Russian)](https://polyapi.ru/): every model with its prices, context and ID; a page per model at https://polyapi.ru/models//.