{
  "openapi": "3.1.0",
  "info": {
    "title": "wapi.kz — WhatsApp API",
    "version": "1.0.0",
    "description": "REST API wapi.kz: отправка и история сообщений с нескольких номеров, вебхуки, лимиты. Подробная документация с примерами — https://wapi.kz/docs. MCP-сервер для ИИ-агентов — https://wapi.kz/docs#mcp. Все ошибки — {\"error\",\"code\"}. Лимиты — https://wapi.kz/docs#limits: не больше 3000 запросов в минуту на ключ (429 key_rate_limited), на 429 — заголовок Retry-After.",
    "contact": {
      "name": "Поддержка wapi.kz",
      "url": "https://t.me/wapi_kz_support_bot"
    },
    "termsOfService": "https://wapi.kz/offer",
    "license": {
      "name": "Проприетарное ПО, использование по оферте",
      "url": "https://wapi.kz/offer"
    }
  },
  "externalDocs": {
    "description": "Документация",
    "url": "https://wapi.kz/docs"
  },
  "servers": [
    {
      "url": "https://wapi.kz/api/v1"
    }
  ],
  "security": [
    {
      "bearer": []
    },
    {
      "apiKey": []
    }
  ],
  "tags": [
    {
      "name": "Сообщения",
      "description": "Отправка, история, чаты, выгрузка, проверка номера"
    },
    {
      "name": "Файлы и хранилище",
      "description": "Файлы сообщений, хранилище, выгрузки архивом"
    },
    {
      "name": "Номера и аккаунт",
      "description": "Номера, сводка, баланс, лимиты и индекс доверия"
    },
    {
      "name": "Сервис",
      "description": "Публичный статус сервиса без ключа"
    }
  ],
  "paths": {
    "/messages/send": {
      "post": {
        "operationId": "sendMessage",
        "tags": [
          "Сообщения"
        ],
        "summary": "Отправить сообщение (текст, фото или файл)",
        "description": "Текст, фото, файл, голосовое или видео. Не чаще 1 сообщения в секунду с номера; первые сообщения новым людям — не чаще раза в 5 секунд и в пределах суточного лимита (GET /limits). Вложений — не больше 2 одновременно на аккаунт. Остаток суточного лимита сообщений — в заголовках X-RateLimit-* (когда номер выбран), на 429 — Retry-After.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SendRequest"
              },
              "example": {
                "to": "77011234567",
                "text": "Ваш заказ №1042 готов к выдаче",
                "from": 1
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Отправлено",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Суточный лимит сообщений номера с учётом индекса доверия",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Сколько сообщений с номера ещё можно отправить сегодня",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Когда лимит обновится: unix-время (секунды) ближайшей полуночи по Алматы (UTC+5)",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SendResponse"
                },
                "example": {
                  "ok": true,
                  "id": 1842,
                  "wa_message_id": "3EB0A1F2C4D5E6F7",
                  "from": {
                    "id": 1,
                    "phone": "77010000000"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Неверное тело, получатель, текст или вложение. code: invalid_json, invalid_to, empty_text, text_too_long, invalid_media, media_too_large, media_fetch_failed, invalid_from, from_required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Нет ключа или ключ неверный. code: unauthorized, invalid_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Номер на паузе — закончился баланс. code: number_paused",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Номера нет в аккаунте. code: number_not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Номер не добавлен или не подключён. code: no_numbers, not_connected",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Лимиты номера, вложений или ключа. code: rate_limited, daily_limit, new_contacts_limit, cold_blocked, spam_detected, attachments_busy, key_rate_limited",
            "headers": {
              "Retry-After": {
                "description": "Через сколько секунд повторить: rate_limited — 1 (или сколько осталось до конца 5-секундной паузы перед первым сообщением новому человеку); daily_limit и new_contacts_limit — до полуночи по Алматы; cold_blocked — до снятия приостановки; spam_detected — 86400; key_rate_limited — 60",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Limit": {
                "description": "Суточный лимит сообщений номера с учётом индекса доверия",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Сколько сообщений с номера ещё можно отправить сегодня",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Когда лимит обновится: unix-время (секунды) ближайшей полуночи по Алматы (UTC+5)",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "WhatsApp не принял сообщение. code: send_failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Номер временно недоступен или сервер принимает много крупных файлов. code: capacity, busy",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/messages": {
      "get": {
        "operationId": "listMessages",
        "tags": [
          "Сообщения"
        ],
        "summary": "История сообщений (новые сверху)",
        "description": "Без from — все номера аккаунта. Страницы: передайте next_before из ответа в before.",
        "parameters": [
          {
            "name": "from",
            "in": "query",
            "required": false,
            "description": "Номер аккаунта: id из GET /numbers или телефон (77010000000). Без from — см. описание метода.",
            "schema": {
              "type": "string"
            },
            "example": "1"
          },
          {
            "name": "chat",
            "in": "query",
            "description": "Собеседник: телефон или chat_id",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "since",
            "in": "query",
            "description": "Не раньше: 2026-10-16, 2026-10-16T09:00:00Z или unix-время",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "until",
            "in": "query",
            "description": "Раньше этого времени, те же форматы",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "direction",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "in",
                "out"
              ]
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 50,
              "minimum": 1,
              "maximum": 500
            }
          },
          {
            "name": "before",
            "in": "query",
            "description": "Сообщения с id меньше этого (next_before прошлого ответа)",
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Сообщения",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "messages": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Message"
                      }
                    },
                    "next_before": {
                      "type": [
                        "integer",
                        "null"
                      ]
                    }
                  }
                },
                "example": {
                  "messages": [
                    {
                      "id": 1842,
                      "from": 1,
                      "direction": "out",
                      "source": "api",
                      "chat_id": "77011234567@c.us",
                      "peer": "77011234567",
                      "peer_lid": null,
                      "name": "Айгерим",
                      "is_group": false,
                      "group_subject": null,
                      "author": null,
                      "wa_message_id": "3EB0A1F2C4D5E6F7",
                      "type": "text",
                      "text": "Ваш заказ №1042 готов к выдаче",
                      "status": "read",
                      "error": null,
                      "created_at": "2026-10-16T09:04:11.482Z",
                      "updated_at": "2026-10-16T09:05:02.117Z",
                      "media": null,
                      "media_skipped": false,
                      "media_skip_reason": null,
                      "media_pending": false
                    }
                  ],
                  "next_before": null
                }
              }
            }
          },
          "400": {
            "description": "Неверный параметр. code: invalid_param, invalid_from",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Нет ключа или ключ неверный. code: unauthorized, invalid_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Номера нет в аккаунте. code: number_not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Слишком много запросов: больше 3000 в минуту на ключ (key_rate_limited) или 30+ запросов с неверным ключом в минуту с одного IP (rate_limited). code: key_rate_limited, rate_limited",
            "headers": {
              "Retry-After": {
                "description": "Через сколько секунд повторить: key_rate_limited — 60; rate_limited (неверные ключи с IP) — 60",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/messages/{id}": {
      "get": {
        "operationId": "getMessage",
        "tags": [
          "Сообщения"
        ],
        "summary": "Одно сообщение по id (статус доставки)",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Сообщение",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Message"
                },
                "example": {
                  "id": 1842,
                  "from": 1,
                  "direction": "out",
                  "source": "api",
                  "chat_id": "77011234567@c.us",
                  "peer": "77011234567",
                  "peer_lid": null,
                  "name": "Айгерим",
                  "is_group": false,
                  "group_subject": null,
                  "author": null,
                  "wa_message_id": "3EB0A1F2C4D5E6F7",
                  "type": "text",
                  "text": "Ваш заказ №1042 готов к выдаче",
                  "status": "read",
                  "error": null,
                  "created_at": "2026-10-16T09:04:11.482Z",
                  "updated_at": "2026-10-16T09:05:02.117Z",
                  "media": null,
                  "media_skipped": false,
                  "media_skip_reason": null,
                  "media_pending": false
                }
              }
            }
          },
          "401": {
            "description": "Нет ключа или ключ неверный. code: unauthorized, invalid_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Нет в аккаунте или старше срока хранения. code: not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Слишком много запросов: больше 3000 в минуту на ключ (key_rate_limited) или 30+ запросов с неверным ключом в минуту с одного IP (rate_limited). code: key_rate_limited, rate_limited",
            "headers": {
              "Retry-After": {
                "description": "Через сколько секунд повторить: key_rate_limited — 60; rate_limited (неверные ключи с IP) — 60",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/chats": {
      "get": {
        "operationId": "listChats",
        "tags": [
          "Сообщения"
        ],
        "summary": "Чаты: собеседник, последнее сообщение, счётчики",
        "parameters": [
          {
            "name": "from",
            "in": "query",
            "required": false,
            "description": "Номер аккаунта: id из GET /numbers или телефон (77010000000). Без from — см. описание метода.",
            "schema": {
              "type": "string"
            },
            "example": "1"
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 100,
              "maximum": 500
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Чаты",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "chats": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Chat"
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Неверный from. code: invalid_from",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Нет ключа или ключ неверный. code: unauthorized, invalid_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Номера нет в аккаунте. code: number_not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Слишком много запросов: больше 3000 в минуту на ключ (key_rate_limited) или 30+ запросов с неверным ключом в минуту с одного IP (rate_limited). code: key_rate_limited, rate_limited",
            "headers": {
              "Retry-After": {
                "description": "Через сколько секунд повторить: key_rate_limited — 60; rate_limited (неверные ключи с IP) — 60",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/export": {
      "get": {
        "operationId": "exportMessages",
        "tags": [
          "Сообщения"
        ],
        "summary": "Выгрузка истории файлом (CSV или JSON)",
        "parameters": [
          {
            "name": "from",
            "in": "query",
            "required": false,
            "description": "Номер аккаунта: id из GET /numbers или телефон (77010000000). Без from — см. описание метода.",
            "schema": {
              "type": "string"
            },
            "example": "1"
          },
          {
            "name": "since",
            "in": "query",
            "description": "Не раньше: 2026-10-16, 2026-10-16T09:00:00Z или unix-время",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "until",
            "in": "query",
            "description": "Раньше этого времени, те же форматы",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "format",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "csv",
                "json"
              ],
              "default": "csv"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Файл (старые сверху, до 100 000 сообщений). CSV — UTF-8, колонки: id, created_at, number_id, number_phone, direction, source, peer_phone, chat_id, type, text, status, wa_message_id, error; текст, начинающийся с = + - @, экранируется апострофом",
            "content": {
              "text/csv": {
                "schema": {
                  "type": "string"
                }
              },
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "messages": {
                      "type": "array",
                      "items": {
                        "allOf": [
                          {
                            "$ref": "#/components/schemas/Message"
                          },
                          {
                            "type": "object",
                            "properties": {
                              "from_phone": {
                                "type": [
                                  "string",
                                  "null"
                                ],
                                "description": "Телефон вашего номера"
                              }
                            }
                          }
                        ]
                      }
                    },
                    "count": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Неверный параметр или from. code: invalid_param, invalid_from",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Нет ключа или ключ неверный. code: unauthorized, invalid_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Номера нет в аккаунте. code: number_not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Больше 10 выгрузок за 10 минут на аккаунт (rate_limited), больше 3000 запросов в минуту на ключ (key_rate_limited) или 30+ запросов с неверным ключом в минуту с IP (rate_limited). code: rate_limited, key_rate_limited",
            "headers": {
              "Retry-After": {
                "description": "Через сколько секунд повторить: key_rate_limited — 60; rate_limited (неверные ключи с IP) — 60",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/media/{id}": {
      "get": {
        "operationId": "getMedia",
        "tags": [
          "Файлы и хранилище"
        ],
        "summary": "Файл сообщения (переадресация на ссылку)",
        "description": "302 на подписанную ссылку, живёт 15 минут. Картинки, видео, аудио и PDF открываются в браузере, остальное — только скачиванием. Ключ номера видит только файлы своего номера.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "download",
            "in": "query",
            "description": "1 — скачать файлом с исходным именем",
            "schema": {
              "type": "string",
              "enum": [
                "1"
              ]
            }
          }
        ],
        "responses": {
          "302": {
            "description": "Переадресация на файл",
            "headers": {
              "Location": {
                "description": "Подписанная ссылка на файл (15 минут)",
                "schema": {
                  "type": "string",
                  "format": "uri"
                }
              }
            }
          },
          "401": {
            "description": "Нет ключа или ключ неверный. code: unauthorized, invalid_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Файла нет: id неверный, файл удалён или принадлежит другому номеру. code: not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Слишком много запросов: больше 3000 в минуту на ключ (key_rate_limited) или 30+ запросов с неверным ключом в минуту с одного IP (rate_limited). code: key_rate_limited, rate_limited",
            "headers": {
              "Retry-After": {
                "description": "Через сколько секунд повторить: key_rate_limited — 60; rate_limited (неверные ключи с IP) — 60",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Хранилище временно недоступно. code: storage_unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "delete": {
        "operationId": "deleteMedia",
        "tags": [
          "Файлы и хранилище"
        ],
        "summary": "Удалить файл",
        "description": "Из хранилища и из счёта — сразу, резервная копия — через 30 дней. Текст сообщения остаётся.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Удалено",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DeleteResult"
                },
                "example": {
                  "ok": true,
                  "deleted": 1,
                  "freed_bytes": 184320
                }
              }
            }
          },
          "401": {
            "description": "Нет ключа или ключ неверный. code: unauthorized, invalid_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Файла нет: id неверный, файл удалён или принадлежит другому номеру. code: not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Слишком много запросов: больше 3000 в минуту на ключ (key_rate_limited) или 30+ запросов с неверным ключом в минуту с одного IP (rate_limited). code: key_rate_limited, rate_limited",
            "headers": {
              "Retry-After": {
                "description": "Через сколько секунд повторить: key_rate_limited — 60; rate_limited (неверные ключи с IP) — 60",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/media/delete": {
      "post": {
        "operationId": "deleteMediaBulk",
        "tags": [
          "Файлы и хранилище"
        ],
        "summary": "Удалить файлы пачкой",
        "description": "Файлы сообщений раньше until (обязательно — защита от «удалить всё» по ошибке), с фильтрами. Текст сообщений остаётся. Ключ номера — только свой номер.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "until"
                ],
                "properties": {
                  "until": {
                    "type": "string",
                    "description": "Удалить файлы сообщений раньше этого момента: дата (2026-09-01), время ISO или unix-время"
                  },
                  "since": {
                    "type": "string",
                    "description": "Не раньше этого момента, те же форматы"
                  },
                  "from": {
                    "type": [
                      "integer",
                      "string"
                    ],
                    "description": "Только этот номер: id или телефон"
                  },
                  "chat": {
                    "type": "string",
                    "description": "Только этот чат: телефон или chat_id"
                  },
                  "kind": {
                    "type": "string",
                    "enum": [
                      "photo",
                      "video",
                      "voice",
                      "document",
                      "sticker"
                    ]
                  },
                  "min_size": {
                    "type": "integer",
                    "minimum": 0,
                    "description": "Только файлы не меньше стольких байт"
                  }
                }
              },
              "example": {
                "until": "2026-09-01",
                "from": 1,
                "chat": "77011234567",
                "kind": "video",
                "min_size": 10485760
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Удалено",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DeleteResult"
                },
                "example": {
                  "ok": true,
                  "deleted": 37,
                  "freed_bytes": 912680960
                }
              }
            }
          },
          "400": {
            "description": "Неверное тело, until, since, kind, min_size или from. code: invalid_json, invalid_param, invalid_from",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Нет ключа или ключ неверный. code: unauthorized, invalid_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Номера нет в аккаунте. code: number_not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Слишком много запросов: больше 3000 в минуту на ключ (key_rate_limited) или 30+ запросов с неверным ключом в минуту с одного IP (rate_limited). code: key_rate_limited, rate_limited",
            "headers": {
              "Retry-After": {
                "description": "Через сколько секунд повторить: key_rate_limited — 60; rate_limited (неверные ключи с IP) — 60",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/storage": {
      "get": {
        "operationId": "getStorage",
        "tags": [
          "Файлы и хранилище"
        ],
        "summary": "Хранилище: объём, пакеты, списания",
        "description": "5 ГБ на аккаунт бесплатно (сумма по всем номерам), сверх — пакеты в месяц, списываются с баланса по дням. Только ключу аккаунта.",
        "responses": {
          "200": {
            "description": "Хранилище",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Storage"
                },
                "example": {
                  "used_bytes": 7516192768,
                  "included_bytes": 5368709120,
                  "over_bytes": 2147483648,
                  "files": 18240,
                  "packs": [
                    {
                      "gb": 5,
                      "kzt": 1000,
                      "count": 1
                    }
                  ],
                  "monthly_charge": 1000,
                  "daily_charge": 33.33,
                  "ceiling_bytes": null,
                  "retention_days": null,
                  "pending_files": 0,
                  "policy": {
                    "skip_groups": false,
                    "skip_unknown": false,
                    "unknown_max_mb": null,
                    "chat_daily_mb": null
                  },
                  "state": "ok",
                  "delete_at": null,
                  "by_number": [
                    {
                      "number_id": 1,
                      "bytes": 7516192768,
                      "files": 18240
                    }
                  ],
                  "by_kind": [
                    {
                      "kind": "photo",
                      "bytes": 2147483648,
                      "files": 15000
                    },
                    {
                      "kind": "video",
                      "bytes": 5368709120,
                      "files": 240
                    },
                    {
                      "kind": "voice",
                      "bytes": 0,
                      "files": 0
                    },
                    {
                      "kind": "document",
                      "bytes": 0,
                      "files": 0
                    },
                    {
                      "kind": "sticker",
                      "bytes": 0,
                      "files": 0
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Нет ключа или ключ неверный. code: unauthorized, invalid_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Ключ одного номера. code: forbidden_for_number_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Слишком много запросов: больше 3000 в минуту на ключ (key_rate_limited) или 30+ запросов с неверным ключом в минуту с одного IP (rate_limited). code: key_rate_limited, rate_limited",
            "headers": {
              "Retry-After": {
                "description": "Через сколько секунд повторить: key_rate_limited — 60; rate_limited (неверные ключи с IP) — 60",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/exports": {
      "get": {
        "operationId": "listExports",
        "tags": [
          "Файлы и хранилище"
        ],
        "summary": "Выгрузки архивом: последние 50",
        "description": "Новые сверху. Ключ номера — только выгрузки своего номера.",
        "responses": {
          "200": {
            "description": "Выгрузки",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "exports": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Export"
                      }
                    }
                  }
                },
                "example": {
                  "exports": [
                    {
                      "id": 17,
                      "status": "done",
                      "from": 1,
                      "chat": "77011234567",
                      "since": "2026-10-01T00:00:00.000Z",
                      "until": "2026-11-01T00:00:00.000Z",
                      "include_media": true,
                      "created_at": "2026-10-16T09:12:00.000Z",
                      "finished_at": "2026-10-16T09:15:00.000Z",
                      "expires_at": "2026-10-17T09:15:00.000Z",
                      "size": 734003200,
                      "files": 1840,
                      "messages": 12650,
                      "missing_files": 0,
                      "url": "https://storage.yandexcloud.kz/wapi-media/exports/u1/17-….zip?X-Amz-Signature=…",
                      "error": null,
                      "progress": 100
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Нет ключа или ключ неверный. code: unauthorized, invalid_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Слишком много запросов: больше 3000 в минуту на ключ (key_rate_limited) или 30+ запросов с неверным ключом в минуту с одного IP (rate_limited). code: key_rate_limited, rate_limited",
            "headers": {
              "Retry-After": {
                "description": "Через сколько секунд повторить: key_rate_limited — 60; rate_limited (неверные ключи с IP) — 60",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "createExport",
        "tags": [
          "Файлы и хранилище"
        ],
        "summary": "Собрать архив ZIP: файлы и переписка",
        "description": "Собирается в фоне, обычно минуты. Готово — событие export.ready на вебхук (без ссылки), ссылка — в GET /exports/{id} (15 минут при каждом запросе, архив хранится 24 часа). Не больше 2 выгрузок одновременно и 10 в сутки на аккаунт (сутки — с полуночи по Алматы). Все поля необязательные; тело можно не передавать.",
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "from": {
                    "type": [
                      "integer",
                      "string"
                    ],
                    "description": "Только этот номер: id или телефон. Без from — все номера (у ключа номера — его номер)"
                  },
                  "chat": {
                    "type": "string",
                    "description": "Только этот чат: телефон или chat_id"
                  },
                  "since": {
                    "type": "string",
                    "description": "Не раньше: дата, время ISO или unix-время"
                  },
                  "until": {
                    "type": "string",
                    "description": "Раньше этого момента"
                  },
                  "include_media": {
                    "type": "boolean",
                    "default": true,
                    "description": "false — только переписка, без файлов"
                  }
                }
              },
              "example": {
                "from": 1,
                "chat": "77011234567",
                "since": "2026-10-01",
                "until": "2026-11-01",
                "include_media": true
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Принято в работу",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Export"
                },
                "example": {
                  "id": 17,
                  "status": "queued",
                  "from": 1,
                  "chat": "77011234567",
                  "since": "2026-10-01T00:00:00.000Z",
                  "until": "2026-11-01T00:00:00.000Z",
                  "include_media": true,
                  "created_at": "2026-10-16T09:12:00.000Z",
                  "finished_at": null,
                  "expires_at": null,
                  "size": null,
                  "files": null,
                  "messages": null,
                  "missing_files": null,
                  "url": null,
                  "error": null,
                  "progress": null
                }
              }
            }
          },
          "400": {
            "description": "Неверный since, until или from. code: invalid_param, invalid_from",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Нет ключа или ключ неверный. code: unauthorized, invalid_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Номера нет в аккаунте. code: number_not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Уже собираются 2 выгрузки, исчерпан лимит 10 в сутки или лимит ключа. code: export_busy, export_limit, key_rate_limited",
            "headers": {
              "Retry-After": {
                "description": "Через сколько секунд повторить: key_rate_limited — 60",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Хранилище не подключено. code: storage_unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/exports/{id}": {
      "get": {
        "operationId": "getExport",
        "tags": [
          "Файлы и хранилище"
        ],
        "summary": "Выгрузка: статус, прогресс, ссылка на архив",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Выгрузка",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Export"
                },
                "example": {
                  "id": 17,
                  "status": "done",
                  "from": 1,
                  "chat": "77011234567",
                  "since": "2026-10-01T00:00:00.000Z",
                  "until": "2026-11-01T00:00:00.000Z",
                  "include_media": true,
                  "created_at": "2026-10-16T09:12:00.000Z",
                  "finished_at": "2026-10-16T09:15:00.000Z",
                  "expires_at": "2026-10-17T09:15:00.000Z",
                  "size": 734003200,
                  "files": 1840,
                  "messages": 12650,
                  "missing_files": 0,
                  "url": "https://storage.yandexcloud.kz/wapi-media/exports/u1/17-….zip?X-Amz-Signature=…",
                  "error": null,
                  "progress": 100
                }
              }
            }
          },
          "401": {
            "description": "Нет ключа или ключ неверный. code: unauthorized, invalid_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Выгрузки нет в аккаунте (или у ключа номера к ней нет доступа). code: not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Слишком много запросов: больше 3000 в минуту на ключ (key_rate_limited) или 30+ запросов с неверным ключом в минуту с одного IP (rate_limited). code: key_rate_limited, rate_limited",
            "headers": {
              "Retry-After": {
                "description": "Через сколько секунд повторить: key_rate_limited — 60; rate_limited (неверные ключи с IP) — 60",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/numbers": {
      "get": {
        "operationId": "listNumbers",
        "tags": [
          "Номера и аккаунт"
        ],
        "summary": "Номера аккаунта",
        "responses": {
          "200": {
            "description": "Номера",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "numbers": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Number"
                      }
                    },
                    "limit": {
                      "type": "integer",
                      "description": "Сколько номеров можно держать"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Нет ключа или ключ неверный. code: unauthorized, invalid_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Слишком много запросов: больше 3000 в минуту на ключ (key_rate_limited) или 30+ запросов с неверным ключом в минуту с одного IP (rate_limited). code: key_rate_limited, rate_limited",
            "headers": {
              "Retry-After": {
                "description": "Через сколько секунд повторить: key_rate_limited — 60; rate_limited (неверные ключи с IP) — 60",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/status": {
      "get": {
        "operationId": "getStatus",
        "tags": [
          "Номера и аккаунт"
        ],
        "summary": "Сводка: номера и баланс одним запросом",
        "description": "С ключом номера — только этот номер, без баланса. Общий статус сервиса без ключа — GET https://wapi.kz/api/status.",
        "responses": {
          "200": {
            "description": "Сводка",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Status"
                }
              }
            }
          },
          "401": {
            "description": "Нет ключа или ключ неверный. code: unauthorized, invalid_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Слишком много запросов: больше 3000 в минуту на ключ (key_rate_limited) или 30+ запросов с неверным ключом в минуту с одного IP (rate_limited). code: key_rate_limited, rate_limited",
            "headers": {
              "Retry-After": {
                "description": "Через сколько секунд повторить: key_rate_limited — 60; rate_limited (неверные ключи с IP) — 60",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/balance": {
      "get": {
        "operationId": "getBalance",
        "tags": [
          "Номера и аккаунт"
        ],
        "summary": "Баланс и на сколько дней хватит",
        "responses": {
          "200": {
            "description": "Баланс",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Balance"
                },
                "example": {
                  "balance": 19470,
                  "daily_charge": 660,
                  "days_left": 29,
                  "active_numbers": 2,
                  "paused_numbers": 0
                }
              }
            }
          },
          "401": {
            "description": "Нет ключа или ключ неверный. code: unauthorized, invalid_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Ключ одного номера. code: forbidden_for_number_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Слишком много запросов: больше 3000 в минуту на ключ (key_rate_limited) или 30+ запросов с неверным ключом в минуту с одного IP (rate_limited). code: key_rate_limited, rate_limited",
            "headers": {
              "Retry-After": {
                "description": "Через сколько секунд повторить: key_rate_limited — 60; rate_limited (неверные ключи с IP) — 60",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/me": {
      "get": {
        "operationId": "getMe",
        "tags": [
          "Номера и аккаунт"
        ],
        "summary": "Данные аккаунта",
        "responses": {
          "200": {
            "description": "Аккаунт",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Me"
                }
              }
            }
          },
          "401": {
            "description": "Нет ключа или ключ неверный. code: unauthorized, invalid_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Ключ одного номера. code: forbidden_for_number_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Слишком много запросов: больше 3000 в минуту на ключ (key_rate_limited) или 30+ запросов с неверным ключом в минуту с одного IP (rate_limited). code: key_rate_limited, rate_limited",
            "headers": {
              "Retry-After": {
                "description": "Через сколько секунд повторить: key_rate_limited — 60; rate_limited (неверные ключи с IP) — 60",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/limits": {
      "get": {
        "operationId": "getLimits",
        "tags": [
          "Номера и аккаунт"
        ],
        "summary": "Лимиты номера на сегодня и индекс доверия",
        "parameters": [
          {
            "name": "from",
            "in": "query",
            "required": false,
            "description": "Номер аккаунта: id из GET /numbers или телефон (77010000000). Без from — см. описание метода.",
            "schema": {
              "type": "string"
            },
            "example": "1"
          }
        ],
        "responses": {
          "200": {
            "description": "Лимиты",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Limits"
                }
              }
            }
          },
          "400": {
            "description": "Неверный from или у аккаунта несколько номеров. code: invalid_from, from_required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Нет ключа или ключ неверный. code: unauthorized, invalid_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Номера нет в аккаунте. code: number_not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "К аккаунту не добавлен номер. code: no_numbers",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Слишком много запросов: больше 3000 в минуту на ключ (key_rate_limited) или 30+ запросов с неверным ключом в минуту с одного IP (rate_limited). code: key_rate_limited, rate_limited",
            "headers": {
              "Retry-After": {
                "description": "Через сколько секунд повторить: key_rate_limited — 60; rate_limited (неверные ключи с IP) — 60",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/contacts/check": {
      "post": {
        "operationId": "checkNumber",
        "tags": [
          "Сообщения"
        ],
        "summary": "Есть ли номер в WhatsApp",
        "description": "До 200 проверок в сутки на номер, не чаще 1 в секунду. Остаток проверок на сегодня — в заголовках X-RateLimit-* (когда номер выбран), на 429 и 503 — Retry-After.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "phone"
                ],
                "properties": {
                  "phone": {
                    "type": "string",
                    "examples": [
                      "77011234567"
                    ]
                  },
                  "from": {
                    "type": [
                      "integer",
                      "string"
                    ]
                  }
                }
              },
              "example": {
                "phone": "77011234567",
                "from": 1
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Результат",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Суточный лимит проверок номера (200)",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Сколько проверок с номера ещё можно сделать сегодня",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Когда лимит обновится: unix-время (секунды) ближайшей полуночи по Алматы (UTC+5)",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CheckResponse"
                },
                "example": {
                  "phone": "77011234567",
                  "exists": true,
                  "chat_id": "77011234567@c.us",
                  "from": {
                    "id": 1,
                    "phone": "77010000000"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Неверный номер. code: invalid_phone, invalid_from, from_required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Нет ключа или ключ неверный. code: unauthorized, invalid_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Номер на паузе — закончился баланс. code: number_paused",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Номера нет в аккаунте. code: number_not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Номер не добавлен или не подключён. code: no_numbers, not_connected",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Лимит проверок или ключа. code: rate_limited, checks_limit, key_rate_limited",
            "headers": {
              "Retry-After": {
                "description": "Через сколько секунд повторить: rate_limited — 1; checks_limit — до полуночи по Алматы; key_rate_limited — 60",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Limit": {
                "description": "Суточный лимит проверок номера (200)",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Сколько проверок с номера ещё можно сделать сегодня",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Когда лимит обновится: unix-время (секунды) ближайшей полуночи по Алматы (UTC+5)",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "Проверка не прошла. code: check_failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Номер временно недоступен. code: capacity",
            "headers": {
              "Retry-After": {
                "description": "Через сколько секунд повторить: 120",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/status": {
      "servers": [
        {
          "url": "https://wapi.kz"
        }
      ],
      "get": {
        "operationId": "serviceStatus",
        "tags": [
          "Сервис"
        ],
        "summary": "Статус сервиса (без ключа)",
        "security": [],
        "description": "Общее состояние частей сервиса — для мониторинга. 200 — работает (ok или degraded), 503 — не работает. Страница для людей — https://wapi.kz/status.",
        "responses": {
          "200": {
            "description": "Работает",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ServiceStatus"
                }
              }
            }
          },
          "503": {
            "description": "Не работает",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ServiceStatus"
                }
              }
            }
          }
        }
      }
    }
  },
  "webhooks": {
    "whatsappEvent": {
      "post": {
        "operationId": "whatsappEvent",
        "summary": "Событие номера на ваш вебхук",
        "description": "Адрес задаётся в кабинете (для всех номеров — «API и MCP», для одного — карточка номера). Только https и публичный адрес; редиректы не выполняем. Подписи нет — добавьте секрет в адрес (?token=…) и проверяйте его.",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WebhookEvent"
              },
              "example": {
                "event": "message",
                "timestamp": 1792154662123,
                "session": "default",
                "payload": {
                  "id": "false_77011234567@c.us_3EB0B7C1D2E3F4A5",
                  "from": "77011234567@c.us",
                  "to": "77010000000@c.us",
                  "fromMe": false,
                  "body": "Здравствуйте, заказ ещё в силе?",
                  "hasMedia": false
                },
                "me": {
                  "id": "77010000000@c.us",
                  "pushName": "Магазин"
                },
                "wapi": {
                  "number_id": 1,
                  "phone": "77010000000",
                  "message_id": 1843
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Принято"
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearer": {
        "type": "http",
        "scheme": "bearer",
        "description": "API-ключ из кабинета (раздел «API и MCP») или ключ одного номера: Authorization: Bearer wa_…"
      },
      "apiKey": {
        "type": "apiKey",
        "in": "header",
        "name": "X-Api-Key",
        "description": "Тот же ключ другим заголовком"
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "required": [
          "error",
          "code"
        ],
        "properties": {
          "error": {
            "type": "string",
            "description": "Понятный текст на русском: что случилось и что сделать"
          },
          "code": {
            "type": "string",
            "description": "Машинный код — проверяйте в программе его",
            "examples": [
              "number_paused"
            ]
          },
          "id": {
            "type": "integer",
            "description": "id сообщения в истории, если оно успело туда попасть (только у ошибок отправки)"
          }
        }
      },
      "Number": {
        "type": "object",
        "required": [
          "id",
          "status",
          "wa_status",
          "connected",
          "created_at"
        ],
        "properties": {
          "id": {
            "type": "integer",
            "description": "id номера — для параметра from"
          },
          "label": {
            "type": [
              "string",
              "null"
            ],
            "description": "Название номера из кабинета"
          },
          "phone": {
            "type": [
              "string",
              "null"
            ],
            "description": "Телефон номера; null, пока номер не подключён",
            "examples": [
              "77010000000"
            ]
          },
          "status": {
            "type": "string",
            "enum": [
              "active",
              "paused"
            ],
            "description": "paused — закончился баланс, включится сам после пополнения"
          },
          "wa_status": {
            "type": "string",
            "description": "WORKING — подключён; SCAN_QR_CODE, STARTING — идёт подключение; FAILED, STOPPED — переподключите; none — не запускался",
            "examples": [
              "WORKING"
            ]
          },
          "connected": {
            "type": "boolean",
            "description": "true — с номера можно отправлять прямо сейчас"
          },
          "connected_at": {
            "type": [
              "string",
              "null"
            ],
            "description": "Когда подключили к WhatsApp (UTC, формат YYYY-MM-DD HH:MM:SS)"
          },
          "rating": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Индекс доверия 0–100"
          },
          "cold_blocked_until": {
            "type": [
              "string",
              "null"
            ],
            "description": "До какого времени приостановлены первые сообщения новым людям"
          },
          "created_at": {
            "type": "string",
            "description": "Когда номер добавлен (UTC, формат YYYY-MM-DD HH:MM:SS)"
          }
        }
      },
      "Message": {
        "type": "object",
        "required": [
          "id",
          "from",
          "direction",
          "source",
          "type",
          "status",
          "created_at"
        ],
        "properties": {
          "id": {
            "type": "integer",
            "description": "id сообщения в wapi.kz"
          },
          "from": {
            "type": "integer",
            "description": "id вашего номера"
          },
          "direction": {
            "type": "string",
            "enum": [
              "in",
              "out"
            ]
          },
          "source": {
            "type": "string",
            "enum": [
              "api",
              "mcp",
              "test",
              "phone",
              "contact",
              "system"
            ],
            "description": "Кто написал: api/mcp — ваша программа или агент, phone — человек с телефона, contact — собеседник"
          },
          "chat_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "Чат WhatsApp: …@c.us — личный, …@lid — личный со скрытым номером, …@g.us — группа"
          },
          "peer": {
            "type": [
              "string",
              "null"
            ],
            "description": "Телефон собеседника; для групп и скрытых номеров с неизвестным телефоном — null"
          },
          "peer_lid": {
            "type": [
              "string",
              "null"
            ],
            "description": "Скрытый идентификатор собеседника (…@lid), если есть"
          },
          "name": {
            "type": [
              "string",
              "null"
            ],
            "description": "Имя собеседника из WhatsApp или название группы"
          },
          "is_group": {
            "type": "boolean"
          },
          "group_subject": {
            "type": [
              "string",
              "null"
            ],
            "description": "Название группы"
          },
          "author": {
            "type": [
              "object",
              "null"
            ],
            "description": "В группе, у входящих: кто написал",
            "properties": {
              "name": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "phone": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "lid": {
                "type": [
                  "string",
                  "null"
                ]
              }
            }
          },
          "wa_message_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "id сообщения в WhatsApp"
          },
          "type": {
            "type": "string",
            "description": "text, image, video, audio, voice, document, sticker, location, contact, media, other"
          },
          "text": {
            "type": [
              "string",
              "null"
            ],
            "description": "Текст или подпись к файлу; null, если хранение текстов выключено или текста нет"
          },
          "status": {
            "type": "string",
            "enum": [
              "queued",
              "sent",
              "delivered",
              "read",
              "failed"
            ]
          },
          "error": {
            "type": [
              "string",
              "null"
            ],
            "description": "Причина, если status = failed"
          },
          "created_at": {
            "type": "string",
            "description": "Время сообщения (UTC, ISO)"
          },
          "updated_at": {
            "type": [
              "string",
              "null"
            ],
            "description": "Когда последний раз менялся статус"
          },
          "media": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/Media"
              },
              {
                "type": "null"
              }
            ],
            "description": "Сохранённый файл сообщения; null — файла нет"
          },
          "media_skipped": {
            "type": "boolean",
            "description": "Файл был, но не сохранён — причина в media_skip_reason"
          },
          "media_skip_reason": {
            "type": [
              "string",
              "null"
            ],
            "description": "history_off, ceiling, no_balance, failed, group_off, unknown_off, unknown_large, chat_daily"
          },
          "media_pending": {
            "type": "boolean",
            "description": "Файл ещё сохраняется (обычно секунды)"
          }
        }
      },
      "Media": {
        "type": "object",
        "required": [
          "id",
          "size"
        ],
        "properties": {
          "id": {
            "type": "integer",
            "description": "id файла — для GET /media/{id} и DELETE /media/{id}"
          },
          "kind": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "photo",
              "video",
              "voice",
              "document",
              "sticker",
              null
            ]
          },
          "mimetype": {
            "type": [
              "string",
              "null"
            ]
          },
          "filename": {
            "type": [
              "string",
              "null"
            ],
            "description": "Исходное имя файла, если было"
          },
          "size": {
            "type": "integer",
            "description": "Размер, байт"
          },
          "url": {
            "type": [
              "string",
              "null"
            ],
            "description": "Подписанная ссылка на файл, живёт 15 минут; новая — GET /media/{id}"
          }
        }
      },
      "Chat": {
        "type": "object",
        "properties": {
          "number_id": {
            "type": "integer"
          },
          "chat_id": {
            "type": "string"
          },
          "peer": {
            "type": [
              "string",
              "null"
            ]
          },
          "peer_lid": {
            "type": [
              "string",
              "null"
            ]
          },
          "name": {
            "type": [
              "string",
              "null"
            ]
          },
          "is_group": {
            "type": "boolean"
          },
          "group_subject": {
            "type": [
              "string",
              "null"
            ]
          },
          "avatar_url": {
            "type": [
              "string",
              "null"
            ],
            "description": "Фото профиля — подписанная ссылка на 15 минут; нет фото — null"
          },
          "last": {
            "$ref": "#/components/schemas/Message"
          },
          "last_at": {
            "type": "string"
          },
          "incoming": {
            "type": "integer"
          },
          "outgoing": {
            "type": "integer"
          },
          "total": {
            "type": "integer"
          }
        }
      },
      "Balance": {
        "type": "object",
        "required": [
          "balance",
          "daily_charge",
          "days_left"
        ],
        "properties": {
          "balance": {
            "type": "integer",
            "description": "Остаток, ₸"
          },
          "daily_charge": {
            "type": "integer",
            "description": "Списание в сутки: 330 ₸ × активные номера"
          },
          "days_left": {
            "type": [
              "integer",
              "null"
            ],
            "description": "На сколько полных суток хватит; null — активных номеров нет"
          },
          "active_numbers": {
            "type": "integer"
          },
          "paused_numbers": {
            "type": "integer"
          }
        }
      },
      "Limits": {
        "type": "object",
        "properties": {
          "from": {
            "type": "object",
            "properties": {
              "id": {
                "type": "integer"
              },
              "phone": {
                "type": [
                  "string",
                  "null"
                ]
              }
            }
          },
          "rating": {
            "type": "integer"
          },
          "tier": {
            "type": "string",
            "enum": [
              "excellent",
              "good",
              "medium",
              "low",
              "critical"
            ]
          },
          "tier_title": {
            "type": "string"
          },
          "enough_data": {
            "type": "boolean"
          },
          "limits": {
            "type": "object",
            "properties": {
              "messages_per_day": {
                "type": "integer"
              },
              "new_contacts_per_day": {
                "type": "integer"
              },
              "checks_per_day": {
                "type": "integer"
              },
              "messages_per_second": {
                "type": "integer"
              }
            }
          },
          "used": {
            "type": "object",
            "properties": {
              "messages": {
                "type": "integer"
              },
              "new_contacts": {
                "type": "integer"
              },
              "checks": {
                "type": "integer"
              }
            }
          },
          "cold_blocked_until": {
            "type": [
              "string",
              "null"
            ]
          },
          "warmup": {
            "type": "boolean",
            "description": "Идёт прогрев (первые 2 недели после подключения)"
          }
        }
      },
      "SendRequest": {
        "type": "object",
        "required": [
          "to"
        ],
        "description": "Текст или одно вложение (image, file, voice или video; с вложением text необязателен). Своего лимита размера файла нет — лимит WhatsApp, до 2 ГБ",
        "properties": {
          "to": {
            "type": "string",
            "description": "Получатель: телефон цифрами с кодом страны, 10–15 цифр (пробелы, +, скобки отбрасываются)",
            "examples": [
              "77011234567"
            ]
          },
          "text": {
            "type": "string",
            "maxLength": 4096,
            "description": "Текст; с вложением — подпись (необязательно)"
          },
          "from": {
            "type": [
              "integer",
              "string"
            ],
            "description": "С какого номера: id или телефон. Можно не указывать, если подключённый номер один"
          },
          "image": {
            "type": "object",
            "required": [
              "url"
            ],
            "properties": {
              "url": {
                "type": "string",
                "format": "uri"
              },
              "caption": {
                "type": "string"
              }
            },
            "description": "Фото по публичной ссылке http(s): JPEG, PNG или WebP. Ссылку скачиваем мы, в WhatsApp она не уходит"
          },
          "file": {
            "type": "object",
            "required": [
              "url"
            ],
            "properties": {
              "url": {
                "type": "string",
                "format": "uri"
              },
              "filename": {
                "type": "string"
              },
              "caption": {
                "type": "string"
              }
            },
            "description": "Любой файл документом по публичной ссылке http(s)"
          },
          "voice": {
            "type": "object",
            "required": [
              "url"
            ],
            "properties": {
              "url": {
                "type": "string",
                "format": "uri"
              }
            },
            "description": "Голосовое по публичной ссылке: MP3, M4A, WAV, OGG, AAC, AMR — перекодируем в OGG/Opus. Подписи нет"
          },
          "video": {
            "type": "object",
            "required": [
              "url"
            ],
            "properties": {
              "url": {
                "type": "string",
                "format": "uri"
              },
              "caption": {
                "type": "string"
              }
            },
            "description": "Видео по публичной ссылке: MP4 как есть, MOV, WebM, 3GP перекодируем в MP4"
          }
        }
      },
      "SendResponse": {
        "type": "object",
        "required": [
          "ok",
          "id",
          "from"
        ],
        "properties": {
          "ok": {
            "type": "boolean"
          },
          "id": {
            "type": "integer",
            "description": "id в истории wapi.kz — для GET /messages/{id}"
          },
          "wa_message_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "from": {
            "type": "object",
            "properties": {
              "id": {
                "type": "integer"
              },
              "phone": {
                "type": [
                  "string",
                  "null"
                ]
              }
            }
          }
        }
      },
      "CheckResponse": {
        "type": "object",
        "properties": {
          "phone": {
            "type": "string"
          },
          "exists": {
            "type": "boolean"
          },
          "chat_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "from": {
            "type": "object",
            "properties": {
              "id": {
                "type": "integer"
              },
              "phone": {
                "type": [
                  "string",
                  "null"
                ]
              }
            }
          }
        }
      },
      "Status": {
        "type": "object",
        "properties": {
          "status": {
            "type": "string",
            "description": "wa_status основного номера, PAUSED или none"
          },
          "phone": {
            "type": [
              "string",
              "null"
            ]
          },
          "numbers": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Number"
            }
          },
          "balance": {
            "type": "integer"
          },
          "daily_charge": {
            "type": "integer"
          },
          "days_left": {
            "type": [
              "integer",
              "null"
            ]
          },
          "paid_until": {
            "type": [
              "string",
              "null"
            ],
            "deprecated": true,
            "description": "Устарело: сегодня + days_left суток"
          }
        }
      },
      "Me": {
        "type": "object",
        "properties": {
          "email": {
            "type": "string"
          },
          "company": {
            "type": [
              "string",
              "null"
            ]
          },
          "balance": {
            "type": "integer"
          },
          "daily_charge": {
            "type": "integer"
          },
          "days_left": {
            "type": [
              "integer",
              "null"
            ]
          },
          "numbers_count": {
            "type": "integer"
          },
          "numbers_limit": {
            "type": "integer"
          },
          "history_enabled": {
            "type": "boolean"
          },
          "wa_status": {
            "type": "string"
          },
          "wa_phone": {
            "type": [
              "string",
              "null"
            ]
          },
          "paid_until": {
            "type": [
              "string",
              "null"
            ],
            "deprecated": true
          }
        }
      },
      "ServiceStatus": {
        "type": "object",
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "ok",
              "degraded",
              "outage"
            ]
          },
          "title": {
            "type": "string"
          },
          "components": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string"
                },
                "title": {
                  "type": "string"
                },
                "status": {
                  "type": "string",
                  "enum": [
                    "ok",
                    "degraded",
                    "outage"
                  ]
                },
                "text": {
                  "type": "string"
                }
              }
            }
          },
          "notice": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "text": {
                "type": "string"
              },
              "level": {
                "type": "string"
              },
              "updated_at": {
                "type": "string"
              }
            }
          },
          "checked_at": {
            "type": "string"
          }
        }
      },
      "Storage": {
        "type": "object",
        "properties": {
          "used_bytes": {
            "type": "integer",
            "description": "Занято файлами, байт (все номера)"
          },
          "included_bytes": {
            "type": "integer",
            "description": "Бесплатно на аккаунт (5 ГБ)"
          },
          "over_bytes": {
            "type": "integer",
            "description": "Сверх бесплатного объёма"
          },
          "files": {
            "type": "integer"
          },
          "packs": {
            "type": "array",
            "description": "Пакеты сверх бесплатного объёма (самый дешёвый набор)",
            "items": {
              "type": "object",
              "properties": {
                "gb": {
                  "type": "integer"
                },
                "kzt": {
                  "type": "integer"
                },
                "count": {
                  "type": "integer"
                }
              }
            }
          },
          "monthly_charge": {
            "type": "integer",
            "description": "₸ в месяц за пакеты"
          },
          "daily_charge": {
            "type": "number",
            "description": "≈ ₸ в сутки"
          },
          "ceiling_bytes": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Потолок объёма из кабинета; null — без потолка"
          },
          "retention_days": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Автоудаление файлов старше N дней; null — выключено"
          },
          "pending_files": {
            "type": "integer",
            "description": "Файлов в очереди на сохранение"
          },
          "policy": {
            "type": "object",
            "description": "Защита от чужих файлов (настраивается в кабинете)",
            "properties": {
              "skip_groups": {
                "type": "boolean"
              },
              "skip_unknown": {
                "type": "boolean"
              },
              "unknown_max_mb": {
                "type": [
                  "integer",
                  "null"
                ]
              },
              "chat_daily_mb": {
                "type": [
                  "integer",
                  "null"
                ]
              }
            }
          },
          "state": {
            "type": "string",
            "enum": [
              "ok",
              "frozen",
              "idle"
            ],
            "description": "frozen — не хватило баланса на пакеты (новые файлы сверх 5 ГБ не сохраняются); idle — нет активных номеров и оплат"
          },
          "delete_at": {
            "type": [
              "string",
              "null"
            ],
            "description": "Когда удалим файлы, если ничего не изменится (frozen — сверх 5 ГБ, idle — все)"
          },
          "by_number": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "number_id": {
                  "type": "integer"
                },
                "bytes": {
                  "type": "integer"
                },
                "files": {
                  "type": "integer"
                }
              }
            }
          },
          "by_kind": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "kind": {
                  "type": "string",
                  "enum": [
                    "photo",
                    "video",
                    "voice",
                    "document",
                    "sticker"
                  ]
                },
                "bytes": {
                  "type": "integer"
                },
                "files": {
                  "type": "integer"
                }
              }
            }
          }
        }
      },
      "Export": {
        "type": "object",
        "required": [
          "id",
          "status"
        ],
        "properties": {
          "id": {
            "type": "integer"
          },
          "status": {
            "type": "string",
            "enum": [
              "queued",
              "running",
              "done",
              "failed",
              "expired"
            ],
            "description": "expired — архив удалён (хранится 24 часа)"
          },
          "from": {
            "type": [
              "integer",
              "null"
            ],
            "description": "id номера; null — все номера"
          },
          "chat": {
            "type": [
              "string",
              "null"
            ]
          },
          "since": {
            "type": [
              "string",
              "null"
            ]
          },
          "until": {
            "type": [
              "string",
              "null"
            ]
          },
          "include_media": {
            "type": "boolean"
          },
          "created_at": {
            "type": "string"
          },
          "finished_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "expires_at": {
            "type": [
              "string",
              "null"
            ],
            "description": "До какого момента хранится архив"
          },
          "size": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Размер архива, байт"
          },
          "files": {
            "type": [
              "integer",
              "null"
            ]
          },
          "messages": {
            "type": [
              "integer",
              "null"
            ]
          },
          "missing_files": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Сколько файлов не нашлось в хранилище (список — missing.txt в архиве)"
          },
          "url": {
            "type": [
              "string",
              "null"
            ],
            "description": "Ссылка на ZIP, когда status = done; живёт 15 минут — за новой запросите выгрузку снова"
          },
          "error": {
            "type": [
              "string",
              "null"
            ],
            "description": "Причина, если status = failed"
          },
          "progress": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Ход сборки, 0–100"
          }
        }
      },
      "DeleteResult": {
        "type": "object",
        "required": [
          "ok",
          "deleted",
          "freed_bytes"
        ],
        "properties": {
          "ok": {
            "type": "boolean"
          },
          "deleted": {
            "type": "integer",
            "description": "Сколько файлов удалено"
          },
          "freed_bytes": {
            "type": "integer",
            "description": "Сколько байт освободилось"
          }
        }
      },
      "WebhookEvent": {
        "type": "object",
        "required": [
          "event",
          "wapi"
        ],
        "description": "Тело POST на ваш вебхук: событие WhatsApp как есть + поле wapi. Отвечайте 200 в течение 10 секунд; повторной доставки нет — пропущенное забирайте из GET /messages?since=…",
        "properties": {
          "event": {
            "type": "string",
            "enum": [
              "message",
              "message.out",
              "message.ack",
              "session.status",
              "export.ready",
              "storage.warning"
            ],
            "description": "message — входящее, message.out — написал человек с телефона, message.ack — статус доставки, session.status — статус подключения, export.ready — выгрузка готова (payload — как GET /exports/{id}, url = null), storage.warning — предупреждение хранилища"
          },
          "timestamp": {
            "type": "integer",
            "description": "мс Unix"
          },
          "session": {
            "type": "string"
          },
          "payload": {
            "type": "object",
            "description": "id, from, to, fromMe, body, hasMedia, participant (группы); у message.ack — ack (1 отправлено, 2 доставлено, 3 прочитано, -1 ошибка); у session.status — status",
            "additionalProperties": true
          },
          "me": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "id": {
                "type": "string"
              },
              "pushName": {
                "type": "string"
              }
            }
          },
          "wapi": {
            "type": "object",
            "properties": {
              "number_id": {
                "type": [
                  "integer",
                  "null"
                ]
              },
              "phone": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "message_id": {
                "type": [
                  "integer",
                  "null"
                ],
                "description": "id в истории wapi.kz (message, message.out; у message.ack — ваша отправка, если нашли)"
              },
              "peer": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "peer_lid": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "name": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "is_group": {
                "type": "boolean"
              },
              "export_id": {
                "type": "integer",
                "description": "Только у export.ready"
              },
              "user_id": {
                "type": "integer",
                "description": "Только у storage.warning"
              }
            }
          }
        }
      }
    }
  }
}
