> ## Documentation Index
> Fetch the complete documentation index at: https://docs.hubtalk.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Обращение к API

> Базовый адрес, аутентификация, формат и карта всех запросов.

# Обращение к API

## Как обращаться к API

* **Базовый адрес:** `https://{{HUBTALK_FQDN}}` — все пути ниже начинаются с `/v1`. Вместо `{{HUBTALK_FQDN}}` подставьте адрес, на котором работает ваш кабинет: `app.hubtalk.kz` — Казахстан, `app.hubtalk.uz` — Узбекистан.
* **Формат:** JSON в запросе и в ответе. Время — **секунды эпохи**.
* **Машиночитаемая схема:** `GET https://{{HUBTALK_FQDN}}/openapi.json`, интерактивный обозреватель — `https://{{HUBTALK_FQDN}}/docs`. Схема авторитетна для **запросов**; успешные ответы опубликованы там нетипизированными объектами, поэтому контракт ответов — таблицы на этой странице.

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

Каждый запрос `/v1` несёт ключ:

```
Authorization: Bearer YOUR_API_KEY
```

Секрет (`cfk_` + hex) выпускается в кабинете и показывается один раз ([Ключ доступа](/ru/v4/web-api/start)); платформа хранит только SHA-256-хеш. Отозванный ключ перестаёт работать сразу — запись ключа перечитывается на каждом запросе. Ключ ограничен организацией, в которой выпущен: всё, что за этой границей, неотличимо от отсутствующего (`404`), а не `403`.

| Отказ                                                     | Статус | `error.type`           | `error.code`                                      |
| --------------------------------------------------------- | ------ | ---------------------- | ------------------------------------------------- |
| нет или неверный заголовок `Authorization`                | 401    | `authentication_error` | `api_key_required`                                |
| неизвестный ключ                                          | 401    | `authentication_error` | `api_key_unknown`                                 |
| отозванный ключ                                           | 401    | `authentication_error` | `api_key_revoked`                                 |
| превышен лимит запросов ключа                             | 429    | `rate_limit_error`     | `api_key_rate_limit_exceeded` (`Retry-After: 60`) |
| привилегированная операция обычным ключом                 | 403    | `permission_error`     | `privileged_key_required`                         |
| привилегированная операция ключом не корневой организации | 403    | `permission_error`     | `root_org_key_required`                           |

Ни один эндпоинт этого справочника не требует привилегированного ключа.

**Лимит на ключ.** У каждого ключа скользящее окно 60 секунд по всем его запросам `/v1`. Лимит задаётся ключу при создании и может быть **изменён позже** в кабинете без ротации секрета (действует со следующего запроса). Лимит `0` означает общий лимит платформы — это настройка развёртывания, 60 запросов в минуту, если оператор не поменял; оператор может и отключить лимит. Это один из **четырёх источников 429**.

### Один конверт ошибки

Все ошибки `/v1` выглядят одинаково:

```json theme={null}
{
  "error": {
    "type": "rate_limit_error",
    "code": "dial_queue_full",
    "status": 429,
    "message": "Dial queue is full; retry after the interval in Retry-After"
  }
}
```

| HTTP-статус | `error.type`                |
| ----------- | --------------------------- |
| 400, 422    | `invalid_request_error`     |
| 401         | `authentication_error`      |
| 403         | `permission_error`          |
| 404         | `not_found_error`           |
| 409         | `conflict_error`            |
| 429         | `rate_limit_error`          |
| 503         | `service_unavailable_error` |
| 500, 502    | `api_error`                 |

`error.code` называет точную причину внутри типа и присутствует **всегда**: если более конкретная причина не зарегистрирована, `code` равен `type`. Новые коды со временем добавляются — незнакомый код разбирайте по `type` + `status`.

**Два дополнительных поля** приходят только на конкретных отказах по кампаниям и больше нигде: `error.blockers` на `409 launch_blocked` и `error.import` на `422 calls_rejected` / `422 calls_dnc_matched`. Любая другая ошибка несёт ровно четыре поля выше.

