Перейти к содержимому
AISARAISAR
REST API

Звонки

Журнал звонков компании (GET /calls, GET /calls/counters) и программное управление голосовыми звонками: инициировать исходящий вызов к контакту (POST /calls/originate) и завершить активный вызов (POST /calls/{call}/hangup). Это API управления звонками, а не браузерный софтфон — оно лишь ставит вызов в работу через телефонный шлюз AISAR; сам звук идёт по SIP/WebRTC вне HTTP. Также включает «Пульт супервайзера» (/v1/telephony/*): активные звонки, presence команды, wallboard, историческую статистику и действия супервайзера над живым звонком (listen/whisper/barge/transfer).

Доступ и требования

Вся группа доступна только при активной подписке и включённом на плане функционале телефонии (telephony); кроме того, вызывающий пользователь должен быть заведён как оператор телефонии (иметь провиженный SIP-эндпоинт) и обладать правом telephony.use в текущей компании — иначе 403 { "data": { "message": "Telephony is not available for your account." } }.

Инициировать вызов (`POST /calls/originate`)

POST /calls/originate принимает contact_id и channel_id (оба обязательны, целые). Канал задаёт способ дозвона: канал WhatsApp/WABA звонит контакту в WhatsApp, а канал типа sip_telephony набирает телефонный номер контакта через SIP-транк. И контакт, и канал должны принадлежать текущей компании (иначе 404 { "data": { "message": "Contact or channel not found." } }).

Ответ и статусы звонка

Успех (200) возвращает созданную запись звонка: { "data": { "call": { id, direction: "outbound", status: "ringing", to_number, from_number, asterisk_channel_id, timeline_message_id } } } — вызов создаётся в состоянии ringing, а в таймлайн диалога добавляется карточка звонка (timeline_message_id). Дальнейшие переходы статуса (ringing → answered → completed, либо rejected / missed / failed) приходят через события реального времени и вебхуки семейства call.*, а не в теле этого ответа.

Разрешение на звонок (WhatsApp и SIP)

WhatsApp-звонки требуют действующего разрешения на звонок (call permission) от контакта: без него originate вернёт 422 с блоком eligibility, где call.can_call = false. Проверить готовность и запросить разрешение заранее можно через GET /v1/contacts/{contact}/outbound-eligibility и POST /v1/contacts/{contact}/call-permission/request (группа «Контакты»). Для SIP-каналов разрешение не требуется (eligibility.call.permission_status = "not_required"), нужен лишь телефонный номер у контакта — иначе 422 { "data": { "message": "Contact has no phone number to dial." } }.

Прочие исходы originate

Прочие исходы originate:

  • 403 с блоком eligibility — канал или контакт не поддерживает звонок (нет WhatsApp-аккаунта, оператор не включён).
  • 502 — телефонный шлюз не смог инициировать вызов.
  • 422 — ошибка валидации тела (отсутствуют contact_id / channel_id).
  • Канал с sip_outbound_gated: true (см. группу «Каналы») пропускается при выборе SIP-канала для звонка — если это единственный SIP-канал компании, originate ведёт себя так, как будто подключённого SIP-канала нет вовсе.
  • Только для sip_telephony-канала в режиме подключения по IP-адресу: дополнительная защита от злоупотреблений — запрещённый префикс номера или превышение лимита в 300 исходящих звонков в час на канал — возвращает 422 с сообщением "This destination cannot be dialled from this line." либо "Too many calls placed on this line recently. Try again later.". Каналы, подключённые по логину/паролю, этой проверке не подвергаются.

Завершение вызова (`POST /calls/{call}/hangup`)

POST /calls/{call}/hangup завершает активный вызов. Помимо доступности телефонии, завершить вызов может только его участник (назначенный оператор либо сторона вызова) или пользователь с правом управления телефонией компании — иначе 403. Успех: { "message": "Hangup requested." } (запрос на разрыв передан в шлюз; фактическое завершение подтверждается событием call.*). Обратите внимание: ответ hangup, в отличие от originate, не обёрнут в data.

Расшифровка звонка (`GET /calls/{call}/transcript`)

GET /calls/{call}/transcript отдаёт расшифровку и сводку голосовой ИИ-сессии (ElevenLabs), привязанной к звонку. У эндпоинта СВОЙ гейт доступа, отдельный от общего требования группы — telephony.use и провиженный SIP-аккаунт здесь не обязательны. Достаточно любого из трёх условий: (а) вызывающий — участник звонка (назначенный оператор либо сторона вызова, та же проверка, что у hangup); (б) у вызывающего есть право company_telephony.manage; (в) звонок принял ИИ-агент (assignee_type = "ai_agent") и у вызывающего есть conversation.view в диалоге этого звонка — так расшифровку может открыть любой оператор, читающий этот диалог, а не только супервайзер. Для звонка, принятого человеком, conversation.view доступа не даёт: такую расшифровку видит только сам участник или менеджер телефонии.

Компания сверяется первой, до любой другой проверки: чужой либо несуществующий {call}404 (как будто записи не существует), а не 403. Если компания совпала, но ни одна из трёх веток доступа не выполнена — 403. Если у звонка вообще нет голосовой сессии (обычный звонок без ИИ-агента) — тоже 404: { "data": { "message": "No voice session for this call." } }.

Успешный ответ (200): { "data": { voiceCallId, callId, status, outcome, summary, transcript: [...], startedAt, answeredAt, endedAt } } — обратите внимание, поля этого ответа в camelCase, а не в snake_case, как в остальной группе. voiceCallId — UUID голосовой сессии, callId — id звонка (совпадает с {call} из пути). status — состояние сессии (например dialing/in_progress/completed/failed). outcomeresolved/failed/null, если вендор не определил исход. summary — текстовая сводка звонка либо null. startedAt/answeredAt/endedAt — ISO-8601 либо null.

Каждый элемент transcript[] — реплика: role (строка, user/agent/…), text, at_sec (секунда звонка, число) и tool_calls[] — вызовы инструментов ИИ-агента на этой реплике. Каждый элемент tool_calls[] отдаёт фиксированный набор полей: type, tool_name, request_id, params_as_json (строка — аргументы вызова как прислал вендор), tool_has_been_called (bool), result (результат инструмента: разобранный JSON, сырая строка или null, если результат не зафиксирован), is_error (bool либо null; null значит «результат не зафиксирован», а НЕ успех), error (текст ошибки вендора либо null) и latency_sec (число секунд либо null). Вендор может прислать вызов инструмента и его результат на разных репликах — API уже сопоставляет их по request_id и отдаёт результат на той же реплике, где случился сам вызов.

Ссылка на запись звонка (`GET /calls/{call}/recording-url`)

GET /calls/{call}/recording-url минтит одноразовую подписанную ссылку на аудиозапись звонка — точечный аналог поля recording_url в журнале (GET /calls), только выпущенный по явному клику, а не заранее для каждой строки списка. Гейт доступа полностью идентичен transcript (см. выше): участник звонка, company_telephony.manage или читатель диалога ИИ-звонка через conversation.view; тот же порядок 404 (чужая компания) → 403 (нет права). Если для звонка нет сохранённой записи (recording_status не stored, либо запись не удалось сопоставить) — 404 { "data": { "message": "No recording for this call." } }.

Успешный ответ (200): { "data": { "recording_url": "<подписанная ссылка>" } } — ссылка живёт 15 минут (короче, чем 30-минутная ссылка из списка журнала, так как минтится по прямому клику «слушать/скачать», а не заранее). Необязательный query-параметр via=play|download (по умолчанию play) — download добавляет Content-Disposition: attachment, заставляя браузер скачать файл вместо проигрывания. Лимит — 30 запросов в минуту на пользователя.

Журнал звонков (`GET /calls`)

GET /calls возвращает журнал звонков компании в конверте { "data": { "items": [...], "pagination": {...} } } (конвенция листинговых эндпоинтов этого API, НЕ конверт { "data": [...] }). По умолчанию оператор видит только звонки, к которым он причастен (назначенный, звонящий или принимающий сторона); право company_telephony.manage расширяет видимость до всех звонков компании — это одно и то же ограничение видимости, что используется в GET /calls/counters, так что счётчики никогда не разойдутся со списком. Базовые фильтры (все необязательные query-параметры): filter=all|missed (по умолчанию all; missed — непринятые входящие звонки), direction=inbound|outbound, channel_id (целое, должен существовать), q (строка до 100 символов — регистронезависимый поиск подстроки по from_number/to_number и имени контакта), assignee_type=operator|ai_agent, page (≥1), perPage (1–100, по умолчанию 20). Сортировка — по убыванию started_at. Лимит: 120 запросов в минуту на пользователя (лимитер telephony-calls-index, добавлен вместе с архивными фильтрами ниже — до этого у GET /calls не было отдельного троттлинга); при превышении — 429.

Фильтры архива записей (`GET /calls`, Ф3c)

Тот же GET /calls поддерживает дополнительный набор необязательных query-параметров для режима архива «Записи» — если их не передавать, поведение журнала (в т.ч. используемого во встроенном чате) не меняется: has_recording=1 — только звонки с сохранённой записью (recording_status = "stored"); assigned_user_id (целое) — звонки конкретного оператора (значение не проверяется на существование — id из чужой компании просто не даёт совпадений, а не 404/422, т.к. видимость и так ограничена текущей компанией); date_from/date_to (даты YYYY-MM-DD, date_to >= date_from) — календарные даты, резолвятся в часовом поясе компании и переводятся в UTC-границы суток, как в GET /telephony/stats; with_summary=1 — добавляет в ответ блок summary по ВСЕМУ отфильтрованному набору (см. ниже), а не только по текущей странице; lazy_recordings=1 — не подписывать recording_url заранее для каждой строки списка (поле будет null у всех звонков, включая те, где есть запись) — ссылка выдаётся по клику через GET /calls/{call}/recording-url.

При with_summary=1 ответ дополнительно содержит { "data": { ..., "summary": { "count": N, "total_duration_sec": S } } } — количество звонков и суммарная длительность в секундах по ВСЕМ звонкам, подходящим под текущий набор фильтров (не только по звонкам на странице). Блок summary отсутствует в ответе, если with_summary не передан.

Каждый элемент items[] — объект звонка: id, direction, status (ringing/answered/completed/rejected/missed/failed), provider (whatsapp/sip/internal), from_number, to_number, временные метки started_at/answered_at/ended_at (ISO-8601 либо null), duration_sec, вложенные contact (id, display_name, avatar_url) и assignee (id, name), channel_id/channel_name/channel_type (слаг типа канала, ChannelType::key, например whatsapp_business или sip_telephony), conversation_id, timeline_message_id, recording_status, recording_url, has_recording (bool, = recording_status === "stored") и has_transcript (bool — есть ли у звонка голосовая ИИ-сессия, т.е. вернёт ли GET /calls/{call}/transcript диалог, а не 404). recording_url — временная подписанная ссылка (TTL ~30 минут); присутствует, только когда recording_status = "stored" И запрос НЕ передавал lazy_recordings=1 — при lazy_recordings=1 поле всегда null, даже у звонков с записью (используйте вместо него GET /calls/{call}/recording-url по клику). has_recording, в отличие от recording_url, не зависит от lazy_recordings и остаётся достоверным индикатором наличия записи в обоих режимах. pagination содержит { page, perPage, total, lastPage }.

Счётчики звонков (`GET /calls/counters`)

GET /calls/counters возвращает бейдж-счётчики { "data": { "all": N, "missed": M } } — построены из того же самого запроса видимости, что и GET /calls (без фильтров direction/channel_id), поэтому число никогда не разойдётся со списком. Параметров нет.

Пульт супервайзера: доступ

Все эндпоинты /v1/telephony/* ниже (пульт, статистика, действия супервайзера) гейтятся ОТДЕЛЬНО от остальной группы: не проверкой telephony-фичи тарифа и telephony.use (это гейт /calls/* выше), а правом company_telephony.manage — без него 403. У вызывающего не обязателен провиженный SIP-эндпоинт (в отличие от telephony.use). На момент публикации отдельного права telephony.supervise не существует: доступ идёт через право company_telephony.manage, которым по умолчанию обладает роль admin (продуктовое решение 2026-08-13).

Пульт: активные звонки, presence, wallboard

Три read-only эндпоинта без параметров, каждый отдаёт снимок «прямо сейчас» (не пагинированы). GET /telephony/console/active-calls{ "data": { "items": [...] } }, каждый элемент: id, direction, kind, provider, status, contact (id/display_name/avatar_url либо null), channel_id, channel_name, from_number, to_number, started_at/answered_at (ISO-8601 либо null), waiting_sec (время в звонке до ответа, либо время звонка на текущий момент, если ещё звонит), talk_sec (текущая длительность разговора, null пока не отвечен), assignee ({ type: "operator"|"ai_agent"|null, id, name }).

GET /telephony/console/presence{ "data": { "items": [...] } }, по одному элементу на оператора с активным SIP-аккаунтом: user_id, name, availability (сырое значение доступности пользователя), status — вычисленный статус: on_call (в разговоре), acw (после звонка, короткое окно донастройки), offline, away, free; devices ({ browser: { registered } | null, external: { registered } | null }) и metrics_today ({ answered, missed, avg_talk_sec }, в часовом поясе компании). GET /telephony/console/wallboard — плоский объект { "data": { active_calls, ringing, operators_online, operators_free, missed_today, service_level_today } }; operators_online/operators_free считаются из того же presence, поэтому никогда не разойдутся с ним. service_level_today — готовый процент (0–100), не доля.

Статистика (`GET /telephony/stats`)

GET /telephony/stats — исторические KPI и ряды для графиков за период. Обязательный query-параметр period=today|7d|30d|custom; для custom дополнительно обязательны date_from/date_to (даты, date_to >= date_from, диапазон не более 92 дней — иначе 422). Периоды считаются в часовом поясе компании, затем переводятся в UTC. Ответ — плоский объект { "data": { from, to, total, answered, missed, avg_talk_sec, service_level, service_level_threshold_sec, ai_share, by_day, by_hour, by_channel, by_assignee } }: service_level и ai_share — готовые проценты (0–100, не доли); service_level_threshold_sec — эхо порога сервис-левела из конфигурации; by_day[{ date, answered, missed }]; by_hour[{ hour, total }]; by_channel[{ channel_id, channel_name, provider, total }]; by_assignee[{ type, id, name, answered, missed, avg_talk_sec }].

Действия супервайзера (`listen`/`whisper`/`barge`/`transfer`)

Четыре POST /telephony/console/calls/{call}/{action} для живого звонка: listen — тихое прослушивание, whisper — суфлирование оператору (клиент не слышит), barge — включение в разговор (слышно обеим сторонам), transfer — перевод другому оператору. Все 4 действия требуют, чтобы звонок был именно в статусе answered — иначе 422 (изначально transfer допускал ещё звонящий ringing звонок, но такой перевод не поддержан ни бриджем, ни UI пульта, поэтому в ревью это было выровнено с остальными тремя действиями). transfer принимает обязательное тело { "target_user_id": <int> } — получатель должен быть реальным (не сервис-аккаунтом) участником той же компании И иметь там активный SIP-аккаунт оператора, иначе 422. Успех (200): { "data": { "message": "Supervisor action executed.", "action": "<listen|whisper|barge|transfer>" } }; следом асинхронно уходит вебхук call.supervised. Лимит — 30 запросов в минуту на пользователя (лимитер telephony-supervise), при превышении 429.

  • 404 — звонок не найден или принадлежит другой компании.
  • 403 — вызывающий это сервис-аккаунт (PAT), ЛИБО у него нет права company_telephony.manage (проверяется в этом порядке).
  • 422 — звонок неактивен (уже завершён либо статус не ringing/answered), либо ещё не отвечен (answered требуется для ВСЕХ 4 действий, включая transfer), либо у звонка пуст/отсутствует asterisk_channel_id, либо (только transfer) не передан target_user_id, либо получатель не является активным SIP-оператором именно этой компании, либо бридж отклонил действие по собственной причине — тело ответа отдаёт фиксированное сообщение, сырая ошибка бриджа в него не попадает (логируется на сервере).
  • 409 — звонок уже супервайзится другим пользователем (гард на стороне бриджа).
  • 410 — живой канал звонка пропал с бриджа между проверкой и вызовом (звонок завершился в этот момент).
  • 502 — бридж (asterisk-bridge) недоступен или вернул ошибку ARI.

Эндпоинты

МетодПуть
GET/v1/calls

Журнал звонков компании (пагинация, фильтры all/missed/direction/channel_id/q/assignee_type; архив: has_recording/assigned_user_id/date_from-date_to/with_summary/lazy_recordings).

GET/v1/calls/counters

Счётчики звонков `{all, missed}` — та же видимость, что и журнал.

POST/v1/calls/originate

Инициировать исходящий звонок контакту через WhatsApp- или SIP-канал.

POST/v1/calls/{call}/hangup

Завершить активный звонок (доступно участнику вызова или менеджеру телефонии).

GET/v1/calls/{call}/transcript

Расшифровка и сводка голосового ИИ-звонка (участнику звонка, менеджеру телефонии или читателю диалога ИИ-звонка).

GET/v1/calls/{call}/recording-url

Подписанная ссылка на запись звонка на 15 минут (тот же гейт, что у transcript).

GET/v1/telephony/console/active-calls

Пульт супервайзера: список активных звонков компании прямо сейчас.

GET/v1/telephony/console/presence

Пульт супервайзера: присутствие/статус операторов телефонии.

GET/v1/telephony/console/wallboard

Пульт супервайзера: сводные KPI «прямо сейчас» (wallboard).

GET/v1/telephony/stats

Историческая статистика звонков за период (today/7d/30d/custom) + ряды для графиков.

POST/v1/telephony/console/calls/{call}/listen

Супервайзер: тихо прослушать отвеченный звонок.

POST/v1/telephony/console/calls/{call}/whisper

Супервайзер: суфлировать оператору на отвеченном звонке (клиент не слышит).

POST/v1/telephony/console/calls/{call}/barge

Супервайзер: включиться в отвеченный звонок (слышно обеим сторонам).

POST/v1/telephony/console/calls/{call}/transfer

Супервайзер: перевести звонок другому оператору компании (target_user_id).

Примеры

Исходящий WhatsApp-звонок

Запрос

bash
curl -X POST https://api.aisar.app/v1/calls/originate \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "contact_id": 42,
    "channel_id": 7
  }'

Ответ

json
{
  "data": {
    "call": {
      "id": 5001,
      "direction": "outbound",
      "status": "ringing",
      "to_number": "7700000XXXX",
      "from_number": "7708550XXXX",
      "asterisk_channel_id": "1720787000.4521",
      "timeline_message_id": 88214
    }
  }
}

Исходящий звонок через SIP-транк

Запрос

bash
curl -X POST https://api.aisar.app/v1/calls/originate \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "contact_id": 42,
    "channel_id": 12
  }'

Ответ

json
{
  "data": {
    "call": {
      "id": 5002,
      "direction": "outbound",
      "status": "ringing",
      "to_number": "7700000XXXX",
      "from_number": "7727000XXXX",
      "asterisk_channel_id": "1720787100.4530",
      "timeline_message_id": 88221
    }
  }
}

WhatsApp-звонок без разрешения контакта (422)

Запрос

bash
curl -X POST https://api.aisar.app/v1/calls/originate \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "contact_id": 42,
    "channel_id": 7
  }'

Ответ

json
{
  "data": {
    "message": "No valid call permission or daily attempts exhausted.",
    "eligibility": {
      "message": {
        "window_open": true,
        "window_expires_at": 1720800000,
        "requires_template": false
      },
      "call": {
        "channel_kind": "whatsapp",
        "can_call": false,
        "permission_status": "none",
        "permission_expires_at": null,
        "can_request_permission": true,
        "request_cooldown_until": null,
        "attempts_remaining": 5,
        "has_whatsapp_account": true
      }
    }
  }
}

Завершение активного звонка

Запрос

bash
curl -X POST https://api.aisar.app/v1/calls/5001/hangup \
  -H "Authorization: Bearer YOUR_API_TOKEN"

Ответ

json
{
  "message": "Hangup requested."
}

Телефония недоступна для аккаунта (403)

Запрос

bash
curl -X POST https://api.aisar.app/v1/calls/originate \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "contact_id": 42,
    "channel_id": 7
  }'

Ответ

json
{
  "data": {
    "message": "Telephony is not available for your account."
  }
}

Журнал звонков

Запрос

bash
curl "https://api.aisar.app/v1/calls?filter=missed&perPage=20" \
  -H "Authorization: Bearer YOUR_API_TOKEN"

Ответ

json
{
  "data": {
    "items": [
      {
        "id": 5001,
        "direction": "inbound",
        "status": "missed",
        "provider": "whatsapp",
        "from_number": "7700000XXXX",
        "to_number": "7708550XXXX",
        "started_at": "2026-07-11T09:12:00+00:00",
        "answered_at": null,
        "ended_at": "2026-07-11T09:12:24+00:00",
        "duration_sec": 0,
        "contact": {
          "id": 42,
          "display_name": "Aigerim K.",
          "avatar_url": null
        },
        "channel_id": 7,
        "channel_name": "WhatsApp — Sales",
        "channel_type": "whatsapp_business",
        "conversation_id": 913,
        "timeline_message_id": 88214,
        "recording_status": null,
        "recording_url": null,
        "has_recording": false,
        "has_transcript": false,
        "assignee": null
      }
    ],
    "pagination": {
      "page": 1,
      "perPage": 20,
      "total": 1,
      "lastPage": 1
    }
  }
}

Архив записей: фильтр + сводка + без предподписи

Запрос

bash
curl "https://api.aisar.app/v1/calls?has_recording=1&date_from=2026-08-01&date_to=2026-08-19&with_summary=1&lazy_recordings=1&perPage=20" \
  -H "Authorization: Bearer YOUR_API_TOKEN"

Ответ

json
{
  "data": {
    "items": [
      {
        "id": 5010,
        "direction": "inbound",
        "status": "completed",
        "provider": "sip",
        "from_number": "7727000XXXX",
        "to_number": "7008550XXXX",
        "started_at": "2026-08-19T09:14:00+00:00",
        "answered_at": "2026-08-19T09:14:01+00:00",
        "ended_at": "2026-08-19T09:14:52+00:00",
        "duration_sec": 51,
        "contact": {
          "id": 118,
          "display_name": "Nurlan T.",
          "avatar_url": null
        },
        "channel_id": 12,
        "channel_name": "SIP — Support",
        "channel_type": "sip_telephony",
        "conversation_id": 921,
        "timeline_message_id": 88250,
        "recording_status": "stored",
        "recording_url": null,
        "has_recording": true,
        "has_transcript": true,
        "assignee": {
          "id": 9,
          "name": "Данияр О."
        }
      }
    ],
    "pagination": {
      "page": 1,
      "perPage": 20,
      "total": 1,
      "lastPage": 1
    },
    "summary": {
      "count": 1,
      "total_duration_sec": 51
    }
  }
}

Счётчики звонков

Запрос

bash
curl https://api.aisar.app/v1/calls/counters \
  -H "Authorization: Bearer YOUR_API_TOKEN"

Ответ

json
{
  "data": {
    "all": 128,
    "missed": 6
  }
}

Расшифровка звонка (ИИ перевёл на оператора)

Запрос

bash
curl https://api.aisar.app/v1/calls/5010/transcript \
  -H "Authorization: Bearer YOUR_API_TOKEN"

Ответ

json
{
  "data": {
    "voiceCallId": "01927e6a-2b3f-7c91-8e0a-4f6b8b1d9a02",
    "callId": 5010,
    "status": "completed",
    "outcome": "resolved",
    "summary": "Клиент попросил соединить с оператором для уточнения деталей заказа.",
    "transcript": [
      {
        "role": "user",
        "text": "Переключите на человека",
        "at_sec": 40,
        "tool_calls": []
      },
      {
        "role": "agent",
        "text": "",
        "at_sec": 47,
        "tool_calls": [
          {
            "type": "webhook",
            "tool_name": "voice_transfer_to_human",
            "request_id": "toolu_8f2a1c9d4e",
            "params_as_json": "{\"reason\":\"client asked\"}",
            "tool_has_been_called": true,
            "result": { "ok": true, "message": "Escalated." },
            "is_error": false,
            "error": null,
            "latency_sec": 0.979
          }
        ]
      },
      {
        "role": "agent",
        "text": "Соединяю со специалистом.",
        "at_sec": 49,
        "tool_calls": []
      }
    ],
    "startedAt": "2026-08-19T09:14:00+00:00",
    "answeredAt": "2026-08-19T09:14:01+00:00",
    "endedAt": "2026-08-19T09:14:52+00:00"
  }
}

У звонка нет голосовой сессии (404)

Запрос

bash
curl https://api.aisar.app/v1/calls/5002/transcript \
  -H "Authorization: Bearer YOUR_API_TOKEN"

Ответ

json
{
  "data": {
    "message": "No voice session for this call."
  }
}

Ссылка на запись звонка

Запрос

bash
curl "https://api.aisar.app/v1/calls/5010/recording-url?via=download" \
  -H "Authorization: Bearer YOUR_API_TOKEN"

Ответ

json
{
  "data": {
    "recording_url": "https://api.aisar.app/v1/messages/88250/attachments/4021/media?disposition=attachment&expires=1755651600&signature=8f2a1c9d4e6b7f0a3c5d8e1f2a4b6c9d0e3f5a7b8c1d2e4f6a9b0c3d5e7f8a1"
  }
}

Для звонка нет записи (404)

Запрос

bash
curl https://api.aisar.app/v1/calls/5002/recording-url \
  -H "Authorization: Bearer YOUR_API_TOKEN"

Ответ

json
{
  "data": {
    "message": "No recording for this call."
  }
}