# GENERATED — не редактировать. source-hash: dda8c78fcc840785
openapi: 3.1.0
info:
  title: QNNECT Public API
  version: '1.0'
  description: 'HTTP API для отправки WhatsApp-сообщений и шаблонов через ваш канал QNNECT

    и для чтения каталога шаблонов канала.


    ## Аутентификация


    Каждый запрос аутентифицируется двумя значениями, которые копируются из

    кабинета QNNECT (**Интеграции → API**):


    * `channel_uuid` — идентификатор канала, часть пути URL;

    * `api_token` — секретный токен канала, передаётся query-параметром `token`.


    Оба значения — per-канал. Держите токен в секрете: любой, у кого он есть,

    может отправлять сообщения за ваш счёт. Токен можно перевыпустить в

    кабинете в любой момент (старый перестаёт работать сразу).


    ## Модель доставки


    Отправляющие эндпоинты асинхронны: успешный вызов возвращает `202` с

    `message_uuid` — сообщение поставлено в очередь и доставляется в фоне.

    Прогресс доставки приходит через webhook-интеграции (см. Webhooks).


    ## Ошибки


    Ошибки — JSON-объекты `{"error": "<код>"}` с опциональным человекочитаемым

    `message`. HTTP-коды: `400` неверный ввод, `401` нет или неверный токен,

    `403` отправка приостановлена (компания заблокирована или нет средств),

    `404` неизвестный канал или шаблон.

    '
servers:
- url: https://qnnect.kz
  description: Production
tags:
- name: Messages
  description: Отправка текстовых и медиа-сообщений пользователям WhatsApp.
- name: Templates
  description: Отправка одобренных WhatsApp-шаблонов и просмотр каталога шаблонов.