<Warning>
  Класс ошибки определяйте по `error.type` + `error.status`, точную причину — по `error.code`. **Никогда не разбирайте `error.message`** — это человекочитаемое английское пояснение, подбираемое по коду на границе API, и частью контракта оно не является. На ошибках схемы `422` (`code: "invalid_request"`) `message` — **массив** замечаний по полям в форме FastAPI/pydantic (`{"type", "loc", "msg", "input"}`), а не строка.
</Warning>

Отказы проносят свои заголовки через конверт: `Retry-After` на 429 приходит всегда.

### Лимиты и `429`

`429` приходит из четырёх независимых источников. Все несут `error.type: "rate_limit_error"` — различайте по `error.code`:

| `error.code`                  | Источник                                                                  | Что делать                                                                               | `Retry-After`               |
| ----------------------------- | ------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------- | --------------------------- |
| `api_key_rate_limit_exceeded` | скользящее окно 60 секунд по всем запросам с ключом                       | снизить общий темп запросов                                                              | да, всегда `60`             |
| `dial_queue_full`             | очередь набора платформы упёрлась в потолок глубины; **звонок не создан** | повторить тот же запрос с **тем же** `Idempotency-Key` через указанный интервал          | да (оценка очереди, 1–60 с) |
| `subgroup_concurrency_limit`  | исчерпан лимит одновременных звонков группы                               | ждать завершения активных звонков; честной оценки не существует, используйте свой бэкофф | нет (осознанно)             |
| `subgroup_rate_limit`         | исчерпан лимит инициаций в минуту у группы                                | выждать минутное окно                                                                    | да (`60`)                   |

Лимиты группы по умолчанию — 10 одновременных звонков и 30 инициаций в минуту, если администратор не задал другие; для транка, используемого по гранту, действует меньшее из лимита группы и лимита гранта.

## Карта запросов

**Кампании**

| Метод и путь                          | Зачем                                                               |
| ------------------------------------- | ------------------------------------------------------------------- |
| `POST /v1/campaigns`                  | создать кампанию, передать номера и запустить — один запрос         |
| `GET /v1/campaigns`                   | список кампаний с курсорной постраничностью (краткие представления) |
| `GET /v1/campaigns/{id}`              | текущее состояние кампании                                          |
| `GET /v1/campaigns/{id}/launch-check` | `{blockers, warnings, dnc_screen}` до действия                      |
| `POST /v1/campaigns/{id}/launch`      | запустить кампанию, которая осталась черновиком                     |
| `POST /v1/campaigns/{id}/calls`       | добавить партию номеров в уже созданную кампанию                    |
| `POST /v1/campaigns/{id}/pause`       | остановить набор новых номеров                                      |
| `POST /v1/campaigns/{id}/resume`      | продолжить (проверки запуска выполняются заново)                    |
| `POST /v1/campaigns/{id}/cancel`      | отменить необратимо                                                 |
| `GET /v1/campaigns/{id}/progress`     | состояния контактов, удержания, темп, счётчики реестра и частоты    |

**Загрузки номеров**

| Метод и путь                                        | Зачем                                                               |
| --------------------------------------------------- | ------------------------------------------------------------------- |
| `GET /v1/imports/{import_id}`                       | сводка по загрузке плюс живая или зафиксированная сверка с реестром |
| `GET /v1/imports/{import_id}/report?format=csv`     | отчёт по строкам, `csv` или `xlsx`                                  |
| `GET /v1/imports/{import_id}/report?only=dnc_match` | только совпадения с реестром «не звонить», `csv` или `xlsx`         |

**Линии**

| Метод и путь                        | Зачем                                 |
| ----------------------------------- | ------------------------------------- |
| `GET /v1/dialing-routes?agent_id=…` | какие транки и группы доступны агенту |

**Отдельные звонки**

| Метод и путь                    | Зачем                                          |
| ------------------------------- | ---------------------------------------------- |
| `POST /v1/calls/phone`          | один исходящий звонок                          |
| `POST /v1/calls/web`            | web-звонок: токен для браузера или SDK         |
| `GET /v1/calls`                 | список звонков с фильтрами и курсором          |
| `GET /v1/calls/{call_id}`       | полный объект звонка                           |
| `GET /v1/calls/{call_id}/turns` | метрики задержек по тёрнам завершённого звонка |
| `POST /v1/calls/{call_id}/end`  | завершить активный звонок                      |
