{
  "openapi": "3.1.0",
  "info": {
    "title": "Konductor API",
    "version": "0.1.0",
    "description": "Шлюз Konductor: доступ к моделям в форматах OpenAI Chat Completions, OpenAI Responses и Anthropic Messages по ключу компании, оплата из рублёвого баланса компании. Base URL для Anthropic SDK и Claude Code — https://llm.kodzavod.ru (без /v1). Источник страницы документации (Ф5)."
  },
  "servers": [
    {
      "url": "https://llm.kodzavod.ru"
    }
  ],
  "security": [
    {
      "companyKey": []
    }
  ],
  "paths": {
    "/v1/chat/completions": {
      "post": {
        "operationId": "createChatCompletion",
        "summary": "Ответ модели на диалог (со стримом и без)",
        "description": "Перед отправкой модели шлюз резервирует деньги: оценка входа × цена входа + max_tokens × цена выхода (без max_tokens — потолок вывода модели). После ответа резерв закрывается и списывается факт по usage. Не хватает доступного баланса на резерв — 402. Модель вне политики компании или ключа (allowed_models) — 403 model_not_allowed до резерва, деньги не трогаются. Пробный ключ: модель из списка запрета пробы — 403 model_not_in_trial до резерва; больше trial_rate_per_minute запросов в минуту на пробный кошелёк — 429 rate_limit_exceeded с limit_scope trial. С stream: true ответ — text/event-stream: чанки chat.completion.chunk и комментарии поставщика (: OPENROUTER PROCESSING) как есть, последний чанк несёт usage с cost_rub, затем data: [DONE]; списание — по usage этого чанка. Ошибки до первого байта (402, 429, отказ поставщика) — обычный JSON без стрима. Стрим — не дольше 900 с и без тишины поставщика дольше 290 с. Стрим, оборванный до usage-чанка или закрытый клиентом, — резерв ждёт сверки с поставщиком; клиенту на связи приходит чанк {error: {code: upstream_unknown, reservation_id}}. Чанк ошибки поставщика (error, finish_reason: error) пересылается как есть.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ChatCompletionRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Ответ модели; usage.cost_rub — списано с баланса, ₽. Со стримом — text/event-stream: чанки chat.completion.chunk, usage-чанк с usage.cost_rub, data: [DONE].",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ChatCompletionResponse"
                },
                "example": {
                  "id": "chatcmpl-example",
                  "object": "chat.completion",
                  "created": 1761000000,
                  "model": "<model-id>",
                  "choices": [
                    {
                      "index": 0,
                      "message": {
                        "role": "assistant",
                        "content": "Привет! Чем помочь?"
                      },
                      "finish_reason": "stop"
                    }
                  ],
                  "usage": {
                    "prompt_tokens": 9,
                    "completion_tokens": 7,
                    "total_tokens": 16,
                    "cost_rub": "0.02"
                  }
                }
              },
              "text/event-stream": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/InsufficientBalance"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "413": {
            "$ref": "#/components/responses/BadRequest"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          },
          "502": {
            "$ref": "#/components/responses/ServerError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/v1/key": {
      "get": {
        "operationId": "getCurrentKey",
        "security": [
          {
            "companyKey": []
          },
          {
            "anthropicApiKey": []
          }
        ],
        "summary": "Статус ключа, его лимит и остаток",
        "description": "Ключ компании — в Authorization: Bearer или x-api-key (как у /v1/messages); разные ключи в двух заголовках — 401. Отвечает только активному ключу. Расход окна считается так же, как проверка лимита перед резервом: списания за окно плюс деньги в резерве незавершённых запросов. Баланс кошелька и лимит компании — в поле company, только если компания разрешила ключам их видеть; иначе company — null. Запрос не резервирует деньги, не пишется в журнал запросов и не расходует лимит запросов в минуту.",
        "responses": {
          "200": {
            "description": "Ключ и его лимит; деньги — копейки строкой.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/KeyInfo"
                },
                "example": {
                  "data": {
                    "key_id": "key-example",
                    "label": "Рабочий ключ",
                    "status": "active",
                    "rpm_limit": 60,
                    "limit_kop": "100000",
                    "limit_window": "month",
                    "spent_kop": "2500",
                    "in_flight_kop": "0",
                    "limit_remaining_kop": "97500",
                    "reset_at": "2026-11-30T21:00:00.000Z",
                    "company": null
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/v1/messages": {
      "post": {
        "operationId": "createMessage",
        "summary": "Ответ модели в формате Anthropic Messages (со стримом и без)",
        "description": "Совместим с Anthropic SDK и Claude Code: base URL https://llm.kodzavod.ru, ключ компании в x-api-key или Authorization: Bearer. Заголовки anthropic-version и anthropic-beta передаются модели. Модели передаются только поля messages, system, tools, tool_choice, temperature, top_p, top_k, stop_sequences, thinking; остальные (metadata, service_tier, mcp_servers, container …) отбрасываются. Серверные инструменты Anthropic (tools с type, кроме custom) — 400: их цена не входит в резерв. В content сообщений принимаются блоки text, tool_use, tool_result (content строкой или text-блоками), thinking и redacted_thinking; system — строкой или text-блоками. image, document и любой другой тип блока, в том числе внутри tool_result и в system, — 400 invalid_request_error с code unsupported_content_part и param до резерва. Перед отправкой шлюз резервирует деньги: оценка входа (system, messages, tools и служебный промпт инструментов) × цена входа + max_tokens × цена выхода; после ответа резерв закрывается и списывается факт по usage (в стриме — накопительный usage из message_delta; токены записи и чтения кэша оплачиваются по цене входа). Не хватает доступного баланса на резерв — 402 billing_error (code insufficient_balance); резерв выходит за лимит ключа или компании — 402 billing_error (code key_limit_exceeded / company_limit_exceeded); превышен лимит запросов в минуту — 429 rate_limit_error (code rate_limit_exceeded). Ошибки — в формате Anthropic: {type: 'error', error: {type, message, code}}. Стрим, оборванный до message_stop, завершается событием error с code upstream_unknown: резерв ждёт сверки с поставщиком. Модель вне политики компании или ключа (allowed_models) — 403 permission_error (code model_not_allowed) до резерва, деньги не трогаются. Пробный ключ: модель из списка запрета пробы — 403 permission_error (code model_not_in_trial); больше trial_rate_per_minute запросов в минуту на пробный кошелёк — 429 rate_limit_error с limit_scope trial.",
        "security": [
          {
            "anthropicApiKey": []
          },
          {
            "companyKey": []
          }
        ],
        "parameters": [
          {
            "name": "anthropic-version",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 1024
            },
            "description": "Передаётся модели как есть."
          },
          {
            "name": "anthropic-beta",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 1024
            },
            "description": "Передаётся модели как есть."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/MessagesRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Ответ модели. Без стрима — объект Message, usage.cost_rub — списано с баланса, ₽. Со стримом — text/event-stream: message_start … message_stop; message_delta несёт usage.cost_rub по накопительному usage.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MessagesResponse"
                },
                "example": {
                  "id": "msg-example",
                  "type": "message",
                  "role": "assistant",
                  "model": "<model-id>",
                  "content": [
                    {
                      "type": "text",
                      "text": "Привет! Чем помочь?"
                    }
                  ],
                  "stop_reason": "end_turn",
                  "usage": {
                    "input_tokens": 9,
                    "output_tokens": 7,
                    "cost_rub": "0.02"
                  }
                }
              },
              "text/event-stream": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/AnthropicBadRequest"
          },
          "401": {
            "$ref": "#/components/responses/AnthropicUnauthorized"
          },
          "402": {
            "$ref": "#/components/responses/AnthropicBilling"
          },
          "403": {
            "$ref": "#/components/responses/AnthropicForbidden"
          },
          "413": {
            "$ref": "#/components/responses/AnthropicBadRequest"
          },
          "429": {
            "$ref": "#/components/responses/AnthropicRateLimited"
          },
          "500": {
            "$ref": "#/components/responses/AnthropicServerError"
          },
          "502": {
            "$ref": "#/components/responses/AnthropicServerError"
          },
          "503": {
            "$ref": "#/components/responses/AnthropicServiceUnavailable"
          }
        }
      }
    },
    "/v1/responses": {
      "post": {
        "operationId": "createResponse",
        "summary": "Ответ модели в формате OpenAI Responses без хранения ответов (со стримом и без)",
        "description": "Ответы не хранятся ни у шлюза, ни у поставщика: передавай всю историю диалога в input каждого запроса. previous_response_id (не null), store: true, background: true и conversation — 400 invalid_request с param до резерва, деньги не резервируются. Модели передаются только поля input, instructions, tools, tool_choice, parallel_tool_calls, temperature, top_p, top_logprobs, frequency_penalty, presence_penalty, text; остальные (reasoning, include, metadata, plugins, models, service_tier, prompt …) отбрасываются. Инструменты — только type function и custom; остальные (web_search_preview, file_search, code_interpreter, mcp, openrouter:* …) — 400: их цена не входит в резерв. В input принимаются строка и элементы message (части input_text, output_text, refusal), function_call, function_call_output, custom_tool_call, custom_tool_call_output, reasoning; input_image, input_file, item_reference и любой другой тип — 400 unsupported_content_part с param до резерва. Перед отправкой шлюз резервирует деньги: оценка входа (instructions, input, tools) × цена входа + max_output_tokens × цена выхода (без max_output_tokens — потолок вывода модели). После ответа резерв закрывается и списывается факт по usage: input_tokens по цене входа (кэш внутри), output_tokens по цене выхода (рассуждения внутри). Не хватает доступного баланса на резерв — 402. Со stream: true — text/event-stream: события response.created … response.completed как есть; response.completed (или response.incomplete, response.failed) несёт usage с cost_rub, списание — по нему. Ошибки до первого байта — обычный JSON без стрима. Стрим — не дольше 900 с и без тишины поставщика дольше 290 с. Стрим без response.completed или закрытый клиентом — резерв ждёт сверки с поставщиком; клиенту на связи приходит event: error с code upstream_unknown и reservation_id. Модель вне политики компании или ключа (allowed_models) — 403 model_not_allowed до резерва, деньги не трогаются. Пробный ключ: модель из списка запрета пробы — 403 model_not_in_trial до резерва; больше trial_rate_per_minute запросов в минуту на пробный кошелёк — 429 rate_limit_exceeded с limit_scope trial.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ResponsesRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Ответ модели. Без стрима — объект response, usage.cost_rub — списано с баланса, ₽. Со стримом — text/event-stream: response.created … response.completed; usage.cost_rub — в response.completed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResponsesResponse"
                }
              },
              "text/event-stream": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "400": {
            "description": "Неверный запрос. previous_response_id, store: true, background: true или conversation — code invalid_request, param с именем поля и текст «Передавай всю историю в запросе: сохранение ответов не поддерживается.»; деньги не резервируются.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "previous_response_id": {
                    "summary": "previous_response_id не поддерживается",
                    "value": {
                      "error": {
                        "message": "Передавай всю историю в запросе: сохранение ответов не поддерживается.",
                        "type": "invalid_request_error",
                        "code": "invalid_request",
                        "param": "previous_response_id"
                      }
                    }
                  },
                  "store": {
                    "summary": "store: true не поддерживается",
                    "value": {
                      "error": {
                        "message": "Передавай всю историю в запросе: сохранение ответов не поддерживается.",
                        "type": "invalid_request_error",
                        "code": "invalid_request",
                        "param": "store"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/InsufficientBalance"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "413": {
            "$ref": "#/components/responses/BadRequest"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          },
          "502": {
            "$ref": "#/components/responses/ServerError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/v1/models": {
      "get": {
        "operationId": "listModels",
        "summary": "Каталог моделей действующей версии тарифа",
        "description": "Без ключа — публично: весь каталог, лимит 60 запросов в минуту с одного IP на каждом экземпляре шлюза (при N экземплярах — до N × 60), CORS только для https://kodzavod.ru. С ключом компании — та же форма ответа, но в data только модели, разрешённые политиками компании и ключа (allowed_models; без политик — весь каталог; у пробного ключа — без моделей из списка запрета пробы), и тот же вход, что у /v1/chat/completions: лимит запросов в минуту ключа и компании (429 rate_limit_exceeded), 503 service_unavailable и gateway_stopped; анонимный лимит не применяется. Цены — рубли за миллион токенов, подпись НДС — price_version.vat_label. availability и p50_24h — за 24 ч, null при меньше чем 20 запросах; пересчёт не чаще раза в 60 с.",
        "security": [
          {},
          {
            "companyKey": []
          }
        ],
        "responses": {
          "200": {
            "description": "Каталог действующей версии тарифа.",
            "headers": {
              "Cache-Control": {
                "description": "Без ключа — public, max-age=60: каталог один на всех, браузер и прокси держат его 60 с. С ключом — private, no-store: data отфильтрована политиками ключа.",
                "schema": {
                  "type": "string",
                  "enum": [
                    "public, max-age=60",
                    "private, no-store"
                  ]
                }
              },
              "Vary": {
                "description": "Без ключа — Origin и Authorization: кэш не отдаст ответ без ключа запросу с ключом.",
                "schema": {
                  "type": "string"
                }
              },
              "Access-Control-Expose-Headers": {
                "description": "Только при Origin https://kodzavod.ru: Retry-After.",
                "schema": {
                  "type": "string",
                  "enum": [
                    "Retry-After"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ModelsResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/ModelsRateLimited"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "companyKey": {
        "type": "http",
        "scheme": "bearer",
        "description": "Ключ компании: Authorization: Bearer sk-kz-…"
      },
      "anthropicApiKey": {
        "type": "apiKey",
        "in": "header",
        "name": "x-api-key",
        "description": "Ключ компании для Anthropic SDK: x-api-key: sk-kz-…"
      }
    },
    "schemas": {
      "ChatCompletionRequest": {
        "type": "object",
        "example": {
          "model": "<model-id>",
          "messages": [
            {
              "role": "user",
              "content": "Привет!"
            }
          ],
          "max_tokens": 256
        },
        "required": [
          "model",
          "messages"
        ],
        "properties": {
          "model": {
            "type": "string",
            "description": "Модель из каталога действующей версии тарифа."
          },
          "messages": {
            "type": "array",
            "minItems": 1,
            "items": {
              "type": "object"
            }
          },
          "max_tokens": {
            "type": "integer",
            "minimum": 1,
            "description": "Предел ответа. Без него резерв считается по потолку вывода модели."
          },
          "max_completion_tokens": {
            "type": "integer",
            "minimum": 1
          },
          "stream": {
            "type": "boolean",
            "description": "true — ответ text/event-stream (SSE)."
          },
          "tools": {
            "type": "array",
            "items": {
              "type": "object"
            }
          },
          "provider": {
            "type": "object",
            "description": "Поля data_collection, allow_fallbacks и max_price задаёт шлюз; only — поставщики модели из каталога, клиентский only не действует."
          }
        },
        "additionalProperties": true
      },
      "ChatCompletionResponse": {
        "type": "object",
        "required": [
          "choices",
          "usage"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "model": {
            "type": "string"
          },
          "choices": {
            "type": "array",
            "items": {
              "type": "object"
            }
          },
          "usage": {
            "type": "object",
            "required": [
              "prompt_tokens",
              "completion_tokens",
              "cost_rub"
            ],
            "properties": {
              "prompt_tokens": {
                "type": "integer"
              },
              "completion_tokens": {
                "type": "integer"
              },
              "total_tokens": {
                "type": "integer"
              },
              "cost_rub": {
                "type": "string",
                "pattern": "^\\d+\\.\\d{2}$"
              }
            }
          }
        }
      },
      "ResponsesRequest": {
        "type": "object",
        "required": [
          "model",
          "input"
        ],
        "properties": {
          "model": {
            "type": "string",
            "description": "Модель из каталога действующей версии тарифа."
          },
          "input": {
            "oneOf": [
              {
                "type": "string",
                "minLength": 1
              },
              {
                "type": "array",
                "minItems": 1,
                "items": {
                  "type": "object"
                }
              }
            ],
            "description": "Вся история диалога: шлюз и поставщик ответы не хранят."
          },
          "instructions": {
            "type": [
              "string",
              "null"
            ]
          },
          "max_output_tokens": {
            "type": "integer",
            "minimum": 1,
            "description": "Предел ответа вместе с рассуждениями. Без него резерв считается по потолку вывода модели."
          },
          "stream": {
            "type": "boolean",
            "description": "true — ответ text/event-stream (SSE)."
          },
          "tools": {
            "type": "array",
            "items": {
              "type": "object"
            },
            "description": "Только type function и custom."
          },
          "store": {
            "type": [
              "boolean",
              "null"
            ],
            "description": "true — 400: ответы не хранятся."
          },
          "previous_response_id": {
            "type": "null",
            "description": "Не null — 400: передавай всю историю в input."
          },
          "provider": {
            "type": "object",
            "description": "Поля data_collection, allow_fallbacks и max_price задаёт шлюз; only — поставщики модели из каталога, клиентский only не действует."
          }
        },
        "additionalProperties": true
      },
      "ResponsesResponse": {
        "type": "object",
        "required": [
          "output",
          "usage"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "object": {
            "type": "string"
          },
          "model": {
            "type": "string"
          },
          "status": {
            "type": "string"
          },
          "output": {
            "type": "array",
            "items": {
              "type": "object"
            }
          },
          "usage": {
            "type": "object",
            "required": [
              "input_tokens",
              "output_tokens",
              "cost_rub"
            ],
            "properties": {
              "input_tokens": {
                "type": "integer"
              },
              "output_tokens": {
                "type": "integer"
              },
              "total_tokens": {
                "type": "integer"
              },
              "cost_rub": {
                "type": "string",
                "pattern": "^\\d+\\.\\d{2}$"
              }
            }
          }
        }
      },
      "KeyLimit": {
        "type": "object",
        "required": [
          "limit_kop",
          "limit_window",
          "spent_kop",
          "in_flight_kop",
          "limit_remaining_kop",
          "reset_at"
        ],
        "properties": {
          "limit_kop": {
            "type": [
              "string",
              "null"
            ],
            "description": "Лимит за окно, копейки; null — лимита нет."
          },
          "limit_window": {
            "enum": [
              "none",
              "day",
              "month"
            ],
            "description": "Окно: day — с 00:00 МСК, month — с 1-го числа 00:00 МСК, none — без периода."
          },
          "spent_kop": {
            "type": "string",
            "description": "Расход окна: списания за окно плюс деньги в резерве незавершённых запросов, копейки."
          },
          "in_flight_kop": {
            "type": "string",
            "description": "Из расхода окна — деньги в резерве незавершённых запросов, копейки."
          },
          "limit_remaining_kop": {
            "type": [
              "string",
              "null"
            ],
            "description": "Остаток лимита (не меньше 0), копейки; null — лимита нет."
          },
          "reset_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "Начало следующего окна (UTC); null для окна none."
          }
        }
      },
      "KeyInfo": {
        "type": "object",
        "required": [
          "data"
        ],
        "properties": {
          "data": {
            "allOf": [
              {
                "$ref": "#/components/schemas/KeyLimit"
              },
              {
                "type": "object",
                "required": [
                  "key_id",
                  "label",
                  "status",
                  "rpm_limit",
                  "company"
                ],
                "properties": {
                  "key_id": {
                    "type": "string"
                  },
                  "label": {
                    "type": "string",
                    "description": "Имя ключа в компании."
                  },
                  "status": {
                    "const": "active"
                  },
                  "rpm_limit": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 1,
                    "description": "Запросов в минуту для ключа; null — без предела."
                  },
                  "company": {
                    "oneOf": [
                      {
                        "type": "null"
                      },
                      {
                        "allOf": [
                          {
                            "$ref": "#/components/schemas/KeyLimit"
                          },
                          {
                            "type": "object",
                            "required": [
                              "balance_kop"
                            ],
                            "properties": {
                              "balance_kop": {
                                "type": "string",
                                "description": "Доступный баланс кошелька компании, копейки."
                              }
                            }
                          }
                        ]
                      }
                    ],
                    "description": "Баланс и лимит компании; null, если компания не разрешила ключам их видеть."
                  }
                }
              }
            ]
          }
        }
      },
      "Error": {
        "type": "object",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "object",
            "required": [
              "message",
              "type",
              "code"
            ],
            "properties": {
              "message": {
                "type": "string"
              },
              "type": {
                "type": "string"
              },
              "code": {
                "type": "string"
              },
              "param": {
                "type": "string"
              }
            }
          }
        }
      },
      "InsufficientBalanceError": {
        "type": "object",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "object",
            "required": [
              "message",
              "type",
              "code",
              "available_kop",
              "required_reserve_kop",
              "hint"
            ],
            "properties": {
              "message": {
                "type": "string"
              },
              "type": {
                "const": "insufficient_balance"
              },
              "code": {
                "const": "insufficient_balance"
              },
              "available_kop": {
                "type": "string",
                "description": "Доступный баланс, копейки."
              },
              "required_reserve_kop": {
                "type": "string",
                "description": "Нужный резерв запроса, копейки."
              },
              "hint": {
                "const": "reduce_max_tokens"
              }
            }
          }
        }
      },
      "ModelsResponse": {
        "type": "object",
        "required": [
          "object",
          "price_version",
          "data"
        ],
        "properties": {
          "object": {
            "const": "list"
          },
          "price_version": {
            "type": "object",
            "required": [
              "id",
              "effective_from",
              "vat_mode",
              "vat_rate_bp",
              "price_basis",
              "vat_label"
            ],
            "properties": {
              "id": {
                "type": "string"
              },
              "effective_from": {
                "type": "integer",
                "description": "С какого момента действует версия, мс Unix epoch."
              },
              "vat_mode": {
                "type": "string",
                "enum": [
                  "exempt",
                  "special_5",
                  "special_7",
                  "general_22"
                ]
              },
              "vat_rate_bp": {
                "type": "integer",
                "description": "Ставка НДС в базисных пунктах (2200 = 22 %)."
              },
              "price_basis": {
                "type": "string",
                "enum": [
                  "excl",
                  "incl"
                ],
                "description": "excl — цены без НДС; incl — НДС уже в цене."
              },
              "vat_label": {
                "type": "string",
                "description": "Подпись к ценам: «без НДС» или «в т. ч. НДС N %»."
              }
            }
          },
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ModelEntry"
            }
          }
        }
      },
      "ModelEntry": {
        "type": "object",
        "required": [
          "id",
          "object",
          "pricing",
          "context_length",
          "max_output_tokens",
          "availability",
          "p50_24h",
          "retire_at",
          "owned_by",
          "provider_countries"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "Идентификатор модели для поля model запроса."
          },
          "object": {
            "const": "model"
          },
          "pricing": {
            "type": "object",
            "required": [
              "input_rub_per_mtok",
              "output_rub_per_mtok"
            ],
            "properties": {
              "input_rub_per_mtok": {
                "type": "string",
                "pattern": "^\\d+\\.\\d{2}$",
                "description": "₽ за миллион токенов входа."
              },
              "output_rub_per_mtok": {
                "type": "string",
                "pattern": "^\\d+\\.\\d{2}$",
                "description": "₽ за миллион токенов ответа."
              }
            }
          },
          "context_length": {
            "type": "integer"
          },
          "max_output_tokens": {
            "type": "integer",
            "description": "Наибольший max_tokens для модели."
          },
          "availability": {
            "type": [
              "number",
              "null"
            ],
            "minimum": 0,
            "maximum": 1,
            "description": "Доля успешных запросов за 24 ч."
          },
          "p50_24h": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Медиана времени до первого токена за 24 ч, мс."
          },
          "retire_at": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Дата вывода модели из каталога, мс Unix epoch."
          },
          "owned_by": {
            "type": "string",
            "description": "Автор модели — префикс id до «/»."
          },
          "provider_countries": {
            "type": [
              "array",
              "null"
            ],
            "items": {
              "type": "string",
              "pattern": "^[A-Z]{2}$"
            },
            "minItems": 1,
            "description": "Страны поставщиков, которым шлюз отправляет запрос к модели (ISO 3166-1 alpha-2)."
          }
        }
      },
      "LimitExceededError": {
        "type": "object",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "object",
            "required": [
              "message",
              "type",
              "code",
              "limit_scope",
              "limit_kop",
              "spent_kop",
              "required_reserve_kop",
              "limit_window",
              "reset_at"
            ],
            "properties": {
              "message": {
                "type": "string"
              },
              "type": {
                "enum": [
                  "key_limit_exceeded",
                  "company_limit_exceeded"
                ]
              },
              "code": {
                "enum": [
                  "key_limit_exceeded",
                  "company_limit_exceeded"
                ]
              },
              "limit_scope": {
                "enum": [
                  "key",
                  "company"
                ],
                "description": "Чей лимит исчерпан: сначала проверяется ключ, потом компания."
              },
              "limit_kop": {
                "type": "string",
                "description": "Лимит за окно, копейки."
              },
              "spent_kop": {
                "type": "string",
                "description": "Расход окна: списания за окно плюс деньги в резерве незавершённых запросов, копейки."
              },
              "required_reserve_kop": {
                "type": "string",
                "description": "Нужный резерв запроса, копейки."
              },
              "limit_window": {
                "enum": [
                  "none",
                  "day",
                  "month"
                ],
                "description": "Окно: day — с 00:00 МСК, month — с 1-го числа 00:00 МСК, none — без периода."
              },
              "reset_at": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "date-time",
                "description": "Начало следующего окна (UTC); null для окна none."
              }
            }
          }
        }
      },
      "RateLimitedError": {
        "type": "object",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "object",
            "required": [
              "message",
              "type",
              "code",
              "limit_scope",
              "rpm_limit"
            ],
            "properties": {
              "message": {
                "type": "string"
              },
              "type": {
                "const": "rate_limit_error"
              },
              "code": {
                "const": "rate_limit_exceeded"
              },
              "limit_scope": {
                "enum": [
                  "key",
                  "company",
                  "trial"
                ],
                "description": "Чей счётчик частоты исчерпан: ключа, компании или пробного кошелька (ключ без компании, порог trial_rate_per_minute)."
              },
              "rpm_limit": {
                "type": "integer",
                "minimum": 1,
                "description": "Запросов в минуту; счётчик общий для всех экземпляров шлюза."
              }
            }
          }
        }
      },
      "ForbiddenError": {
        "type": "object",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "object",
            "required": [
              "message",
              "type",
              "code",
              "reason"
            ],
            "properties": {
              "message": {
                "type": "string"
              },
              "type": {
                "const": "permission_error"
              },
              "code": {
                "type": "string"
              },
              "reason": {
                "type": "string",
                "enum": [
                  "key_paused",
                  "key_blocked",
                  "company_blocked",
                  "trial_blocked",
                  "model_not_allowed",
                  "model_not_in_trial"
                ]
              },
              "key_id": {
                "type": "string"
              },
              "param": {
                "const": "model",
                "description": "Только у model_not_allowed и model_not_in_trial."
              },
              "policy": {
                "type": "string",
                "enum": [
                  "company",
                  "key"
                ],
                "description": "Только у model_not_allowed: чей список моделей закрыл модель — компании или ключа."
              }
            }
          }
        }
      },
      "MessagesRequest": {
        "type": "object",
        "example": {
          "model": "<model-id>",
          "max_tokens": 256,
          "messages": [
            {
              "role": "user",
              "content": "Привет!"
            }
          ]
        },
        "required": [
          "model",
          "max_tokens",
          "messages"
        ],
        "properties": {
          "model": {
            "type": "string",
            "description": "Модель из каталога действующей версии тарифа."
          },
          "max_tokens": {
            "type": "integer",
            "minimum": 1,
            "description": "Предел ответа, не больше потолка вывода модели в версии тарифа."
          },
          "messages": {
            "type": "array",
            "minItems": 1,
            "items": {
              "type": "object",
              "description": "Сообщение {role, content}: content — строка или массив блоков text, tool_use, tool_result, thinking, redacted_thinking; image, document и прочие блоки — 400 unsupported_content_part."
            }
          },
          "system": {
            "oneOf": [
              {
                "type": "string"
              },
              {
                "type": "array",
                "items": {
                  "type": "object"
                }
              }
            ]
          },
          "tools": {
            "type": "array",
            "items": {
              "type": "object"
            }
          },
          "stream": {
            "type": "boolean"
          },
          "provider": {
            "type": "object",
            "description": "Поля data_collection, allow_fallbacks и max_price задаёт шлюз; only — поставщики модели из каталога, клиентский only не действует."
          }
        },
        "additionalProperties": true
      },
      "MessagesResponse": {
        "type": "object",
        "required": [
          "type",
          "content",
          "usage"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "type": {
            "const": "message"
          },
          "role": {
            "const": "assistant"
          },
          "model": {
            "type": "string"
          },
          "content": {
            "type": "array",
            "items": {
              "type": "object"
            }
          },
          "stop_reason": {
            "type": [
              "string",
              "null"
            ]
          },
          "usage": {
            "type": "object",
            "required": [
              "input_tokens",
              "output_tokens",
              "cost_rub"
            ],
            "properties": {
              "input_tokens": {
                "type": "integer"
              },
              "output_tokens": {
                "type": "integer"
              },
              "cache_creation_input_tokens": {
                "type": [
                  "integer",
                  "null"
                ]
              },
              "cache_read_input_tokens": {
                "type": [
                  "integer",
                  "null"
                ]
              },
              "cost_rub": {
                "type": "string",
                "pattern": "^\\d+\\.\\d{2}$"
              }
            }
          }
        }
      },
      "AnthropicError": {
        "type": "object",
        "required": [
          "type",
          "error"
        ],
        "properties": {
          "type": {
            "const": "error"
          },
          "error": {
            "type": "object",
            "required": [
              "type",
              "message",
              "code"
            ],
            "properties": {
              "type": {
                "type": "string",
                "enum": [
                  "invalid_request_error",
                  "authentication_error",
                  "billing_error",
                  "permission_error",
                  "not_found_error",
                  "request_too_large",
                  "rate_limit_error",
                  "api_error",
                  "overloaded_error"
                ]
              },
              "message": {
                "type": "string"
              },
              "code": {
                "type": "string"
              },
              "param": {
                "type": "string"
              }
            }
          }
        }
      },
      "AnthropicBillingError": {
        "type": "object",
        "required": [
          "type",
          "error"
        ],
        "properties": {
          "type": {
            "const": "error"
          },
          "error": {
            "type": "object",
            "required": [
              "type",
              "message",
              "code",
              "available_kop",
              "required_reserve_kop",
              "hint"
            ],
            "properties": {
              "type": {
                "const": "billing_error"
              },
              "message": {
                "type": "string"
              },
              "code": {
                "const": "insufficient_balance"
              },
              "available_kop": {
                "type": "string",
                "description": "Доступный баланс, копейки."
              },
              "required_reserve_kop": {
                "type": "string",
                "description": "Нужный резерв запроса, копейки."
              },
              "hint": {
                "const": "reduce_max_tokens"
              }
            }
          }
        }
      },
      "AnthropicLimitExceededError": {
        "type": "object",
        "required": [
          "type",
          "error"
        ],
        "properties": {
          "type": {
            "const": "error"
          },
          "error": {
            "type": "object",
            "required": [
              "type",
              "message",
              "code",
              "limit_scope",
              "limit_kop",
              "spent_kop",
              "required_reserve_kop",
              "limit_window",
              "reset_at"
            ],
            "properties": {
              "type": {
                "const": "billing_error"
              },
              "message": {
                "type": "string"
              },
              "code": {
                "enum": [
                  "key_limit_exceeded",
                  "company_limit_exceeded"
                ]
              },
              "limit_scope": {
                "enum": [
                  "key",
                  "company"
                ],
                "description": "Чей лимит исчерпан: сначала проверяется ключ, потом компания."
              },
              "limit_kop": {
                "type": "string",
                "description": "Лимит за окно, копейки."
              },
              "spent_kop": {
                "type": "string",
                "description": "Расход окна: списания за окно плюс деньги в резерве незавершённых запросов, копейки."
              },
              "required_reserve_kop": {
                "type": "string",
                "description": "Нужный резерв запроса, копейки."
              },
              "limit_window": {
                "enum": [
                  "none",
                  "day",
                  "month"
                ],
                "description": "Окно: day — с 00:00 МСК, month — с 1-го числа 00:00 МСК, none — без периода."
              },
              "reset_at": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "date-time",
                "description": "Начало следующего окна (UTC); null для окна none."
              }
            }
          }
        }
      },
      "AnthropicRateLimitedError": {
        "type": "object",
        "required": [
          "type",
          "error"
        ],
        "properties": {
          "type": {
            "const": "error"
          },
          "error": {
            "type": "object",
            "required": [
              "type",
              "message",
              "code",
              "limit_scope",
              "rpm_limit"
            ],
            "properties": {
              "type": {
                "const": "rate_limit_error"
              },
              "message": {
                "type": "string"
              },
              "code": {
                "const": "rate_limit_exceeded"
              },
              "limit_scope": {
                "enum": [
                  "key",
                  "company",
                  "trial"
                ],
                "description": "Чей счётчик частоты исчерпан: ключа, компании или пробного кошелька (ключ без компании, порог trial_rate_per_minute)."
              },
              "rpm_limit": {
                "type": "integer",
                "minimum": 1,
                "description": "Запросов в минуту; счётчик общий для всех экземпляров шлюза."
              }
            }
          }
        }
      },
      "AnthropicForbiddenError": {
        "type": "object",
        "required": [
          "type",
          "error"
        ],
        "properties": {
          "type": {
            "const": "error"
          },
          "error": {
            "type": "object",
            "required": [
              "type",
              "message",
              "code",
              "reason"
            ],
            "properties": {
              "type": {
                "const": "permission_error"
              },
              "message": {
                "type": "string"
              },
              "code": {
                "type": "string"
              },
              "reason": {
                "type": "string",
                "enum": [
                  "key_paused",
                  "key_blocked",
                  "company_blocked",
                  "trial_blocked",
                  "model_not_allowed",
                  "model_not_in_trial"
                ]
              },
              "key_id": {
                "type": "string"
              },
              "param": {
                "const": "model",
                "description": "Только у model_not_allowed и model_not_in_trial."
              },
              "policy": {
                "type": "string",
                "enum": [
                  "company",
                  "key"
                ],
                "description": "Только у model_not_allowed: чей список моделей закрыл модель — компании или ключа."
              }
            }
          }
        }
      }
    },
    "responses": {
      "BadRequest": {
        "description": "Неверный запрос.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Unauthorized": {
        "description": "Ключ не передан, неверный или отозван.",
        "headers": {
          "WWW-Authenticate": {
            "schema": {
              "type": "string"
            }
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "InsufficientBalance": {
        "description": "Доступного баланса недостаточно для резерва запроса (insufficient_balance) или резерв выходит за лимит ключа или компании (key_limit_exceeded, company_limit_exceeded).",
        "content": {
          "application/json": {
            "schema": {
              "oneOf": [
                {
                  "$ref": "#/components/schemas/InsufficientBalanceError"
                },
                {
                  "$ref": "#/components/schemas/LimitExceededError"
                }
              ]
            }
          }
        }
      },
      "RateLimited": {
        "description": "Превышен лимит запросов в минуту ключа или компании.",
        "headers": {
          "Retry-After": {
            "description": "Секунды до конца минутного окна счётчика.",
            "schema": {
              "type": "integer",
              "minimum": 1
            }
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/RateLimitedError"
            }
          }
        }
      },
      "Forbidden": {
        "description": "Ключ на паузе или заблокирован, компания заблокирована, модель закрыта политикой компании или ключа (model_not_allowed).",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ForbiddenError"
            }
          }
        }
      },
      "ModelsRateLimited": {
        "description": "Без ключа — анонимный лимит по IP (code rate_limited); с ключом — лимит частоты ключа или компании (code rate_limit_exceeded).",
        "headers": {
          "Retry-After": {
            "description": "Через сколько секунд повторить.",
            "schema": {
              "type": "integer",
              "minimum": 1
            }
          },
          "Access-Control-Expose-Headers": {
            "description": "Только при Origin https://kodzavod.ru: Retry-After — страница цен на другом origin читает его из JS.",
            "schema": {
              "type": "string",
              "enum": [
                "Retry-After"
              ]
            }
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "oneOf": [
                {
                  "$ref": "#/components/schemas/Error"
                },
                {
                  "$ref": "#/components/schemas/RateLimitedError"
                }
              ]
            }
          }
        }
      },
      "ServerError": {
        "description": "Ошибка шлюза или модели.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "ServiceUnavailable": {
        "description": "Шлюз временно не принимает запросы. service_unavailable — шлюз не прочитал ключ, аварийный стоп или счётчик частоты из базы, запрос не принят; повтори через Retry-After; gateway_stopped — включён аварийный стоп; trial_unavailable — у пробного ключа не прочитан счётчик частоты или выключатели пробы, запрос к модели не отправлен; остальные коды — поставщик модели или конфигурация шлюза.",
        "headers": {
          "Retry-After": {
            "schema": {
              "type": "string"
            }
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "AnthropicBadRequest": {
        "description": "Неверный запрос: invalid_request_error (в том числе code unsupported_content_part — блок content, который шлюз не оценивает) или request_too_large.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/AnthropicError"
            }
          }
        }
      },
      "AnthropicUnauthorized": {
        "description": "Ключ не передан, неверный, отозван или в x-api-key и Authorization разные ключи (authentication_error).",
        "headers": {
          "WWW-Authenticate": {
            "schema": {
              "type": "string"
            }
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/AnthropicError"
            }
          }
        }
      },
      "AnthropicBilling": {
        "description": "billing_error: доступного баланса недостаточно для резерва запроса (code insufficient_balance) или резерв выходит за лимит ключа или компании (code key_limit_exceeded, company_limit_exceeded).",
        "content": {
          "application/json": {
            "schema": {
              "oneOf": [
                {
                  "$ref": "#/components/schemas/AnthropicBillingError"
                },
                {
                  "$ref": "#/components/schemas/AnthropicLimitExceededError"
                }
              ]
            }
          }
        }
      },
      "AnthropicRateLimited": {
        "description": "rate_limit_error: превышен лимит запросов в минуту ключа или компании (code rate_limit_exceeded).",
        "headers": {
          "Retry-After": {
            "description": "Секунды до конца минутного окна счётчика.",
            "schema": {
              "type": "integer",
              "minimum": 1
            }
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/AnthropicRateLimitedError"
            }
          }
        }
      },
      "AnthropicForbidden": {
        "description": "Ключ на паузе или заблокирован, компания заблокирована, модель закрыта политикой компании или ключа — model_not_allowed (permission_error).",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/AnthropicForbiddenError"
            }
          }
        }
      },
      "AnthropicServiceUnavailable": {
        "description": "Шлюз временно не принимает запросы (api_error; при перегрузке поставщика — overloaded_error). service_unavailable — шлюз не прочитал ключ, аварийный стоп или счётчик частоты из базы, запрос не принят; повтори через Retry-After; gateway_stopped — включён аварийный стоп; trial_unavailable — у пробного ключа не прочитан счётчик частоты или выключатели пробы, запрос к модели не отправлен; остальные коды — поставщик модели или конфигурация шлюза.",
        "headers": {
          "Retry-After": {
            "schema": {
              "type": "string"
            }
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/AnthropicError"
            }
          }
        }
      },
      "AnthropicServerError": {
        "description": "Ошибка шлюза или модели (api_error, overloaded_error).",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/AnthropicError"
            }
          }
        }
      }
    }
  }
}