paths:
  /api/v1/{channel_uuid}/message/:
    post:
      operationId: sendMessage
      tags:
      - Messages
      summary: Отправить текст или медиа
      description: 'Отправляет свободное сообщение пользователю WhatsApp. Свободные

        сообщения доставляются только внутри 24-часового сервисного окна,

        открытого последним входящим сообщением пользователя; вне окна

        используйте шаблон.


        `type` выбирает вид сообщения. Для `text` обязательно поле `text`.

        Для медиа-типов (`image`, `video`, `audio`, `document`, `sticker`)

        обязателен `media.url`; `text` становится подписью там, где WhatsApp

        её поддерживает (`image`, `video`, `document`).


        Поддерживаемые форматы и лимиты размера:


        | type | MIME | макс. размер |

        |---|---|---|

        | image | image/jpeg, image/png | 5 MB |

        | video | video/mp4, video/3gpp | 16 MB |

        | audio | audio/aac, audio/mp4, audio/mpeg, audio/amr, audio/ogg | 16 MB |

        | document | pdf, doc(x), xls(x), ppt(x), txt | 100 MB |

        | sticker | image/webp | 500 KB |


        `media.url` должен быть публичным HTTPS-URL.

        '
      security:
      - channelToken: []
      parameters:
      - $ref: '#/components/parameters/ChannelUuid'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SendMessageRequest'
            examples:
              text:
                summary: Простой текст
                value:
                  type: text
                  phone: '77778866697'
                  text: Hello! Your order is ready.
              image:
                summary: Изображение с подписью
                value:
                  type: image
                  phone: '77778866697'
                  text: Our new arrivals
                  media:
                    url: https://example.com/promo.jpg
              document:
                summary: Документ с именем файла
                value:
                  type: document
                  phone: '77778866697'
                  text: Price list attached
                  media:
                    url: https://example.com/pricelist.pdf
                    filename: pricelist.pdf
      responses:
        '202':
          description: Сообщение принято и поставлено в очередь доставки.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/QueuedResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/SendingSuspended'
        '404':
          $ref: '#/components/responses/ChannelNotFound'
  /api/v1/{channel_uuid}/template/:
    post:
      operationId: sendTemplate
      tags:
      - Templates
      summary: Отправить шаблонное сообщение
      description: 'Отправляет одобренный WhatsApp-шаблон. Шаблоны работают вне 24-часового

        окна — это способ начать диалог первым.


        `values` заполняет нумерованные плейсхолдеры тела `{{1}}, {{2}}, …`

        по порядку; количество должно точно совпадать с шаблоном (см.

        `GET /api/v1/{channel_uuid}/templates/` → `variables.body`).

        `header_values` и `button_values` заполняют плейсхолдеры

        заголовка/кнопок, если они есть в шаблоне.


        Медиа-шаблоны (заголовок IMAGE/VIDEO/DOCUMENT): передайте публичный

        `media_url` (опционально с `media_mime_type`) или заранее

        зарегистрированный `media_id`. Карусели: `cards` подменяет картинки

        карточек по индексу; `null` оставляет образец из шаблона.

        '
      security:
      - channelToken: []
      parameters:
      - $ref: '#/components/parameters/ChannelUuid'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SendTemplateRequest'
            examples:
              basic:
                summary: Только переменные тела
                value:
                  phone: '77778866697'
                  template_name: order_confirm
                  values:
                  - Ivan
                  - '12345'
              media:
                summary: Медиа-шаблон
                value:
                  phone: '77778866697'
                  template_name: promo_image
                  values:
                  - Ivan
                  media_url: https://example.com/promo.jpg
                  media_mime_type: image/jpeg
              carousel:
                summary: Карусель с подменой картинки карточки
                value:
                  phone: '77778866697'
                  template_name: products_carousel
                  values:
                  - Ivan
                  cards:
                  - media_url: https://example.com/product1.jpg
                  - null
      responses:
        '202':
          description: Шаблон принят и поставлен в очередь доставки.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/QueuedResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/SendingSuspended'
        '404':
          description: Канал или шаблон не найден.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                channel:
                  value:
                    error: channel_not_found
                template:
                  value:
                    error: template_not_found
  /api/v1/{channel_uuid}/templates/:
    get:
      operationId: listTemplates
      tags:
      - Templates
      summary: Список шаблонов канала
      description: 'Возвращает каталог шаблонов канала со статусами и количеством

        переменных. Используйте его, чтобы узнать, что можно отправить и

        сколько `values` ждёт каждый шаблон, до вызова отправки.


        По умолчанию возвращаются все статусы, включая `PENDING` и

        `REJECTED` — реально отправить можно только `APPROVED`.

        '
      security:
      - channelToken: []
      parameters:
      - $ref: '#/components/parameters/ChannelUuid'
      - name: status
        in: query
        required: false
        description: Фильтр по точному статусу (например APPROVED, PENDING, REJECTED).
        schema:
          type: string
          enum:
          - APPROVED
          - PENDING
          - REJECTED
          - DISABLED
          - PAUSED
          - FLAGGED
          - IN_APPEAL
          - LOCKED
          - LIMIT_EXCEEDED
          - FAILED
      - name: category
        in: query
        required: false
        description: Фильтр по категории.
        schema:
          type: string
          enum:
          - MARKETING
          - UTILITY
          - AUTHENTICATION
      - name: type
        in: query
        required: false
        description: Фильтр по типу шаблона (TEXT, IMAGE, VIDEO, DOCUMENT, CAROUSEL, …).
        schema:
          type: string
      responses:
        '200':
          description: Каталог шаблонов канала.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TemplatesListResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/ChannelNotFound'
