{
  "openapi": "3.1.0",
  "info": {
    "title": "ModelMesh API",
    "version": "1.0.0",
    "description": "OpenAI-совместимое API агрегатора нейросетей ModelMesh. Поменяйте в вашем OpenAI SDK только `base_url` на `https://api.modelmesh.shop/v1` и ключ на `mm_live_...` — и всё заработает (Cursor, LangChain, n8n, OpenAI SDK). Стоимость каждого запроса возвращается в `usage.cost_mt` (Mesh-токены) и в заголовке `X-ModelMesh-Cost`. Картинки, озвучка и распознавание речи — OpenAI-совместимыми `/images/*` и `/audio/*`; видео, музыка и любые долгие генерации — асинхронными задачами `/tasks` с уведомлением на `webhook_url`.",
    "contact": {
      "name": "ModelMesh",
      "url": "https://modelmesh.shop"
    }
  },
  "servers": [
    {
      "url": "https://api.modelmesh.shop/v1",
      "description": "Production"
    }
  ],
  "security": [
    {
      "ApiKeyAuth": []
    }
  ],
  "tags": [
    {
      "name": "Models"
    },
    {
      "name": "Chat"
    },
    {
      "name": "Embeddings"
    },
    {
      "name": "Account"
    },
    {
      "name": "Images"
    },
    {
      "name": "Audio"
    },
    {
      "name": "Tasks"
    }
  ],
  "paths": {
    "/models": {
      "get": {
        "tags": ["Models"],
        "summary": "Список моделей",
        "description": "Модели, доступные вашему ключу, с ценами в Mesh-токенах (MT).",
        "responses": {
          "200": {
            "description": "Список моделей",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ModelList"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        },
        "operationId": "listModels"
      }
    },
    "/chat/completions": {
      "post": {
        "tags": ["Chat"],
        "summary": "Чат (OpenAI chat.completions)",
        "description": "Поддерживает `stream: true` (SSE в формате OpenAI), `tools` (вызов функций) и vision (картинки в `content`). Списание — с кошелька prepaid-ключа или с баланса аккаунта в пределах лимита ключа. Тестовые ключи `mm_test_` возвращают заглушку без списаний. Поддерживается только `n=1`. В стриме цена приходит в последнем чанке `usage` (`cost_mt`), заголовок `X-ModelMesh-Balance` — баланс до запроса.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ChatCompletionRequest"
              },
              "examples": {
                "simple": {
                  "summary": "Простой запрос",
                  "value": {
                    "model": "gpt-5.6-luna",
                    "messages": [
                      {
                        "role": "user",
                        "content": "Привет! Кто ты?"
                      }
                    ]
                  }
                },
                "stream": {
                  "summary": "Стрим",
                  "value": {
                    "model": "gpt-5.6-luna",
                    "stream": true,
                    "messages": [
                      {
                        "role": "user",
                        "content": "Напиши хайку про токены"
                      }
                    ]
                  }
                },
                "vision": {
                  "summary": "Vision",
                  "value": {
                    "model": "gpt-5.6-luna",
                    "messages": [
                      {
                        "role": "user",
                        "content": [
                          {
                            "type": "text",
                            "text": "Что на картинке?"
                          },
                          {
                            "type": "image_url",
                            "image_url": {
                              "url": "https://example.com/cat.png"
                            }
                          }
                        ]
                      }
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Ответ модели. При `stream: true` — `text/event-stream` (чанки `data: {...}` + `data: [DONE]`).",
            "headers": {
              "X-ModelMesh-Cost": {
                "$ref": "#/components/headers/XModelMeshCost"
              },
              "X-ModelMesh-Balance": {
                "$ref": "#/components/headers/XModelMeshBalance"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ChatCompletion"
                }
              },
              "text/event-stream": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/InsufficientBalance"
          },
          "403": {
            "$ref": "#/components/responses/ModelNotAllowed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "404": {
            "$ref": "#/components/responses/ModelNotFound"
          },
          "502": {
            "$ref": "#/components/responses/ProviderError"
          }
        },
        "operationId": "createChatCompletion"
      }
    },
    "/embeddings": {
      "post": {
        "tags": ["Embeddings"],
        "summary": "Эмбеддинги (OpenAI embeddings)",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/EmbeddingsRequest"
              },
              "examples": {
                "simple": {
                  "value": {
                    "model": "text-embedding-3-small",
                    "input": "Текст для поиска"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Эмбеддинги",
            "headers": {
              "X-ModelMesh-Cost": {
                "$ref": "#/components/headers/XModelMeshCost"
              },
              "X-ModelMesh-Balance": {
                "$ref": "#/components/headers/XModelMeshBalance"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EmbeddingList"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/InsufficientBalance"
          },
          "403": {
            "$ref": "#/components/responses/ModelNotAllowed"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "404": {
            "$ref": "#/components/responses/ModelNotFound"
          },
          "502": {
            "$ref": "#/components/responses/ProviderError"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        },
        "operationId": "createEmbedding"
      }
    },
    "/balance": {
      "get": {
        "tags": ["Account"],
        "summary": "Остаток по ключу",
        "description": "Для prepaid-ключа — баланс кошелька ключа; для limit-ключа — минимум из остатка лимита и баланса аккаунта.",
        "responses": {
          "200": {
            "description": "Баланс",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Balance"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        },
        "operationId": "getBalance"
      }
    },
    "/usage": {
      "get": {
        "tags": ["Account"],
        "summary": "Расход по ключу",
        "parameters": [
          {
            "name": "from",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "to",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Расход по дням",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/UsageReport"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          }
        },
        "operationId": "getUsage"
      }
    },
    "/images/generations": {
      "post": {
        "tags": ["Images"],
        "operationId": "createImage",
        "summary": "Генерация изображений (OpenAI images)",
        "description": "Модели категории `image` из `GET /models`. `size` переводится в соотношение сторон и разрешение модели, `n` — число картинок (если модель умеет), прочие параметры модели — в `extra` (ключи `ui_schema` из `GET /models`). Ответ приходит, когда картинка готова; ссылки `url` подписаны и действуют сутки. Списывается цена только за полученные картинки, при ошибке провайдера — ничего (`usage.cost_mt`).",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Повтор запроса с тем же ключом не запускает вторую генерацию и не списывает второй раз.",
            "schema": {
              "type": "string",
              "maxLength": 200
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ImageGenerationRequest"
              },
              "examples": {
                "simple": {
                  "value": {
                    "model": "nano-banana-2",
                    "prompt": "Кот-космонавт, акварель",
                    "size": "1792x1024",
                    "n": 1
                  }
                }
              }
            }
          }
        },
        "responses": {
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/InsufficientBalance"
          },
          "403": {
            "$ref": "#/components/responses/ModelNotAllowed"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "404": {
            "$ref": "#/components/responses/ModelNotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "200": {
            "description": "Картинки",
            "headers": {
              "X-ModelMesh-Cost": {
                "$ref": "#/components/headers/XModelMeshCost"
              },
              "X-ModelMesh-Balance": {
                "$ref": "#/components/headers/XModelMeshBalance"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ImagesResponse"
                }
              }
            }
          },
          "202": {
            "description": "Генерация не успела за 5 минут: `data` пустой, задача — в `modelmesh_task`, результат — `GET /tasks/{id}` (повтор с тем же Idempotency-Key дождётся той же генерации).",
            "headers": {
              "X-ModelMesh-Task-Id": {
                "description": "id задачи",
                "schema": {
                  "type": "string",
                  "format": "uuid"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PendingResult"
                }
              }
            }
          },
          "502": {
            "$ref": "#/components/responses/ProviderError"
          }
        }
      }
    },
    "/images/edits": {
      "post": {
        "tags": ["Images"],
        "operationId": "editImage",
        "summary": "Редактирование изображений (OpenAI images/edits)",
        "description": "`multipart/form-data`: `image` (один или несколько файлов PNG/JPEG/WebP/GIF), `prompt`, `model`, `n`, `size`, `response_format`. Либо JSON с картинками data-URL: `images: [{ \"image_url\": \"data:image/png;base64,...\" }]`. Внешние URL не принимаются, `mask` не поддерживается. Модели — категории `image` с полем картинок в `ui_schema`.",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Повтор запроса с тем же ключом не запускает вторую генерацию и не списывает второй раз.",
            "schema": {
              "type": "string",
              "maxLength": 200
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "$ref": "#/components/schemas/ImageEditForm"
              }
            },
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ImageEditJson"
              }
            }
          }
        },
        "responses": {
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/InsufficientBalance"
          },
          "403": {
            "$ref": "#/components/responses/ModelNotAllowed"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "404": {
            "$ref": "#/components/responses/ModelNotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "200": {
            "description": "Картинки",
            "headers": {
              "X-ModelMesh-Cost": {
                "$ref": "#/components/headers/XModelMeshCost"
              },
              "X-ModelMesh-Balance": {
                "$ref": "#/components/headers/XModelMeshBalance"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ImagesResponse"
                }
              }
            }
          },
          "202": {
            "description": "Генерация не успела за 5 минут: `data` пустой, задача — в `modelmesh_task`, результат — `GET /tasks/{id}` (повтор с тем же Idempotency-Key дождётся той же генерации).",
            "headers": {
              "X-ModelMesh-Task-Id": {
                "description": "id задачи",
                "schema": {
                  "type": "string",
                  "format": "uuid"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PendingResult"
                }
              }
            }
          },
          "502": {
            "$ref": "#/components/responses/ProviderError"
          }
        }
      }
    },
    "/audio/speech": {
      "post": {
        "tags": ["Audio"],
        "operationId": "createSpeech",
        "summary": "Озвучка текста (OpenAI audio/speech)",
        "description": "Ответ — аудиофайл (`audio/*`). `voice` — голос модели из `ui_schema` (стандартные голоса OpenAI заменяются голосом модели по умолчанию). Цена — по числу символов.",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Повтор запроса с тем же ключом не запускает вторую генерацию и не списывает второй раз.",
            "schema": {
              "type": "string",
              "maxLength": 200
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SpeechRequest"
              },
              "examples": {
                "simple": {
                  "value": {
                    "model": "elevenlabs-v4",
                    "input": "Привет! Это ModelMesh.",
                    "voice": "Sarah"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/InsufficientBalance"
          },
          "403": {
            "$ref": "#/components/responses/ModelNotAllowed"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "404": {
            "$ref": "#/components/responses/ModelNotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "200": {
            "description": "Аудио",
            "headers": {
              "X-ModelMesh-Cost": {
                "$ref": "#/components/headers/XModelMeshCost"
              },
              "X-ModelMesh-Balance": {
                "$ref": "#/components/headers/XModelMeshBalance"
              }
            },
            "content": {
              "audio/mpeg": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              },
              "audio/wav": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "202": {
            "description": "Генерация не успела за 5 минут: `data` пустой, задача — в `modelmesh_task`, результат — `GET /tasks/{id}` (повтор с тем же Idempotency-Key дождётся той же генерации).",
            "headers": {
              "X-ModelMesh-Task-Id": {
                "description": "id задачи",
                "schema": {
                  "type": "string",
                  "format": "uuid"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PendingResult"
                }
              }
            }
          },
          "502": {
            "$ref": "#/components/responses/ProviderError"
          }
        }
      }
    },
    "/audio/transcriptions": {
      "post": {
        "tags": ["Audio"],
        "operationId": "createTranscription",
        "summary": "Распознавание речи (OpenAI audio/transcriptions)",
        "description": "`multipart/form-data`: `file` (MP3, WAV, M4A, OGG, MP4, MOV), `model`, `language`, `response_format` (`json`, `text`, `verbose_json`). Цена — по длительности, измеренной по файлу.",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Повтор запроса с тем же ключом не запускает вторую генерацию и не списывает второй раз.",
            "schema": {
              "type": "string",
              "maxLength": 200
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "$ref": "#/components/schemas/TranscriptionForm"
              }
            }
          }
        },
        "responses": {
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/InsufficientBalance"
          },
          "403": {
            "$ref": "#/components/responses/ModelNotAllowed"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "404": {
            "$ref": "#/components/responses/ModelNotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "200": {
            "description": "Текст",
            "headers": {
              "X-ModelMesh-Cost": {
                "$ref": "#/components/headers/XModelMeshCost"
              },
              "X-ModelMesh-Balance": {
                "$ref": "#/components/headers/XModelMeshBalance"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Transcription"
                }
              },
              "text/plain": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "202": {
            "description": "Генерация не успела за 5 минут: `data` пустой, задача — в `modelmesh_task`, результат — `GET /tasks/{id}` (повтор с тем же Idempotency-Key дождётся той же генерации).",
            "headers": {
              "X-ModelMesh-Task-Id": {
                "description": "id задачи",
                "schema": {
                  "type": "string",
                  "format": "uuid"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PendingResult"
                }
              }
            }
          },
          "502": {
            "$ref": "#/components/responses/ProviderError"
          }
        }
      }
    },
    "/tasks": {
      "post": {
        "tags": ["Tasks"],
        "operationId": "createTask",
        "summary": "Асинхронная задача: видео, музыка, любая медиа-модель",
        "description": "Сразу отвечает `202` с задачей (`status: queued`). Токены резервируются на цену при запуске и списываются только за готовый результат; при ошибке и таймауте (2 ч) — возвращаются. О завершении можно узнать опросом `GET /tasks/{id}` или по `webhook_url` (https): ModelMesh отправит POST с событием `task.succeeded`, `task.failed` или `task.canceled` и подписью `X-ModelMesh-Signature: t=<unix>,v1=<hex>` — HMAC-SHA256 от `<t>.<тело>` секретом из `GET /webhooks/secret`. Неуспешная доставка (не 2xx) повторяется с паузой до ~суток; дубликаты отсекайте по заголовку `webhook-id`.",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Повтор запроса с тем же ключом не запускает вторую генерацию и не списывает второй раз.",
            "schema": {
              "type": "string",
              "maxLength": 200
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TaskRequest"
              },
              "examples": {
                "video": {
                  "value": {
                    "model": "kling-3-pro",
                    "input": {
                      "prompt": "Маяк в шторм, камера облетает",
                      "durationSec": 5
                    },
                    "webhook_url": "https://example.com/modelmesh/webhook"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/InsufficientBalance"
          },
          "403": {
            "$ref": "#/components/responses/ModelNotAllowed"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "404": {
            "$ref": "#/components/responses/ModelNotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "202": {
            "description": "Задача создана",
            "headers": {
              "X-ModelMesh-Task-Id": {
                "description": "id задачи",
                "schema": {
                  "type": "string",
                  "format": "uuid"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Task"
                }
              }
            }
          },
          "409": {
            "description": "Idempotency-Key уже использован другим запросом.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/tasks/{id}": {
      "get": {
        "tags": ["Tasks"],
        "operationId": "getTask",
        "summary": "Статус и результат задачи",
        "description": "Видны только задачи и синхронные генерации, созданные этим ключом. Ссылки на файлы подписаны и действуют сутки — запросите задачу снова, чтобы получить свежие.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Задача",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Task"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "description": "Задача не найдена.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/webhooks/secret": {
      "get": {
        "tags": ["Tasks"],
        "operationId": "getWebhookSecret",
        "summary": "Секрет проверки подписи webhook_url",
        "description": "Секрет `whsec_...` для проверки `X-ModelMesh-Signature` у событий задач этого ключа.",
        "responses": {
          "200": {
            "description": "Секрет",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookSecret"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "ApiKeyAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "`Authorization: Bearer mm_live_...`. Тестовые ключи — `mm_test_...` (без списаний)."
      }
    },
    "headers": {
      "XModelMeshCost": {
        "description": "Списано Mesh-токенов (MT) за запрос.",
        "schema": {
          "type": "string"
        }
      },
      "XModelMeshBalance": {
        "description": "Остаток Mesh-токенов (MT) после запроса.",
        "schema": {
          "type": "string"
        }
      }
    },
    "responses": {
      "Unauthorized": {
        "description": "Ключ отсутствует, неверен, приостановлен, отозван или истёк.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "InsufficientBalance": {
        "description": "Недостаточно токенов (баланс аккаунта/ключа или лимит ключа исчерпан).",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "ModelNotAllowed": {
        "description": "Ключу не разрешена эта модель или категория.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "RateLimited": {
        "description": "Превышен rpm-лимит ключа.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "BadRequest": {
        "description": "Некорректный запрос (invalid_request, content_policy_violation, n>1 не поддерживается).",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "ModelNotFound": {
        "description": "Модель не найдена или недоступна (model_not_found).",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "ProviderError": {
        "description": "Провайдер модели временно недоступен (provider_error).",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "properties": {
          "error": {
            "type": "object",
            "properties": {
              "message": {
                "type": "string"
              },
              "type": {
                "type": "string"
              },
              "param": {
                "type": ["string", "null"]
              },
              "code": {
                "type": "string"
              }
            },
            "required": ["message", "type", "code"]
          }
        }
      },
      "ChatMessage": {
        "type": "object",
        "properties": {
          "role": {
            "type": "string",
            "enum": ["system", "developer", "user", "assistant", "tool"]
          },
          "content": {
            "oneOf": [
              {
                "type": "string"
              },
              {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/ContentPart"
                }
              }
            ]
          },
          "name": {
            "type": "string"
          },
          "tool_calls": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ToolCall"
            }
          },
          "tool_call_id": {
            "type": "string"
          }
        },
        "required": ["role"]
      },
      "ContentPart": {
        "oneOf": [
          {
            "type": "object",
            "properties": {
              "type": {
                "const": "text"
              },
              "text": {
                "type": "string"
              }
            },
            "required": ["type", "text"]
          },
          {
            "type": "object",
            "properties": {
              "type": {
                "const": "image_url"
              },
              "image_url": {
                "type": "object",
                "properties": {
                  "url": {
                    "type": "string"
                  },
                  "detail": {
                    "type": "string",
                    "enum": ["auto", "low", "high"]
                  }
                },
                "required": ["url"]
              }
            },
            "required": ["type", "image_url"]
          }
        ]
      },
      "ToolCall": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "type": {
            "const": "function"
          },
          "function": {
            "type": "object",
            "properties": {
              "name": {
                "type": "string"
              },
              "arguments": {
                "type": "string"
              }
            },
            "required": ["name", "arguments"]
          }
        },
        "required": ["id", "type", "function"]
      },
      "Tool": {
        "type": "object",
        "properties": {
          "type": {
            "const": "function"
          },
          "function": {
            "type": "object",
            "properties": {
              "name": {
                "type": "string"
              },
              "description": {
                "type": "string"
              },
              "parameters": {
                "type": "object"
              }
            },
            "required": ["name"]
          }
        },
        "required": ["type", "function"]
      },
      "ChatCompletionRequest": {
        "type": "object",
        "properties": {
          "model": {
            "type": "string"
          },
          "messages": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ChatMessage"
            }
          },
          "stream": {
            "type": "boolean",
            "default": false
          },
          "max_tokens": {
            "type": "integer"
          },
          "max_completion_tokens": {
            "type": "integer"
          },
          "temperature": {
            "type": "number"
          },
          "top_p": {
            "type": "number"
          },
          "n": {
            "type": "integer"
          },
          "stop": {
            "oneOf": [
              {
                "type": "string"
              },
              {
                "type": "array",
                "items": {
                  "type": "string"
                }
              }
            ]
          },
          "seed": {
            "type": "integer"
          },
          "tools": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Tool"
            }
          },
          "tool_choice": {},
          "response_format": {
            "type": "object"
          },
          "user": {
            "type": "string"
          }
        },
        "required": ["model", "messages"]
      },
      "Usage": {
        "type": "object",
        "properties": {
          "prompt_tokens": {
            "type": "integer"
          },
          "completion_tokens": {
            "type": "integer"
          },
          "total_tokens": {
            "type": "integer"
          },
          "cost_mt": {
            "type": "string",
            "description": "Списано Mesh-токенов (MT), десятичная строка."
          },
          "cost_mmt": {
            "type": "string",
            "description": "То же в милли-токенах (целое)."
          }
        }
      },
      "ChatCompletion": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "object": {
            "const": "chat.completion"
          },
          "created": {
            "type": "integer"
          },
          "model": {
            "type": "string"
          },
          "choices": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "index": {
                  "type": "integer"
                },
                "message": {
                  "$ref": "#/components/schemas/ChatMessage"
                },
                "finish_reason": {
                  "type": ["string", "null"]
                }
              }
            }
          },
          "usage": {
            "$ref": "#/components/schemas/Usage"
          }
        }
      },
      "EmbeddingsRequest": {
        "type": "object",
        "properties": {
          "model": {
            "type": "string"
          },
          "input": {
            "oneOf": [
              {
                "type": "string"
              },
              {
                "type": "array",
                "items": {
                  "type": "string"
                }
              }
            ]
          },
          "dimensions": {
            "type": "integer"
          },
          "user": {
            "type": "string"
          }
        },
        "required": ["model", "input"]
      },
      "EmbeddingList": {
        "type": "object",
        "properties": {
          "object": {
            "const": "list"
          },
          "data": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "object": {
                  "const": "embedding"
                },
                "index": {
                  "type": "integer"
                },
                "embedding": {
                  "type": "array",
                  "items": {
                    "type": "number"
                  }
                }
              }
            }
          },
          "model": {
            "type": "string"
          },
          "usage": {
            "$ref": "#/components/schemas/Usage"
          }
        }
      },
      "ModelList": {
        "type": "object",
        "properties": {
          "object": {
            "const": "list"
          },
          "data": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string"
                },
                "object": {
                  "const": "model"
                },
                "owned_by": {
                  "type": "string"
                },
                "category": {
                  "type": "string"
                },
                "context_window": {
                  "type": ["integer", "null"]
                },
                "operation": {
                  "type": "string",
                  "enum": ["image", "video", "music", "speech", "transcription"],
                  "description": "Только медиа-модели: какой метод использовать (`/images/*`, `/audio/*`, `/tasks`)."
                },
                "ui_schema": {
                  "type": "object",
                  "description": "Только медиа-модели: поля параметров (`fields[].key`, тип, варианты, умолчание) — для `extra` и `input` задачи."
                }
              }
            }
          }
        }
      },
      "Balance": {
        "type": "object",
        "properties": {
          "object": {
            "const": "balance"
          },
          "mode": {
            "type": "string",
            "enum": ["limit", "prepaid"]
          },
          "balance_mt": {
            "type": "string"
          },
          "balance_mmt": {
            "type": "string"
          },
          "currency": {
            "const": "MT"
          }
        }
      },
      "UsageReport": {
        "type": "object",
        "properties": {
          "object": {
            "const": "usage"
          },
          "from": {
            "type": "string",
            "format": "date-time"
          },
          "to": {
            "type": "string",
            "format": "date-time"
          },
          "total_cost_mt": {
            "type": "string"
          },
          "total_cost_mmt": {
            "type": "string"
          },
          "total_requests": {
            "type": "integer"
          },
          "days": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "date": {
                  "type": "string",
                  "description": "YYYY-MM-DD (UTC)"
                },
                "cost_mt": {
                  "type": "string"
                },
                "cost_mmt": {
                  "type": "string"
                },
                "requests": {
                  "type": "integer"
                }
              }
            }
          }
        }
      },
      "ImageGenerationRequest": {
        "type": "object",
        "required": ["model", "prompt"],
        "properties": {
          "model": {
            "type": "string"
          },
          "prompt": {
            "type": "string",
            "maxLength": 5000
          },
          "n": {
            "type": "integer",
            "minimum": 1,
            "maximum": 10
          },
          "size": {
            "type": "string",
            "example": "1024x1024",
            "description": "WxH или auto"
          },
          "quality": {
            "type": "string",
            "description": "standard | hd | low | medium | high | auto — если модель поддерживает"
          },
          "style": {
            "type": "string"
          },
          "response_format": {
            "type": "string",
            "enum": ["url", "b64_json"],
            "default": "url"
          },
          "extra": {
            "type": "object",
            "additionalProperties": {
              "oneOf": [
                {
                  "type": "string"
                },
                {
                  "type": "number"
                },
                {
                  "type": "boolean"
                },
                {
                  "type": "array",
                  "items": {
                    "type": "string"
                  }
                }
              ]
            },
            "description": "Параметры модели — ключи `ui_schema` из GET /models."
          }
        }
      },
      "ImageEditForm": {
        "type": "object",
        "required": ["model", "prompt", "image"],
        "properties": {
          "model": {
            "type": "string"
          },
          "prompt": {
            "type": "string"
          },
          "image": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "binary"
            }
          },
          "n": {
            "type": "integer"
          },
          "size": {
            "type": "string"
          },
          "response_format": {
            "type": "string",
            "enum": ["url", "b64_json"]
          },
          "extra": {
            "type": "string",
            "description": "JSON-объект параметров модели"
          }
        }
      },
      "ImageEditJson": {
        "allOf": [
          {
            "$ref": "#/components/schemas/ImageGenerationRequest"
          },
          {
            "type": "object",
            "properties": {
              "images": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "image_url": {
                      "type": "string",
                      "description": "data:image/...;base64,..."
                    }
                  }
                }
              }
            }
          }
        ]
      },
      "ImagesResponse": {
        "type": "object",
        "properties": {
          "created": {
            "type": "integer"
          },
          "data": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "url": {
                  "type": "string"
                },
                "b64_json": {
                  "type": "string"
                }
              }
            }
          },
          "usage": {
            "type": "object",
            "properties": {
              "cost_mt": {
                "type": "string",
                "description": "Списано, MT (десятичная строка)"
              },
              "cost_mmt": {
                "type": "string",
                "description": "Списано, mMT"
              }
            }
          }
        }
      },
      "PendingResult": {
        "type": "object",
        "properties": {
          "created": {
            "type": "integer"
          },
          "data": {
            "type": "array",
            "maxItems": 0,
            "items": {}
          },
          "modelmesh_task": {
            "$ref": "#/components/schemas/Task"
          }
        }
      },
      "SpeechRequest": {
        "type": "object",
        "required": ["model", "input"],
        "properties": {
          "model": {
            "type": "string"
          },
          "input": {
            "type": "string",
            "maxLength": 5000
          },
          "voice": {
            "type": "string"
          },
          "response_format": {
            "type": "string"
          },
          "speed": {
            "type": "number"
          },
          "extra": {
            "type": "object",
            "additionalProperties": {
              "oneOf": [
                {
                  "type": "string"
                },
                {
                  "type": "number"
                },
                {
                  "type": "boolean"
                },
                {
                  "type": "array",
                  "items": {
                    "type": "string"
                  }
                }
              ]
            },
            "description": "Параметры модели — ключи `ui_schema` из GET /models."
          }
        }
      },
      "TranscriptionForm": {
        "type": "object",
        "required": ["model", "file"],
        "properties": {
          "model": {
            "type": "string"
          },
          "file": {
            "type": "string",
            "format": "binary"
          },
          "language": {
            "type": "string"
          },
          "response_format": {
            "type": "string",
            "enum": ["json", "text", "verbose_json"]
          },
          "extra": {
            "type": "string",
            "description": "JSON-объект параметров модели"
          }
        }
      },
      "Transcription": {
        "type": "object",
        "properties": {
          "text": {
            "type": "string"
          },
          "language": {
            "type": ["string", "null"]
          },
          "duration": {
            "type": ["number", "null"]
          },
          "usage": {
            "type": "object",
            "properties": {
              "cost_mt": {
                "type": "string",
                "description": "Списано, MT (десятичная строка)"
              },
              "cost_mmt": {
                "type": "string",
                "description": "Списано, mMT"
              }
            }
          }
        }
      },
      "TaskRequest": {
        "type": "object",
        "required": ["model"],
        "properties": {
          "model": {
            "type": "string"
          },
          "input": {
            "type": "object",
            "description": "`prompt` и параметры модели (ключи `ui_schema` из GET /models). Входные файлы (картинка-референс, аудио) — data-URL.",
            "additionalProperties": true
          },
          "webhook_url": {
            "type": "string",
            "format": "uri",
            "description": "https-адрес для события о завершении"
          }
        }
      },
      "TaskFile": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "kind": {
            "type": "string",
            "enum": ["image", "video", "audio", "document"]
          },
          "mime": {
            "type": "string"
          },
          "url": {
            "type": "string",
            "description": "Подписанная ссылка"
          },
          "url_expires_at": {
            "type": "integer"
          },
          "size_bytes": {
            "type": ["integer", "null"]
          },
          "width": {
            "type": ["integer", "null"]
          },
          "height": {
            "type": ["integer", "null"]
          },
          "duration_ms": {
            "type": ["integer", "null"]
          }
        }
      },
      "Task": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "object": {
            "type": "string",
            "const": "task"
          },
          "model": {
            "type": "string"
          },
          "category": {
            "type": "string"
          },
          "operation": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": ["queued", "in_progress", "succeeded", "failed", "canceled"]
          },
          "progress": {
            "type": ["integer", "null"]
          },
          "created_at": {
            "type": "integer"
          },
          "finished_at": {
            "type": ["integer", "null"]
          },
          "price_mt": {
            "type": "string",
            "description": "Цена при запуске (зарезервировано), MT"
          },
          "price_mmt": {
            "type": "string"
          },
          "cost_mt": {
            "type": "string",
            "description": "Списано, MT (десятичная строка)"
          },
          "cost_mmt": {
            "type": "string",
            "description": "Списано, mMT"
          },
          "output": {
            "type": ["object", "null"],
            "properties": {
              "files": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/TaskFile"
                }
              },
              "text": {
                "type": ["string", "null"]
              }
            }
          },
          "error": {
            "type": ["object", "null"],
            "properties": {
              "code": {
                "type": "string"
              },
              "message": {
                "type": "string"
              }
            }
          },
          "webhook_url": {
            "type": ["string", "null"]
          }
        }
      },
      "WebhookSecret": {
        "type": "object",
        "properties": {
          "object": {
            "type": "string",
            "const": "webhook_secret"
          },
          "secret": {
            "type": "string",
            "example": "whsec_..."
          },
          "signature_header": {
            "type": "string"
          },
          "algorithm": {
            "type": "string"
          }
        }
      }
    }
  }
}
