{"openapi":"3.1.0","info":{"title":"polyapi","version":"1.0.0","summary":"OpenAI-compatible API to many language models, prepaid in rubles.","description":"The subset of the OpenAI API the gateway serves: `GET /models`, `GET /models/{model}`, `POST /chat/completions`, `POST /responses`, `POST /embeddings`, and the tasks API `/tasks` (images). Any OpenAI SDK works with `base_url` https://api.polyapi.ru/v1 for chat, responses and embeddings; tasks are plain HTTP in their own format.\n\nChat and responses take text, and images (a URL or a data URL; in user messages, and on /responses in tool outputs too) for models whose `input_modalities` has `image`; an image to any other model is refused with 400 `invalid_request`. Each image holds 5 000 input tokens while the request runs; the charge is the tokens the model counted. Audio and file inputs are refused with 400. Images are made by tasks, `POST /tasks`. Nothing is stored on the server: `store` is ignored, `previous_response_id`, `conversation` and `background` are refused, send the full history with every request. Built-in tools (web search, file search, code interpreter, MCP) are refused, tools of type `function` and `custom` work; on /responses a `web_search` tool (Codex CLI adds one to every request) is dropped instead, and the model answers without searching; so is a `namespace` of client tools in `tools` (Codex CLI's sub-agents), and the model works without them.\n\nA request body may carry only the fields listed in its schema; fields without a note are passed to the model as is. Any other field is refused with 400 `invalid_request` and `param` naming it, unless its value is null. Model IDs are `vendor/model`, e.g. `openai/gpt-5.6-sol`; the bare name (`gpt-5.6-sol`) works too; the live list with prices is https://polyapi.ru/api/catalog (no key) or `GET /models`. A model may have several providers (`providers` in the catalog) and has one price, whichever provider answers.\n\nLimits per key by default: 600 requests per minute and 64 concurrent requests, counted by each gateway server on its own: a guard against runaway loops, not a metered quota. Request body up to 8 MiB; for tasks (`POST /tasks`, `POST /tasks/quote`) up to 32 MiB, and a data URL in a reference up to 10 MiB. Every response, errors included, carries `x-request-id`. A 429 for balance or a key spending cap carries `x-should-retry: false`: a retry gets the same answer.\n\nTasks (`/tasks`) have their own error object, `{ \"error\": { \"code\", \"retry\", \"message\" } }`, see `TaskError`; `retry` says whether to repeat the request as is (`as_is`), fix it (`change_input`) or stop (`no`).","contact":{"name":"polyapi support","email":"support@polyapi.ru","url":"https://polyapi.ru/docs"}},"externalDocs":{"description":"Docs (Russian)","url":"https://polyapi.ru/docs"},"servers":[{"url":"https://api.polyapi.ru/v1"}],"security":[{"bearer":[]}],"paths":{"/models":{"get":{"operationId":"listModels","summary":"List models","description":"Every model with its kind, context length and price. The same data without a key: https://polyapi.ru/api/catalog.","responses":{"200":{"description":"Models.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ModelList"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"503":{"$ref":"#/components/responses/Unavailable"}}}},"/models/{model}":{"get":{"operationId":"retrieveModel","summary":"Retrieve a model","description":"One model of `GET /models` by id; the id keeps its slash (`/models/openai/gpt-5.6-sol`).","parameters":[{"name":"model","in":"path","required":true,"schema":{"$ref":"#/components/schemas/ModelId"}}],"responses":{"200":{"description":"The model.","headers":{"x-request-id":{"$ref":"#/components/headers/RequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Model"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"503":{"$ref":"#/components/responses/Unavailable"}}}},"/chat/completions":{"post":{"operationId":"createChatCompletion","summary":"Create a chat completion","description":"Chat Completions as in the OpenAI API, for models of kind `chat`. With `stream: true` the answer is Server-Sent Events of `chat.completion.chunk` objects ending with `data: [DONE]`. As with OpenAI, reasoning text is not returned; its size is `usage.completion_tokens_details.reasoning_tokens`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChatRequest"}}}},"responses":{"200":{"description":"The completion, or an SSE stream when `stream` is true.","headers":{"x-request-id":{"$ref":"#/components/headers/RequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChatCompletion"}},"text/event-stream":{"schema":{"type":"string","description":"`data: {chat.completion.chunk}` events, then `data: [DONE]`. If the model stream breaks: `data: {\"error\":{\"code\":\"stream_interrupted\",...}}` and the stream closes."}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/BadRequest"},"413":{"$ref":"#/components/responses/TooLarge"},"422":{"$ref":"#/components/responses/BadRequest"},"429":{"$ref":"#/components/responses/TooMany"},"502":{"$ref":"#/components/responses/BadGateway"},"503":{"$ref":"#/components/responses/Unavailable"},"504":{"$ref":"#/components/responses/Timeout"}}}},"/responses":{"post":{"operationId":"createResponse","summary":"Create a model response","description":"Responses API without server-side state, for models of kind `chat`: `store` is forced to false; `previous_response_id`, `conversation` and `background: true` are refused with 400. Keep reasoning between turns with `include: [\"reasoning.encrypted_content\"]` and send it back in `input`; reasoning items sent back without `encrypted_content` are dropped, as they refer to storage. With `stream: true` the answer is Server-Sent Events of Responses events (`response.created` ... `response.completed`).","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ResponsesRequest"}}}},"responses":{"200":{"description":"The response object, or an SSE stream when `stream` is true.","headers":{"x-request-id":{"$ref":"#/components/headers/RequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Response"}},"text/event-stream":{"schema":{"type":"string","description":"`event: <type>` / `data: {...}` Responses events. If the model stream breaks: `event: error` with `{\"type\":\"error\",\"code\":\"stream_interrupted\",\"param\":null,\"sequence_number\":...}` and the stream closes."}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/BadRequest"},"413":{"$ref":"#/components/responses/TooLarge"},"422":{"$ref":"#/components/responses/BadRequest"},"429":{"$ref":"#/components/responses/TooMany"},"502":{"$ref":"#/components/responses/BadGateway"},"503":{"$ref":"#/components/responses/Unavailable"},"504":{"$ref":"#/components/responses/Timeout"}}}},"/embeddings":{"post":{"operationId":"createEmbedding","summary":"Create embeddings","description":"Embeddings for models of kind `embedding`. No streaming.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmbeddingsRequest"}}}},"responses":{"200":{"description":"Embeddings.","headers":{"x-request-id":{"$ref":"#/components/headers/RequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmbeddingList"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/BadRequest"},"413":{"$ref":"#/components/responses/TooLarge"},"422":{"$ref":"#/components/responses/BadRequest"},"429":{"$ref":"#/components/responses/TooMany"},"502":{"$ref":"#/components/responses/BadGateway"},"503":{"$ref":"#/components/responses/Unavailable"},"504":{"$ref":"#/components/responses/Timeout"}}}},"/tasks":{"post":{"operationId":"createTask","summary":"Create a task","description":"Creates a task and holds the request up to `wait` seconds (0 to 60, default 60). Done in time: `200` with the finished task, `completed` or `failed`. Not done: `202` with `queued` or `in_progress`, `poll_after` and `Retry-After`; ask `GET /tasks/{id}`. Branch on `status`, not on the HTTP code. The cost is reserved first and charged at most up to the reserve; a failed task is never charged. Repeat the same request with the same `id` after any 5xx or a cut connection: you get the same task.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TaskRequest"}}}},"responses":{"200":{"description":"The task. `status` says whether it is done.","headers":{"x-request-id":{"$ref":"#/components/headers/RequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Task"}}}},"202":{"description":"The task is not done yet: ask again after `poll_after` seconds.","headers":{"x-request-id":{"$ref":"#/components/headers/RequestId"},"Retry-After":{"$ref":"#/components/headers/RetryAfter"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Task"}}}},"400":{"$ref":"#/components/responses/TaskBadRequest"},"401":{"$ref":"#/components/responses/TaskUnauthorized"},"402":{"$ref":"#/components/responses/TaskPaymentRequired"},"404":{"$ref":"#/components/responses/TaskNotFound"},"405":{"$ref":"#/components/responses/TaskMethodNotAllowed"},"409":{"$ref":"#/components/responses/TaskConflict"},"410":{"$ref":"#/components/responses/TaskGone"},"413":{"$ref":"#/components/responses/TaskTooLarge"},"429":{"$ref":"#/components/responses/TaskTooMany"},"500":{"$ref":"#/components/responses/TaskServerError"},"503":{"$ref":"#/components/responses/TaskServerError"}}},"get":{"operationId":"listTasks","summary":"List your tasks","description":"Newest first.","parameters":[{"name":"kind","in":"query","schema":{"type":"string"},"description":"Exact, `image/generate`, or a group, `image/*`."},{"name":"status","in":"query","schema":{"type":"string","enum":["queued","in_progress","completed","failed"]}},{"name":"created_after","in":"query","schema":{"type":"string","format":"date-time"}},{"name":"created_before","in":"query","schema":{"type":"string","format":"date-time"}},{"name":"metadata[key]","in":"query","schema":{"type":"string"},"description":"Tasks whose `metadata` has this value under `key`."},{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":100,"default":20}},{"name":"cursor","in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"A page of tasks.","headers":{"x-request-id":{"$ref":"#/components/headers/RequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TaskList"}}}},"400":{"$ref":"#/components/responses/TaskBadRequest"},"401":{"$ref":"#/components/responses/TaskUnauthorized"},"405":{"$ref":"#/components/responses/TaskMethodNotAllowed"},"500":{"$ref":"#/components/responses/TaskServerError"},"503":{"$ref":"#/components/responses/TaskServerError"}}}},"/tasks/{id}":{"get":{"operationId":"getTask","summary":"Get a task","description":"Reads the task from the database; the provider is not asked, and the rate limits do not apply to polling. Not done: the answer is `200` with `status` `queued` or `in_progress` and `poll_after`; by `deadline_at` it has finished at the latest. The files are kept 30 days; the task is readable while its events live (90 days), later `410`.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"The task. `status` says whether it is done.","headers":{"x-request-id":{"$ref":"#/components/headers/RequestId"},"Retry-After":{"$ref":"#/components/headers/RetryAfter"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Task"}}}},"400":{"$ref":"#/components/responses/TaskBadRequest"},"401":{"$ref":"#/components/responses/TaskUnauthorized"},"404":{"$ref":"#/components/responses/TaskNotFound"},"405":{"$ref":"#/components/responses/TaskMethodNotAllowed"},"410":{"$ref":"#/components/responses/TaskGone"},"500":{"$ref":"#/components/responses/TaskServerError"},"503":{"$ref":"#/components/responses/TaskServerError"}}}},"/tasks/quote":{"post":{"operationId":"quoteTask","summary":"Price and check a request","description":"The same body as `POST /tasks` (`id` and `wait` are not used, but a malformed one is `400`): the full check and the price. A wrong request gets the same `400` with `param` as the creation. Creates no task, touches no money, never returns `402`; `sufficient` covers only the account's balance. A quote counts against the key's requests per minute and takes a slot of the process like any call: `429` `rate_limited` or `503` `upstream_unavailable` are possible. The price is a reference, not a promise: a new task is charged at the catalog price of that moment.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TaskRequest"}}}},"responses":{"200":{"description":"The price.","headers":{"x-request-id":{"$ref":"#/components/headers/RequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TaskQuote"}}}},"400":{"$ref":"#/components/responses/TaskBadRequest"},"401":{"$ref":"#/components/responses/TaskUnauthorized"},"404":{"$ref":"#/components/responses/TaskNotFound"},"405":{"$ref":"#/components/responses/TaskMethodNotAllowed"},"413":{"$ref":"#/components/responses/TaskTooLarge"},"429":{"$ref":"#/components/responses/TaskTooMany"},"500":{"$ref":"#/components/responses/TaskServerError"},"503":{"$ref":"#/components/responses/TaskServerError"}}}},"/tasks/models":{"get":{"operationId":"listTaskModels","summary":"Task models","description":"The models with their fields, allowed values, frames and price tables. Providers and routes are not shown.","parameters":[{"name":"kind","in":"query","schema":{"type":"string"},"description":"Exact or a group, `image/*`."}],"responses":{"200":{"description":"The models.","headers":{"x-request-id":{"$ref":"#/components/headers/RequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TaskModelList"}}}},"400":{"$ref":"#/components/responses/TaskBadRequest"},"401":{"$ref":"#/components/responses/TaskUnauthorized"},"405":{"$ref":"#/components/responses/TaskMethodNotAllowed"},"500":{"$ref":"#/components/responses/TaskServerError"},"503":{"$ref":"#/components/responses/TaskServerError"}}}}},"components":{"securitySchemes":{"bearer":{"type":"http","scheme":"bearer","description":"API key from the cabinet (https://polyapi.ru/c/): `sk-` followed by 64 hex characters. `Authorization: Bearer sk-...`."}},"headers":{"RequestId":{"description":"Request id; quote it to support.","schema":{"type":"string","format":"uuid"}},"ShouldRetry":{"description":"`false`: the request must not be sent again.","schema":{"type":"string","enum":["false"]}},"RetryAfter":{"description":"Seconds to wait before retrying, when known.","schema":{"type":"string"}}},"responses":{"BadRequest":{"description":"`invalid_request`: the body is not a JSON object, a field is unknown or has an unsupported value (`param` names it), or the provider refused the request. `context_length_exceeded`: the input is too long for the model. `content_policy_violation`: refused by the content policy. These provider refusals keep the provider's status, which may also be 404, 409, 413 or 422.","headers":{"x-request-id":{"$ref":"#/components/headers/RequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Unauthorized":{"description":"`invalid_api_key`: no key, a malformed key, an unknown or revoked key.","headers":{"x-request-id":{"$ref":"#/components/headers/RequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"NotFound":{"description":"`model_not_found` (`param: model`): no such model, or not of the kind this endpoint serves (a chat model on /embeddings and vice versa). `not_found`: unknown path or wrong HTTP method.","headers":{"x-request-id":{"$ref":"#/components/headers/RequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"TooLarge":{"description":"`request_too_large`: the body is over 8 MiB. A provider's 413 refusal keeps its own code, see 400.","headers":{"x-request-id":{"$ref":"#/components/headers/RequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"TooMany":{"description":"`rate_limit_exceeded` (type `requests`): the key is over its requests per minute or concurrent requests, or the model provider is rate limiting; retry later. `insufficient_quota`: the balance does not cover the request or the key reached its spending cap; top up or lower the output limit, a retry will not help.","headers":{"x-request-id":{"$ref":"#/components/headers/RequestId"},"Retry-After":{"$ref":"#/components/headers/RetryAfter"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"BadGateway":{"description":"`upstream_unavailable` or `upstream_error`: every provider of the model failed; retry later.","headers":{"x-request-id":{"$ref":"#/components/headers/RequestId"},"Retry-After":{"$ref":"#/components/headers/RetryAfter"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Unavailable":{"description":"`service_unavailable`: overload, billing unavailable, requests paused, or the model has no provider available right now; retry later. May carry `Retry-After`.","headers":{"x-request-id":{"$ref":"#/components/headers/RequestId"},"Retry-After":{"$ref":"#/components/headers/RetryAfter"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Timeout":{"description":"`upstream_timeout`: no provider started answering in time; retry later.","headers":{"x-request-id":{"$ref":"#/components/headers/RequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"TaskBadRequest":{"description":"`invalid_request` (`param` names the field: an unknown field, a value the model does not take, `/wait` out of 0 to 60, an unknown `kind`, a malformed query parameter or `id` in the path), `reference_too_large`, `reference_unsupported`: `retry` is `change_input`. Nothing was reserved or charged.","headers":{"x-request-id":{"$ref":"#/components/headers/RequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TaskErrorResponse"}}}},"TaskUnauthorized":{"description":"`unauthorized`: no key, a wrong key or a revoked one. `retry`: `no`.","headers":{"x-request-id":{"$ref":"#/components/headers/RequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TaskErrorResponse"}}}},"TaskPaymentRequired":{"description":"`insufficient_balance`: the balance does not cover the reserve; `required` and `balance` say how much. `retry`: `as_is` after a top-up. `key_limit_reached`: the key's own spending cap, a top-up does not lift it. `retry`: `as_is`, after the owner raises the key's limit.","headers":{"x-request-id":{"$ref":"#/components/headers/RequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TaskErrorResponse"}}}},"TaskNotFound":{"description":"`not_found`: no such task in the account, or an unknown model. `retry`: `no`.","headers":{"x-request-id":{"$ref":"#/components/headers/RequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TaskErrorResponse"}}}},"TaskConflict":{"description":"`task_id_conflict`: the `id` is taken by another request, or by a task or a request of another account; also a purged task's `id` sent with another request. `retry`: `no`; use a new `id`.","headers":{"x-request-id":{"$ref":"#/components/headers/RequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TaskErrorResponse"}}}},"TaskGone":{"description":"`task_purged`: the retention of the task is over. `POST` with the `id` of a purged task and the same request gets the same `410`. `retry`: `no`.","headers":{"x-request-id":{"$ref":"#/components/headers/RequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TaskErrorResponse"}}}},"TaskTooLarge":{"description":"`payload_too_large`: the request body is over 32 MiB. `retry`: `change_input`.","headers":{"x-request-id":{"$ref":"#/components/headers/RequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TaskErrorResponse"}}}},"TaskTooMany":{"description":"`rate_limited`: the rate or the concurrent tasks limit; wait `retry_after` seconds. `retry`: `as_is`.","headers":{"x-request-id":{"$ref":"#/components/headers/RequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TaskErrorResponse"}}}},"TaskServerError":{"description":"`internal_error` (500), `capacity_exceeded` (503, the model is temporarily unavailable: repeat after `retry_after`) or `upstream_unavailable` (503: no route for the model with these parameters, the service is paused or tasks are off, the process is overloaded, or reading tasks ran past its time; `Retry-After`, 1 s for the last two): `retry` is `as_is`. Any 5xx, a cut connection or a non-JSON answer to `POST /tasks` means «unknown whether the task was made»: repeat the same request with the same `id`.","headers":{"x-request-id":{"$ref":"#/components/headers/RequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TaskErrorResponse"}}}},"TaskMethodNotAllowed":{"description":"405 `method_not_allowed`: the path does not serve this method. `retry`: `no`.","headers":{"x-request-id":{"$ref":"#/components/headers/RequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TaskErrorResponse"}}}}},"schemas":{"Error":{"type":"object","description":"OpenAI error format.","required":["error"],"properties":{"error":{"type":"object","required":["message","type","code"],"properties":{"message":{"type":"string","description":"Human-readable, in English."},"type":{"type":"string","description":"Error class.","enum":["invalid_request_error","requests","insufficient_quota","server_error"]},"code":{"type":"string","description":"Machine-readable code.","enum":["invalid_request","invalid_api_key","model_not_found","not_found","request_too_large","context_length_exceeded","content_policy_violation","rate_limit_exceeded","insufficient_quota","upstream_unavailable","upstream_error","service_unavailable","upstream_timeout"]},"param":{"type":["string","null"],"description":"The request field the error is about, or null."}}}},"example":{"error":{"message":"Insufficient balance for this request. Top up in the cabinet or lower the output limit.","type":"insufficient_quota","code":"insufficient_quota","param":null}}},"ModelId":{"type":"string","description":"`vendor/model`, e.g. `openai/gpt-5.6-sol`. Live list: https://polyapi.ru/api/catalog.","examples":["openai/gpt-5.6-sol"]},"ModelList":{"type":"object","properties":{"object":{"const":"list"},"data":{"type":"array","items":{"$ref":"#/components/schemas/Model"}}}},"Model":{"type":"object","properties":{"id":{"$ref":"#/components/schemas/ModelId"},"object":{"const":"model"},"created":{"type":"integer","description":"Always 0."},"owned_by":{"type":"string","description":"The vendor part of the id."},"name":{"type":"string","description":"Display name."},"kind":{"type":"string","description":"Which endpoint serves the model: `chat` for /chat/completions and /responses, `embedding` for /embeddings.","enum":["chat","embedding"]},"context_length":{"type":["integer","null"],"description":"Context window, tokens; null when unknown."},"input_modalities":{"type":"array","items":{"type":"string","enum":["text","image"]},"description":"What a request to the model may carry: `image` for models that take images in chat and responses. An image to a model without it is refused with 400."},"pricing":{"type":"object","description":"The price you are charged, whichever provider answers.","properties":{"input":{"type":"number","description":"RUB per 1M input tokens."},"cached":{"type":"number","description":"RUB per 1M cached input tokens."},"output":{"type":"number","description":"RUB per 1M output tokens; 0 for embeddings."},"unit":{"const":"RUB/1M"}}}}},"Tool":{"type":"object","description":"Only `function` and `custom` tools, run by the client. Built-in tools (file search, code interpreter, MCP, ...) are refused with 400, param `tools`; so is web search on /chat/completions, while on /responses a `web_search` tool is dropped.","required":["type"],"properties":{"type":{"enum":["function","custom"]}}},"ToolChoice":{"description":"`none`, `auto`, `required`, an object naming a `function` or `custom` tool, or `allowed_tools` listing only such tools. Anything else: 400, param `tool_choice`.","oneOf":[{"enum":["none","auto","required"]},{"type":"object"}]},"ChatMessage":{"type":"object","required":["role"],"properties":{"role":{"enum":["developer","system","user","assistant","tool","function"]},"content":{"description":"A string or an array of parts of type `text`, `refusal` or `image_url` (`{\"url\": ...}`: a URL or a data URL; user messages only, for a model that takes images). Audio and file parts are refused with 400.","oneOf":[{"type":"string"},{"type":"null"},{"type":"array","items":{"type":"object","required":["type"],"properties":{"type":{"enum":["text","refusal"]},"text":{"type":"string"}}}}]},"name":{"type":"string"},"tool_calls":{"type":"array","items":{"type":"object"}},"tool_call_id":{"type":"string"}}},"ChatRequest":{"type":"object","required":["model","messages"],"additionalProperties":false,"properties":{"model":{"$ref":"#/components/schemas/ModelId"},"messages":{"type":"array","minItems":1,"items":{"$ref":"#/components/schemas/ChatMessage"}},"max_completion_tokens":{"type":"integer","description":"Output limit, 1 to the model maximum. Absent: no limit, the model answers in full. Give either this or `max_tokens`, not both.","minimum":1},"max_tokens":{"type":"integer","description":"Same as `max_completion_tokens`.","minimum":1},"stream":{"type":"boolean","description":"Answer as SSE."},"stream_options":{"type":"object","description":"`include_usage: true` adds the final chunk with `usage` and empty `choices`. Ignored without `stream`.","properties":{"include_usage":{"type":"boolean"}}},"tools":{"type":"array","items":{"$ref":"#/components/schemas/Tool"}},"tool_choice":{"$ref":"#/components/schemas/ToolChoice"},"parallel_tool_calls":{"type":"boolean"},"functions":{"type":"array","items":{"type":"object"},"description":"Legacy function calling."},"function_call":{"description":"Legacy function calling."},"response_format":{"type":"object","description":"`text`, `json_object` or `json_schema`, passed to the model."},"temperature":{"type":"number","description":"Sampling temperature, passed to the model."},"top_p":{"type":"number","description":"Nucleus sampling, passed to the model."},"frequency_penalty":{"type":"number"},"presence_penalty":{"type":"number"},"stop":{},"seed":{"type":"integer"},"n":{"type":"integer","description":"Only 1.","enum":[1]},"logit_bias":{"type":"object"},"logprobs":{"type":"boolean"},"top_logprobs":{"type":"integer"},"reasoning_effort":{"type":"string"},"verbosity":{"type":"string"},"prediction":{"type":"object","description":"Predicted output; rejected prediction tokens are billed as output tokens."},"modalities":{"type":"array","items":{"const":"text"},"description":"Only `[\"text\"]`."},"store":{"type":"boolean","description":"Ignored: nothing is stored."},"service_tier":{"type":"string","description":"Only `auto` or `default`; any other value is refused (400, param `service_tier`).","enum":["auto","default"]},"metadata":{"type":"object","description":"Ignored: nothing is stored."},"user":{"type":"string","description":"End-user id, passed to the model."},"safety_identifier":{"type":"string","description":"End-user id for abuse detection, passed to the model."},"prompt_cache_key":{"type":"string","description":"Accepted, but replaced: the gateway sends its own cache key derived from your account and the conversation, so turns of one conversation hit the same prompt cache."},"prompt_cache_retention":{"type":"string","description":"Accepted and ignored: the cache is keyed by the gateway (see `prompt_cache_key`)."},"prompt_cache_options":{"type":"object","description":"Accepted and ignored, as `prompt_cache_retention`."}}},"ChatCompletion":{"type":"object","description":"As in the OpenAI API. `id` is ours (`chatcmpl-...`), `model` is the id you sent, `system_fingerprint` is absent.","properties":{"id":{"type":"string"},"object":{"const":"chat.completion"},"created":{"type":"integer"},"model":{"$ref":"#/components/schemas/ModelId"},"choices":{"type":"array","items":{"type":"object","properties":{"index":{"type":"integer"},"message":{"$ref":"#/components/schemas/ChatMessage"},"finish_reason":{"type":"string"}}}},"usage":{"$ref":"#/components/schemas/ChatUsage"}}},"ChatUsage":{"type":"object","description":"Tokens you are billed for.","properties":{"prompt_tokens":{"type":"integer"},"completion_tokens":{"type":"integer"},"total_tokens":{"type":"integer"},"prompt_tokens_details":{"type":"object","properties":{"cached_tokens":{"type":"integer"}}}}},"ResponsesRequest":{"type":"object","required":["model","input"],"additionalProperties":false,"properties":{"model":{"$ref":"#/components/schemas/ModelId"},"input":{"description":"A string or an array of items. Item types: `message` (or no type), `function_call`, `function_call_output`, `custom_tool_call`, `custom_tool_call_output`, `reasoning`. Content parts: `input_text`, `output_text`, `text`, `refusal`, `summary_text`, `reasoning_text`, and `input_image` with `image_url` (a URL or a data URL) in user messages and tool outputs, for a model that takes images. A `reasoning` item without `encrypted_content` is dropped. Other items (`item_reference`, a bare `{\"id\"}`, built-in tool calls) and parts (`input_image` by `file_id`, `input_file`, audio) are refused with 400.","oneOf":[{"type":"string"},{"type":"array","items":{"type":"object"}}]},"instructions":{"type":"string","description":"System instructions."},"max_output_tokens":{"type":"integer","description":"Output limit, 1 to the model maximum. Absent: no limit, the model answers in full.","minimum":1},"stream":{"type":"boolean","description":"Answer as SSE."},"stream_options":{"type":"object"},"tools":{"type":"array","items":{"$ref":"#/components/schemas/Tool"}},"tool_choice":{"$ref":"#/components/schemas/ToolChoice"},"parallel_tool_calls":{"type":"boolean"},"max_tool_calls":{"type":"integer"},"text":{"type":"object","description":"Output format and verbosity, passed to the model."},"reasoning":{"type":"object","description":"Reasoning effort and summary, passed to the model."},"include":{"type":"array","items":{"type":"string"},"description":"E.g. `[\"reasoning.encrypted_content\"]` for stateless multi-turn reasoning."},"truncation":{"type":"string"},"temperature":{"type":"number","description":"Sampling temperature, passed to the model."},"top_p":{"type":"number","description":"Nucleus sampling, passed to the model."},"top_logprobs":{"type":"integer"},"store":{"type":"boolean","description":"Always sent as false: nothing is stored."},"service_tier":{"type":"string","description":"Only `auto` or `default`; any other value is refused (400, param `service_tier`).","enum":["auto","default"]},"metadata":{"type":"object"},"user":{"type":"string","description":"End-user id, passed to the model."},"safety_identifier":{"type":"string","description":"End-user id for abuse detection, passed to the model."},"prompt_cache_key":{"type":"string","description":"Accepted, but replaced: the gateway sends its own cache key derived from your account and the conversation, so turns of one conversation hit the same prompt cache."},"prompt_cache_retention":{"type":"string","description":"Accepted and ignored: the cache is keyed by the gateway (see `prompt_cache_key`)."},"prompt_cache_options":{"type":"object","description":"Accepted and ignored, as `prompt_cache_retention`."},"previous_response_id":{"type":"null","description":"Not supported: 400, param `previous_response_id`. Send the full input with every request."},"conversation":{"type":"null","description":"Not supported: 400, param `conversation`. Send the full input with every request."},"background":{"const":false,"description":"Not supported: `true` is refused with 400."}}},"Response":{"type":"object","description":"As in the OpenAI API, with `model` as you sent it.","properties":{"id":{"type":"string"},"object":{"const":"response"},"status":{"type":"string"},"model":{"$ref":"#/components/schemas/ModelId"},"output":{"type":"array","items":{"type":"object"}},"usage":{"type":"object","properties":{"input_tokens":{"type":"integer"},"output_tokens":{"type":"integer"},"total_tokens":{"type":"integer"},"input_tokens_details":{"type":"object","properties":{"cached_tokens":{"type":"integer"}}}}}}},"EmbeddingsRequest":{"type":"object","required":["model","input"],"additionalProperties":false,"properties":{"model":{"$ref":"#/components/schemas/ModelId"},"input":{"description":"A string, a non-empty array of strings, or arrays of token ids.","oneOf":[{"type":"string"},{"type":"array","minItems":1,"items":{"oneOf":[{"type":"string"},{"type":"integer"},{"type":"array","items":{"type":"integer","minimum":0}}]}}]},"encoding_format":{"type":"string","enum":["float","base64"]},"dimensions":{"type":"integer","description":"Passed to the model, if it supports it."},"user":{"type":"string","description":"End-user id, passed to the model."}}},"EmbeddingList":{"type":"object","properties":{"object":{"const":"list"},"model":{"$ref":"#/components/schemas/ModelId"},"data":{"type":"array","items":{"type":"object","properties":{"object":{"const":"embedding"},"index":{"type":"integer"},"embedding":{"oneOf":[{"type":"array","items":{"type":"number"}},{"type":"string"}]}}}},"usage":{"type":"object","properties":{"prompt_tokens":{"type":"integer"},"total_tokens":{"type":"integer"}}}}},"Money":{"type":"object","required":["amount","currency"],"properties":{"amount":{"type":"string","description":"Decimal string, exactly as charged: microroubles without trailing zeros, but at least two decimals (`42.50`, `0.0015`, `0`).","examples":["42.50"]},"currency":{"type":"string","enum":["rub"]}}},"TaskError":{"type":"object","description":"The error of a failed task (`error`) and of an HTTP error (`{ \"error\": … }`). The code is from a closed list.","required":["code","retry","message"],"properties":{"code":{"type":"string","enum":["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"]},"retry":{"type":"string","enum":["as_is","change_input","no"],"description":"`as_is`: repeat the same request. `change_input`: fix the request. `no`: do not repeat. `content_policy_violation` is `as_is` when the result was refused (`stage: output`) and `change_input` when the input was."},"message":{"type":"string","description":"For the developer, in English."},"param":{"type":"string","description":"JSON Pointer to the field of the request body, e.g. `/input/references/1`, `/wait`. For an error in a query parameter of a `GET` it is the parameter's name, e.g. `limit`."},"stage":{"type":"string","enum":["input","output"],"description":"`content_policy_violation` only: what the moderation refused, the prompt or references (`input`) or the made result (`output`). A refused result is made once more on the same provider while the task has time."},"category":{"type":"string","enum":["sexual","minors","violence","hate","self_harm","illegal","public_figure","copyright"],"description":"`content_policy_violation` only, and only when the provider named one: what the moderation found."},"retry_after":{"type":"integer","description":"Seconds, on 429 and 503; also in `Retry-After`."},"required":{"$ref":"#/components/schemas/Money","description":"402: the reserve this task needs."},"balance":{"$ref":"#/components/schemas/Money","description":"402: the balance."}}},"TaskErrorResponse":{"type":"object","required":["error"],"properties":{"error":{"$ref":"#/components/schemas/TaskError"}},"example":{"error":{"code":"insufficient_balance","retry":"as_is","message":"Insufficient balance for this task.","required":{"amount":"17.18","currency":"rub"},"balance":{"amount":"4.00","currency":"rub"}}}},"TaskRequest":{"type":"object","required":["kind","model","input"],"additionalProperties":false,"properties":{"id":{"type":"string","format":"uuid","description":"Your UUID of the task, 36 characters with hyphens (any valid UUID except the zero one; stored lowercase). The same `id` with the same request returns the same task: repeat after a cut connection or a 5xx without a second charge. Another request with this `id`: 409 `task_id_conflict`. The `id` of a purged task: the same request gets 410 `task_purged`, another 409 `task_id_conflict`. Not sent: generated and returned, and a repeat makes a second task."},"kind":{"type":"string","enum":["image/generate"],"description":"What to do; it sets the fields of `input`."},"model":{"type":"string","description":"Required, no default. A model of this `kind` from `GET /tasks/models`.","examples":["google/gemini-3-pro-image"]},"input":{"$ref":"#/components/schemas/ImageGenerateInput"},"options":{"type":"object","description":"Fields only some models have. No model declares any yet: any field is 400."},"metadata":{"type":"object","description":"Up to 16 string pairs: key up to 40 characters `[a-z0-9_]`, value up to 500. Returned in the task, filterable in the list.","additionalProperties":{"type":"string","maxLength":500}},"wait":{"type":"integer","minimum":0,"maximum":60,"default":60,"description":"Seconds to hold the request for the result. Not done in time: `202` and polling."}},"example":{"id":"0b9e3c1a-7f2d-4c5e-9a81-3d6f2b8e4c17","kind":"image/generate","model":"google/gemini-3-pro-image","input":{"prompt":"Рыжий кот на подоконнике, акварель","aspect_ratio":"16:9","resolution":"2K"}}},"ImageGenerateInput":{"type":"object","description":"The fields of `image/generate`. The values a model accepts are in `GET /tasks/models`; a value outside them or an unknown field is 400 with `param`.","required":["prompt"],"additionalProperties":false,"properties":{"prompt":{"type":"string","description":"The picture or the edit."},"count":{"type":"integer","minimum":1,"default":1,"description":"How many pictures; the most is the model's."},"aspect_ratio":{"type":"string","examples":["16:9"]},"resolution":{"type":"string","examples":["2K"]},"size":{"type":"string","description":"The frame in pixels, `1024x1024`, if the model sets it so. Not together with `aspect_ratio` and `resolution`.","examples":["1024x1024"]},"format":{"type":"string","enum":["png","jpeg","webp"]},"quality":{"type":"string","description":"If the model knows it."},"seed":{"type":"integer","minimum":0,"maximum":4294967295,"description":"If a moderation refuses the made result, the repeat is sent with a new seed; `output[].seed`, if the model reports it, is the one used."},"references":{"type":"array","items":{"type":"string"},"description":"https or data URLs. A data URL is checked before the reserve (`reference_too_large`, `reference_unsupported`); an https URL is downloaded by the provider, and if it cannot, the task fails without a charge. A data URL is at most 10 MiB."}}},"TaskEvent":{"type":"object","required":["type","at"],"description":"Skip unknown `type` values.","properties":{"type":{"type":"string","enum":["task.queued","attempt.started","attempt.failed","attempt.lost","task.completed","task.failed"]},"at":{"type":"string","format":"date-time"},"attempt":{"type":"integer"},"reason":{"type":"string","description":"On `attempt.failed` and `attempt.lost`: a code of `TaskError`."},"stage":{"type":"string","enum":["input","output"],"description":"On `attempt.failed` with `content_policy_violation`: as `TaskError.stage`."},"category":{"type":"string","enum":["sexual","minors","violence","hate","self_harm","illegal","public_figure","copyright"],"description":"On `attempt.failed` with `content_policy_violation`, when the provider named one: as `TaskError.category`."}}},"TaskPart":{"type":"object","required":["type","media_type","bytes","retained_until"],"description":"A part of `output`. `image` has all the fields below; a part past `retained_until` has no `url` and `expires_at`, and `expired: true`.","properties":{"type":{"type":"string","enum":["image"]},"url":{"type":"string","description":"A signed link to the file: lives at least an hour, signed again on every `GET`, the same within the hour."},"media_type":{"type":"string","examples":["image/png"]},"expires_at":{"type":"string","format":"date-time","description":"When this link ends."},"retained_until":{"type":"string","format":"date-time","description":"The file is kept 30 days from completion."},"width":{"type":"integer"},"height":{"type":"integer"},"bytes":{"type":"integer"},"seed":{"type":"integer","description":"If the model knows it. After a refused result it is the seed of the repeat, not the one sent."},"expired":{"type":"boolean","enum":[true]}}},"Task":{"type":"object","required":["id","kind","model","status","events"],"description":"One object for every state. No field is null: absent means no value. Branch on `status` only; the HTTP code (200, 202) is a hint and `events` the history.","properties":{"id":{"type":"string","format":"uuid"},"kind":{"type":"string"},"model":{"type":"string"},"status":{"type":"string","enum":["queued","in_progress","completed","failed"]},"events":{"type":"array","items":{"$ref":"#/components/schemas/TaskEvent"}},"output":{"type":"array","items":{"$ref":"#/components/schemas/TaskPart"},"description":"Only `completed`. Not more parts than `count`: pictures refused by moderation are dropped and not charged."},"error":{"$ref":"#/components/schemas/TaskError","description":"Only `failed`."},"usage":{"type":"object","properties":{"reserved":{"$ref":"#/components/schemas/Money","description":"While running: reserved, the most that will be charged."},"cost":{"$ref":"#/components/schemas/Money","description":"When done: charged, final. `0` for a failed task."}}},"deadline_at":{"type":"string","format":"date-time","description":"While running: by then the task has finished at the latest (after it: `failed`, `task_expired`)."},"poll_after":{"type":"integer","description":"While running: seconds to wait before asking again; also in `Retry-After`."},"metadata":{"type":"object","additionalProperties":{"type":"string"}}}},"TaskList":{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Task"}},"next_cursor":{"type":"string","description":"Pass as `cursor` for the next page."}}},"TaskQuote":{"type":"object","required":["cost","balance","sufficient"],"properties":{"cost":{"$ref":"#/components/schemas/Money"},"balance":{"$ref":"#/components/schemas/Money"},"sufficient":{"type":"boolean","description":"True if the price is within the account's balance. Only the balance is compared: the key's spending cap, the rate limits and the availability of the model are not checked, so a create can still answer `402` `key_limit_reached`, `429` or `503`."}}},"TaskModel":{"type":"object","required":["id","name","kind","input","price"],"properties":{"id":{"type":"string","description":"The value of `model`."},"name":{"type":"string"},"kind":{"type":"string"},"input":{"type":"object","description":"The fields of the kind with this model's allowed values: `type`, `values` (for `format` and `quality`), `max_length`, `min`, `max`, `max_items`, `max_bytes`, `media_types`, and a `default` where one exists.","additionalProperties":{"type":"object"}},"variants":{"type":"array","description":"The frames the model accepts. Every variant has `aspect_ratio`, `resolution` and `size`; a request sends either the pair or `size`. `default: true` marks the default one.","items":{"type":"object"}},"price":{"type":"object","required":["currency","per","depends_on","rows"],"properties":{"currency":{"type":"string"},"per":{"type":"string","enum":["image","second","request"],"description":"Only `image` for now."},"depends_on":{"type":"array","items":{"type":"string"},"description":"The fields the price depends on."},"rows":{"type":"array","items":{"type":"object","description":"The values of the `depends_on` fields and `amount`."}}}}}},"TaskModelList":{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/TaskModel"}}}}}}}