webhooks:
  incoming_message:
    post:
      operationId: whIncomingMessage
      summary: Входящее сообщение от пользователя WhatsApp
      description: 'Отправляется на настроенный вами webhook-URL, когда пользователь

        WhatsApp пишет в канал. Webhook-URL настраиваются в кабинете

        (**Интеграции → Webhooks**) и включаются по типам событий.


        Доставка: `POST` с JSON-телом, `Content-Type: application/json;

        charset=utf-8`. Ответьте любым `2xx` за 12 секунд; неуспешные

        доставки ретраятся в фоне.

        '
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/WebhookEvent'
      responses:
        '200':
          description: Подтвердите получение любым 2xx-статусом.
  outgoing_message:
    post:
      operationId: whOutgoingMessage
      summary: Исходящее сообщение из канала
      description: 'Отправляется, когда сообщение уходит из канала — из интерфейса QNNECT,

        по API или из интеграции. Контракт доставки как у `incoming_message`.

        '
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/WebhookEvent'
      responses:
        '200':
          description: Подтвердите получение любым 2xx-статусом.
  message_status_update:
    post:
      operationId: whMessageStatusUpdate
      summary: Изменился статус доставки сообщения
      description: 'Отправляется, когда WhatsApp сообщает об изменении статуса ранее

        отправленного сообщения (`sent` → `delivered` → `read` или `failed`).

        Сопоставляйте по `message_uuid`. Контракт доставки как у

        `incoming_message`.

        '
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/WebhookEvent'
      responses:
        '200':
          description: Подтвердите получение любым 2xx-статусом.
components:
  securitySchemes:
    channelToken:
      type: apiKey
      in: query
      name: token
      description: 'API-токен канала (64 hex-символа). Скопируйте в кабинете:

        **Интеграции → API**. Перевыпуск токена немедленно отзывает старый.

        '
  parameters:
    ChannelUuid:
      name: channel_uuid
      in: path
      required: true
      description: Идентификатор канала (UUID) из кабинета, **Интеграции → API**.
      schema:
        type: string
        format: uuid
  responses:
    BadRequest:
      description: Неверный ввод. В `error` — машиночитаемый код.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    Unauthorized:
      description: Токен отсутствует (`missing_token`) или неверен (`invalid_token`).
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    SendingSuspended:
      description: Отправка приостановлена — компания заблокирована или нет средств (`company_inactive`).
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    ChannelNotFound:
      description: Канала с таким `channel_uuid` нет (`channel_not_found`).
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
  schemas:
    Error:
      type: object
      required:
      - error
      properties:
        error:
          type: string
          description: Машиночитаемый код ошибки.
        message:
          type: string
          description: Опциональное человекочитаемое пояснение.
    QueuedResponse:
      type: object
      required:
      - status
      - message_uuid
      properties:
        status:
          type: string
          const: queued
          description: Сообщение поставлено в очередь асинхронной доставки.
        message_uuid:
          type: string
          format: uuid
          description: Идентификатор сообщения; по нему сопоставляются webhook-статусы.
        timestamp:
          type: string
          format: date-time
          description: Серверное время приёма сообщения.
    SendMessageRequest:
      type: object
      required:
      - type
      - phone
      properties:
        type:
          type: string
          enum:
          - text
          - image
          - video
          - audio
          - document
          - sticker
          description: Вид сообщения. `text` требует `text`; медиа-виды требуют `media.url`.
        phone:
          type: string
          description: Телефон получателя в международном формате, только цифры (например 77778866697).
        text:
          type: string
          description: Текст для `type=text`; подпись для image/video/document.
        media:
          type: object
          description: Описание медиа; обязательно для медиа-типов.
          properties:
            url:
              type: string
              format: uri
              description: Публичный HTTPS-URL медиафайла.
            filename:
              type: string
              description: Отображаемое имя файла (только для документов).
    SendTemplateRequest:
      type: object
      required:
      - phone
      - template_name
      properties:
        phone:
          type: string
          description: Телефон получателя в международном формате, только цифры.
        template_name:
          type: string
          description: Имя шаблона ровно как в каталоге (`listTemplates`).
        values:
          type: array
          items:
            type: string
          description: Значения плейсхолдеров тела `{{1}}, {{2}}, …` по порядку; количество должно совпадать
            с `variables.body`.
        header_values:
          type: array
          items:
            type: string
          description: Значения плейсхолдеров заголовка, если они есть в шаблоне.
        button_values:
          type: array
          items:
            type: string
          description: Значения плейсхолдеров кнопок (динамические суффиксы URL и т.п.).
        media_url:
          type: string
          format: uri
          description: Публичный URL медиа для шаблонов с медиа-заголовком.
        media_id:
          type: string
          description: Заранее зарегистрированный media id — альтернатива `media_url`.
        media_mime_type:
          type: string
          description: MIME-тип `media_url` (например image/jpeg).
        cards:
          type: array
          description: Только карусель — подмена картинок карточек по индексу; `null` оставляет образец.
          items:
            oneOf:
            - type: 'null'
            - type: object
              properties:
                media_url:
                  type: string
                  format: uri
    TemplatesListResponse:
      type: object
      required:
      - channel_uuid
      - templates_count
      - templates
      properties:
        channel_uuid:
          type: string
          description: Эхо запрошенного канала.
        templates_count:
          type: integer
          description: Число шаблонов после фильтров.
        templates:
          type: array
          items:
            $ref: '#/components/schemas/Template'
    Template:
      type: object
      properties:
        template_id:
          type: string
          description: Идентификатор шаблона.
        name:
          type: string
          description: Имя шаблона — используйте как `template_name` при отправке.
        category:
          type: string
          enum:
          - MARKETING
          - UTILITY
          - AUTHENTICATION
          description: Тарифная категория WhatsApp.
        language:
          type: string
          description: Код языка шаблона (например ru, en).
        type:
          type: string
          description: Тип шаблона (TEXT, IMAGE, VIDEO, DOCUMENT, CAROUSEL, …).
        status:
          type: string
          description: Статус модерации; отправлять можно только APPROVED.
        status_reason:
          type:
          - string
          - 'null'
          description: Причина отказа Meta для REJECTED/FAILED-шаблонов.
        quality_score:
          type:
          - string
          - 'null'
          enum:
          - GREEN
          - YELLOW
          - RED
          - UNKNOWN
          - null
          description: Оценка качества шаблона от WhatsApp.
        content:
          type: string
          description: Текст тела с нумерованными плейсхолдерами `{{1}}, {{2}}, …`.
        header:
          type: string
          description: Текст заголовка для шаблонов с TEXT-заголовком.
        header_media_url:
          type: string
          description: Образец медиа-URL для шаблонов с медиа-заголовком.
        buttons:
          type:
          - array
          - 'null'
          description: Определения кнопок, как сохранены в шаблоне.
        cards:
          type:
          - array
          - 'null'
          description: Определения карточек карусели (медиа, текст, кнопки).
        variables:
          type: object
          description: Сколько значений плейсхолдеров ждёт каждая часть при отправке.
          properties:
            body:
              type: integer
              description: Требуемая длина `values`.
            header:
              type: integer
              description: Требуемая длина `header_values`.
            button:
              type: integer
              description: Требуемая длина `button_values`.
    WebhookEvent:
      type: object
      required:
      - event
      - phone
      - channel_id
      - message_uuid
      - status
      - sender
      - timestamp
      properties:
        event:
          type: string
          enum:
          - incoming_message
          - outgoing_message
          - message_status_update
          description: Тип события; совпадает с именем вебхука.
        phone:
          type: string
          description: Номер телефона пользователя WhatsApp.
        channel_id:
          type: string
          description: Идентификатор канала (Gupshup app id), к которому относится событие.
        text:
          type: string
          description: Текст сообщения (пусто для нетекстового контента).
        message_uuid:
          type: string
          format: uuid
          description: Идентификатор сообщения; для API-отправок совпадает с `QueuedResponse.message_uuid`.
        status:
          type: string
          description: Текущий статус сообщения (queued, sent, delivered, read, failed, …).
        sender:
          type: string
          enum:
          - CLIENT
          - COMPANY
          - AI
          description: Кто автор сообщения.
        timestamp:
          type: string
          format: date-time
          description: Время события (ISO 8601).
