Команды и пользователи
Управление командами (группировка операторов + доступ к каналам), участниками компании и приглашениями новых пользователей по email. Группа также включает блок текущего пользователя (GET /me): профиль с ролями и правами в активной компании, статус доступности и переключение активной компании. GET /v1/users перечисляет реальных (не сервисных) пользователей компании. Отдельный блок — провижининг SIP-телефонии операторам (companies/current/members/telephony*, админский гейт company_telephony.manage) и самообслуживание оператора (/v1/softphone/*, только сессия/мобильный токен). GET /v1/bootstrap собирает стартовый пакет кабинета (профиль, подписку, конфиги, непрочитанные) одним запросом вместо девяти.
Роли и членство
Роль назначается на уровне компании (admin/manager/agent) через Spatie permissions с teams-режимом — один пользователь может состоять в нескольких компаниях с разными ролями.
POST /v1/companies/current/members/{memberId}/role и .../revoke работают только с людьми: сервисный аккаунт API-токена (см. группу «API-токены») этими эндпоинтами не понижается и не отзывается — для него ответ 404 с тем же телом "User not found.", что и для несуществующего пользователя. Отключить интеграцию можно только удалением её API-токена (DELETE /v1/api-tokens/{id}). Смена роли отправляет вебхук user.role_changed с source: "company" — только если роль действительно изменилась: повторное назначение той же роли события не создаёт.
Команды
Команда группирует операторов и открывает им доступ к набору каналов (см. «Права доступа к каналу» в группе «Каналы»). Когда для платформы включены командные ограничения доступа к каналам (см. там же), менять СОСТАВ команды (member_ids) и её КАНАЛЫ (channel_access_mode/channel_ids) на POST/PATCH /v1/teams может только администратор компании или сотрудник платформы — иначе 403 {"message": "...", "code": "channel_access_admin_only"}, и запрос откатывается целиком. Решение принимается по фактическому ЭФФЕКТУ запроса, а не по тому, какие ключи в нём присутствуют: переименование команды (name/description) без изменения состава или каналов проходит у любого пользователя с правом team.update, даже если тело запроса заново отправляет прежние channel_ids — форма кабинета обычно отправляет их всегда, и это не расценивается как попытка их сменить, пока итоговый список не отличается от текущего.
Сохранение команды (channel_ids) принимает как живые, так и архивные каналы компании — правило команды на архивном канале не теряется при обычном сохранении (например при переименовании, если форма пересылает список каналов целиком), даже несмотря на то, что доступ к архивному каналу в интерфейсе обычно не редактируется напрямую. Отдельно от этого, канал, ожидающий окончательного удаления (DELETE /channels/archived/{channelId} уже поставил его в очередь), НЕ принимается как новое значение channel_ids и не удаляется из уже сохранённого списка правил команды — он просто исключён из проверки: такое правило исчезнет само, вместе с каналом, когда очередь на удаление завершится.
DELETE /v1/teams/{team} тоже подчиняется тому же правилу: удаление команды снимает её правила доступа к каналам, поэтому оно требует администратора/сотрудника платформы, только если у команды действительно есть хотя бы одно правило доступа (channel_access) — команду без единого правила может удалить любой держатель права team.delete, при включённых ограничениях в том числе. Отдельно от прав на команды: бывший сотрудник автоматически исключается из ВСЕХ команд компании в момент, когда он теряет в ней роль (увольнение, отзыв приглашения, выход) — держать список членов команды в актуальном состоянии вручную не требуется.
Приглашения
Приглашение живёт 30 дней (expires_at). Статус приглашения принимает значения pending, accepted, declined, expired и revoked, и наружу он всегда вычисляется с учётом срока: приглашение с прошедшим expires_at приходит как expired, а не как pending. Перевод записи в конечный статус выполняет фоновая уборка (каждые 15 минут), но ответ на неё не рассчитывает — просроченное приглашение отдаётся как expired и до её прохода. Поэтому сравнивать expires_at с текущим временем на своей стороне не нужно: pending в ответе означает, что приглашение действительно ещё можно принять.
Тот же признак приходит в поле invitation_status каждой строки GET /v1/companies/current/members: accepted — у уже присоединившегося участника, у строки-приглашения — её статус с учётом срока.
Если в POST /v1/invitations указан email уже действующего участника компании, запрос отклоняется с 422 и errors.email, error: "member_already_exists" — приглашение не создаётся и место в лимите пользователей не расходуется.
Повторное приглашение того же email, пока предыдущее ещё активно (pending, срок не истёк), не создаёт вторую запись: обновляется та же запись — новый guid (прежняя ссылка перестаёт работать), новая роль и новый срок действия. Проверка лимита пользователей в этом случае не выполняется — обновление действующего приглашения места не расходует.
Гонка из двух одновременных запросов на приглашение одного email в одной компании не создаёт дубль и не отдаёт 500: проигравший запрос получает тот же результат, что и повторное приглашение выше — то же приглашение с новым guid, запрошенными ролью и сроком действия.
Публичные шаги разрешения приглашения (resolve/context/intent, без auth) в этот справочник не включены — см. руководство по приглашениям.
Текущий пользователь
Блок текущего пользователя (GET /me) отдаёт профиль вместе с ролями и правами в активной компании — это отправная точка для потребителей сервисных токенов; сопутствующие эндпоинты обновляют профиль, статус доступности и активную компанию (при переключении компании профиль возвращает роли и права уже новой компании).
С 2026-09-26 channel_ids в GET /me — только каналы компании (кроме черновиков), доступные пользователю по правилам channel_access; администратор по-прежнему получает все.
С 2026-09-30 под embed-токеном (привязан к одной компании ability company:{id} — см. группу «API-токены» и партнёрскую интеграционную документацию) GET /me сужен до этой компании: memberships содержит ровно одну запись — привязанную компанию, pending_invitations всегда [] (их guid — предъявительский секрет на вступление в ДРУГУЮ компанию), has_partner_access всегда false. Раскрывать партнёрскому origin, в каких ещё компаниях состоит оператор, нет причины — токен и так не может работать нигде, кроме привязанной компании. Для сессии кабинета, мобильного токена и обычных (*-ability) сервисных токенов ответ не меняется.
С 30.09.2026 GET /me (и профиль в ответе PUT /v1/me/current-company — та же форма) несёт булево поле can_edit_channel_access: может ли ТЕКУЩИЙ пользователь прямо сейчас менять правила доступа к каналам (.../access) и состав/список каналов команд в активной компании (см. «Права доступа к каналу» в группе «Каналы» и «Команды» ниже). Пока командные ограничения доступа выключены для платформы (текущее поведение по умолчанию), поле всегда true; когда включены — true только у администратора компании и сотрудника платформы, у остальных — false. Само поле ничего не резервирует и не гарантирует: обычные права (channel.update для доступа к каналу, team.* для команд) по-прежнему нужны и проверяются отдельно — кабинет использует can_edit_channel_access вместе с ними, чтобы решить, показывать ли элементы управления, а не вместо них.
Статус доступности (PUT /v1/me/availability) принимает ровно три значения на запись: online, away, offline (busy — системное состояние, через этот эндпоинт не выставляется).
С 2026-09-23 photo в POST /v1/me больше не свободная строка: значение должно быть либо абсолютной http(s)-ссылкой, либо путём вида {current_company_id}/users/... — любое другое значение отклоняется с 422. Подписанную ссылку на фото, которую сама же выдала в GET /v1/me, можно прислать обратно без изменений — это читается как «фото не менялось», и такое значение просто не сохраняется заново.
Активная компания
Активная компания хранится в едином серверном поле current_company_id вашего аккаунта: оно общее для всех ваших сессий и API-токенов (не привязано к отдельному токену), и по нему скоупятся company-ориентированные списки (например, GET /v1/conversations) и авторизация realtime-каналов.
Поэтому если тот же пользователь переключит компанию в другой вкладке, приложении или токене, за ним последуют все контексты — встроенный чат может внезапно показать диалоги другой компании, а подписка на каналы прежней (уже не активной) компании начнёт отвечать 403. Держите компанию, из которой читаете и на каналы которой подписаны, синхронной с current_company_id.
Настройки уведомлений
GET /v1/me/notification-preferences отдаёт снимок из четырёх блоков: channels — булевы мастер-переключатели по каналу доставки (mail, push, whatsapp; отсутствующий ключ трактуется как true), categories — объект по каждой категории уведомлений с полями enabled/label/controllable, а также push_triggers и quiet_hours (см. ниже). Категория transactional (коды подтверждения, приглашения, сброс пароля) всегда controllable: false и enabled: true — её нельзя отключить; остальные категории (onboarding, trial, billing, win_back, engagement) пользователь может выключать по отдельности.
PUT /v1/me/notification-preferences — частичное обновление (merge, не replace): передавайте только те ключи channels.*/categories.*/push_triggers.*/quiet_hours.*, которые меняете — остальные ключи, включая другие блоки целиком, сохраняют текущее значение. channels.whatsapp = false — единственный способ для пользователя отказаться от WhatsApp-канала биллинговых уведомлений (напоминания об оплате, приостановке и т.п.) — этот отказ обязателен политикой Meta для WhatsApp-шаблонов и должен уважаться отправителем.
Оба эндпоинта также несут push_triggers — объект булевых переключателей по типу push-уведомления: chat_message_new, conversation_assigned, call_missed, call_incoming, company_unassigned. По умолчанию включены все, кроме company_unassigned (пуш о том, что диалог в компании остался без ответственного — опционален, чтобы не заваливать им сразу всех операторов). call_incoming фактически всегда доставляется независимо от сохранённого значения — входящий звонок это системное уведомление, которое не подавляется.
quiet_hours — окно «не беспокоить»: { "enabled": bool, "start": "HH:MM"|null, "end": "HH:MM"|null }, по умолчанию enabled: false и start/end — null. Пока окно активно, некритичные push-уведомления (например, conversation_assigned) подавляются — кроме call_incoming, который тихие часы не затрагивают. Время трактуется в часовом поясе пользователя (users.timezone); если start больше end, окно считается ночным (переходит через полночь, например 22:00–08:00).
Подтверждение номера телефона
Номер из профиля (users.phone) можно подтвердить кодом, который приходит в WhatsApp: POST /v1/me/phone/send-otp отправляет 6-значный код, POST /v1/me/phone/verify с телом {"code": "123456"} его проверяет. Подтверждённый номер отражается полем phone_verified в GET /v1/me и служит адресом для WhatsApp-дубликатов биллинговых уведомлений. Изменение номера через POST /v1/me сбрасывает подтверждение.
send-otp не принимает номер в теле запроса — код всегда уходит на сохранённый номер профиля, поэтому сначала сохраните номер, а затем запрашивайте код. Ответ 200 содержит маскированный phone и expires_in (секунды жизни кода, сейчас 600). Возможные ошибки: 422 — номер не задан либо не проходит проверку формата E.164; 429 — превышены лимиты отправки/проверки; 409 (только у verify) — номер изменился между отправкой и проверкой, запросите код заново; 502 — сообщение не удалось доставить; 503 — исчерпан суточный лимит отправки кодов на платформе.
Оба эндпоинта доступны только сессионной авторизации и мобильным токенам. Сервисные аккаунты, клиентские API-токены и токены встраивания получают 403 — подтверждение номера остаётся действием живого пользователя.
Телефония операторов
Провижининг SIP-телефонии оператору — шаг, следующий за подключением SIP-канала (см. руководство по подключению каналов, раздел «SIP-телефония (BYO-транк)»). Администратор компании (право company_telephony.manage) включает телефонию конкретному участнику через POST /v1/companies/current/members/{memberId}/telephony/enable, что провижинит СРАЗУ два SIP-профиля: браузерный agent-{id}-web (для встроенного WebRTC-софтфона в приложениях AISAR, пароль непрозрачный) и внешний agent-{id} (для настольных SIP-телефонов и приложений вроде Zoiper/MicroSIP/Linphone, пароль типизируемый). Отдельного тумблера для внешнего профиля нет — disable снимает оба профиля разом.
Административные эндпоинты (companies/current/members/telephony*) вызываются company api_token наравне с остальной группой — партнёрский сервис-аккаунт с ролью admin (которая безусловно несёт company_telephony.manage, см. ниже) может провижинить операторов программно. Самообслуживание (/v1/softphone/*) — обратная ситуация: эти эндпоинты требуют, чтобы вызывающий БЫЛ тем самым оператором (сессионная авторизация или мобильный токен — тот же паттерн, что у подтверждения номера телефона выше), поэтому company api_token / сервисный аккаунт получает здесь 403 — своего SIP-профиля у сервисного аккаунта никогда нет (провижининг участников сознательно фильтрует is_service_account = false). Партнёрам доступен также server-to-server способ включения телефонии операторам без пользовательской сессии — он описан в партнёрской интеграционной документации.
company_telephony.manage не тарифный гейт: с 13.08.2026 роль admin в ЛЮБОЙ компании безусловно несёт это право (бэкфилл-миграция закрыла разрыв для компаний, заведённых раньше). Тарифный гейт живёт отдельно — фича telephony плана нужна оператору, чтобы САМОМУ пользоваться звонками (TelephonyAccess::availableTo), но не нужна администратору, чтобы управлять операторами, схемами и каналами.
Обе точки получения кредов внешнего профиля — админская GET .../telephony/credentials и операторская GET /v1/softphone/external-credentials — используют один и тот же построитель (ExternalSipCredentialPresenter), поэтому форма ответа не может разойтись: sip_username, sip_password, host, tcp_port (5060), tls_port (5061), default_transport: "TCP", codecs: ["opus", "PCMU", "PCMA"], media_encryption, registered (жива ли SIP-регистрация прямо сейчас — best-effort проверка через ARA, при ошибке трактуется как false) и provisioning_uri — generic sip:-URI (sip:user:pass@host:port;transport=tcp) для импорта по QR в Zoiper / MicroSIP / Linphone / Grandstream Wave. Не подходит для 3CX — его приложение читает только собственный формат провижининг-QR, там поля вводятся вручную. Браузерный (agent-{id}-web) пароль непрозрачный: GET .../telephony/credentials (админ) его вообще не возвращает — эндпоинт трогает только внешний профиль; а вот POST .../telephony/reset-password (админ), наоборот, ЕГО возвращает — ответ несёт свежую пару sip_username/sip_password браузерного профиля (тот же принцип одноразового показа, что у создания API-токена), и заодно молча ротирует внешний профиль, не отдавая его новый пароль в этом же ответе — свой новый внешний пароль оператор увидит через собственный /softphone/external-credentials.
GET /v1/companies/current/members/telephony отдаёт по строке на каждого реального (не сервисного) участника компании: user_id, name, email, photo, telephony_enabled (активен ли браузерный профиль), browser_registered, extension, external_enabled (активен ли внешний профиль) и external_registered. Пара *_registered — это живая SIP-регистрация прямо сейчас, отдельно для браузерного софтфона (browser_registered) и для настольного телефона / внешнего SIP-приложения (external_registered); оба флага снимаются одним batched-запросом к ARA, и при недоступности ARA оба трактуются как false — список операторов из-за этого не падает. Это булев признак «сейчас или нет»: история регистраций не хранится, поэтому «не подключён с 09:40» из ответа не выводится. Профиль встраиваемого софтфона (embed) в этих флагах не учитывается — это софтфон в iframe партнёрской панели, а не устройство оператора компании.
С 2026-10-20 каждая строка GET /v1/companies/current/members/telephony несёт блок outbound — с чего сотрудник звонит: { "schemes": [ { "id", "name", "via", "channel_ids" } ], "primary_scheme_id": string|null, "primary_source": string|null, "falls_back_to": string|null }. schemes — исходящие схемы, в которых сотрудник состоит (via — лично или через команду; channel_ids — линии схемы); primary_scheme_id и primary_source — какая схема основная и как это определено: explicit (выбрана админом), implicit (выбрана автоматически по прежнему порядку: личная раньше командной) или single (единственная схема); null, если схем нет; falls_back_to заполнен только у сотрудника без схем: company_default (общий номер), legacy (любая доступная линия) или company_default_unavailable (общий номер задан, но недоступен — звонки сотрудника уходят в отказ). Эндпоинты .../telephony* этой группы закрыты для embed-токенов: список теперь называет схемы.
PUT /v1/companies/current/members/{memberId}/telephony/outbound-primary делает одну из схем сотрудника основной: тело { "scheme_id": "uuid" | null } (поле обязательно; null снимает явный выбор и возвращает автоматический). Схема должна быть исходящей, активной, своей компании, а сотрудник — уже в ней состоять; эндпоинт никого в схему не добавляет. Ответ — строка сотрудника в том же виде, что в списке выше (с обновлённым блоком outbound). Отказы 422: scheme_not_found (чужая, черновая или несуществующая схема — без различий) и not_a_member_of_scheme. POST /v1/companies/current/members/telephony/outbound-primary/bulk делает одну схему основной сразу многим: тело { "user_ids": [int, ...], "scheme_id": "uuid" } (от 1 до 500 id; поле target необязательно и допускает только scheme; отдельного массового действия «свой прямой номер» нет). Ответ: { "data": { "updated": [user_id, ...], "skipped": [ { "user_id", "reason": "not_in_scheme" } ] } }. Один id постороннего человека отклоняет весь запрос целиком (422), ничего не записывается. Оба эндпоинта требуют права company_telephony.manage и полноправного токена, недоступны embed-токенам, ограничены лимитером telephony-outbound-primary и пишут аудит (telephony.outbound_primary.set / .bulk_set) и WebSocket-событие telephony.outbound.updated.
POST .../telephony/extension — внутренний номер оператора, 3–4 цифры (^\d{3,4}$), уникален в пределах компании; пустое значение снимает номер. Требует, чтобы телефония уже была включена — иначе 422.
Самообслуживание оператора (только сессия/мобильный токен): GET /v1/softphone/credentials — собственные браузерные креды (используются встроенным WebRTC-софтфоном напрямую, не для ручного ввода); POST /v1/softphone/reset-password — их ротация; GET /v1/softphone/external-credentials / POST /v1/softphone/external/reset-password — то же для внешнего профиля, включая provisioning_uri для QR; GET /v1/softphone/colleagues — телефонные коллеги по компании (директория набора) с extension и статусом доступности; POST /v1/softphone/internal-call — звонок коллеге по callee_user_id либо внутреннему номеру; POST /v1/softphone/external-call — исходящий звонок через SIP-транк компании на внешний номер, с опциональным contact_id (резолвится строго в рамках компании — чужой/несуществующий id это 422 без молчаливого фолбэка на резолв по номеру). external-call разделяет с POST /v1/calls/originate общий лимит call-originate — 20 запросов в минуту на пользователя; те же ограничения по sip_outbound_gated и по антизлоупотребительной проверке IP-режима, что и у /calls/originate, применяются и здесь (см. группу «Звонки»).
Bootstrap оболочки кабинета
GET /v1/bootstrap — один запрос вместо девяти отдельных, которые кабинет (my.aisar.app) делает при открытии. Части: me (GET /v1/me), subscription (GET /v1/subscription), usage (GET /v1/subscription/usage), plans (GET /v1/plans), subscription_config (GET /v1/subscription/config), bank_transfer_config (GET /v1/billing/bank-transfer/config), nudges (GET /v1/nudges), ai_pending_actions (GET /v1/ai-pending-actions), chat_behavior (GET /v1/companies/current/chat-behavior), notifications_unread_count (GET /v1/notifications/unread-count) и tasks_counters (GET /v1/deal-tasks/counters, с 2026-09-26; у компании без фичи сделок — null и статус отказа). По умолчанию отдаются все одиннадцать частей; ?parts=a,b,c запрашивает подмножество — неизвестное имя части или повтор в списке отклоняются 422.
Каждая часть строится тем же контроллером, тем же FormRequest и той же маршрутной middleware, что и её отдельный эндпоинт, — форма и права части не описываются заново и не могут разойтись с источником (тот же гейт подписки, тот же гейт типа компании, те же права). Ответ: { "data": { "parts": {...}, "status": {...}, "errors": {...} } }. parts.<name> — тело части, развёрнутое точно так же, как это делает клиент кабинета (берётся data ответа, если оно есть, иначе тело целиком), либо null, если часть не удалась. status.<name> — HTTP-статус, который вернул бы отдельный эндпоинт. errors.<name> присутствует только для 4xx (то же JSON-тело, что дал бы отдельный эндпоинт — отказ по правам, по типу компании, по подписке и т. п.); при 5xx записи в errors нет, parts.<name> всё равно null, а инцидент логируется на сервере — в этом случае клиенту нужно обратиться к отдельному эндпоинту части напрямую. Один упавший блок не валит остальные.
Доступ — только сессия кабинета (cookie) или токен с правом * (сервисные аккаунты, включая обычный company api_token, и мобильное приложение). Токен, ограниченный allowlist'ом путей (embed:*, connect-session:*), получает 403 независимо от того, что разрешил бы allowlist каждой части по отдельности — у встроенного чата/софтфона и у визарда подключения канала нет причин запрашивать стартовый пакет кабинета целиком. Без авторизации — 401. Отдельный лимитер shell-bootstrap — 30 запросов в минуту на пользователя (сессионные вызовы по cookie не покрываются общим лимитом bearer-токенов, а один вызов строит работу десяти эндпоинтов).
Эндпоинты
| Метод | Путь | Описание |
|---|---|---|
| GET | /v1/teamsСписок команд | Список команд |
| POST | /v1/teamsСоздание команды | Создание команды |
| GET | /v1/teams/{team}Получение команды | Получение команды |
| PATCH | /v1/teams/{team}Обновление команды | Обновление команды |
| DELETE | /v1/teams/{team}Удаление команды | Удаление команды |
| GET | /v1/companies/current/membersСписок участников текущей компании | Список участников текущей компании |
| POST | /v1/companies/current/members/{memberId}/roleСмена роли участника | Смена роли участника |
| POST | /v1/companies/current/members/{memberId}/revokeОтзыв членства участника | Отзыв членства участника |
| GET | /v1/usersСписок пользователей текущей компании (без сервисных аккаунтов) | Список пользователей текущей компании (без сервисных аккаунтов) |
| POST | /v1/invitationsОтправка приглашения (email + роль); не более 6 в минуту на пользователя, сверх этого 429 | Отправка приглашения (email + роль); не более 6 в минуту на пользователя, сверх этого 429 |
| POST | /v1/invitations/{guid}/acceptПринятие приглашения | Принятие приглашения |
| POST | /v1/invitations/{guid}/declineОтклонение приглашения | Отклонение приглашения |
| POST | /v1/invitations/{invitationId}/roleСмена роли в приглашении | Смена роли в приглашении |
| POST | /v1/invitations/{invitationId}/revokeОтзыв приглашения | Отзыв приглашения |
| DELETE | /v1/invitations/{invitationId}Удаление приглашения | Удаление приглашения |
| GET | /v1/bootstrapОболочка кабинета одним запросом: me, subscription, usage, plans, subscription_config, bank_transfer_config, nudges, ai_pending_actions, chat_behavior, notifications_unread_count. ?parts=a,b — подмножество; неизвестная/повторённая часть — 422. Только сессия кабинета или токен с правом *; лимит 30/мин. | Оболочка кабинета одним запросом: me, subscription, usage, plans, subscription_config, bank_transfer_config, nudges, ai_pending_actions, chat_behavior, notifications_unread_count. ?parts=a,b — подмножество; неизвестная/повторённая часть — 422. Только сессия кабинета или токен с правом *; лимит 30/мин. |
| GET | /v1/meТекущий профиль пользователя (роли и права) | Текущий профиль пользователя (роли и права) |
| POST | /v1/meОбновление профиля | Обновление профиля |
| POST | /v1/me/passwordСмена пароля | Смена пароля |
| PUT | /v1/me/availabilityУстановить статус доступности (online, away, offline) | Установить статус доступности (online, away, offline) |
| PUT | /v1/me/current-companyПереключение активной компании (единое поле для всех сессий/токенов) | Переключение активной компании (единое поле для всех сессий/токенов) |
| GET | /v1/me/notification-preferencesСнимок настроек уведомлений: каналы (mail/push/whatsapp) + категории + push-триггеры (push_triggers) + тихие часы (quiet_hours) | Снимок настроек уведомлений: каналы (mail/push/whatsapp) + категории + push-триггеры (push_triggers) + тихие часы (quiet_hours) |
| PUT | /v1/me/notification-preferencesЧастичное обновление настроек (channels.mail/push/whatsapp, categories.*, push_triggers.*, quiet_hours) | Частичное обновление настроек (channels.mail/push/whatsapp, categories.*, push_triggers.*, quiet_hours) |
| POST | /v1/me/phone/send-otpОтправка кода подтверждения в WhatsApp на сохранённый номер профиля | Отправка кода подтверждения в WhatsApp на сохранённый номер профиля |
| POST | /v1/me/phone/verifyПроверка кода и подтверждение номера профиля | Проверка кода и подтверждение номера профиля |
| GET | /v1/companies/current/members/telephonyСписок реальных участников компании с телефонным статусом (включена ли, extension, зарегистрирован ли прямо сейчас браузерный софтфон и настольный телефон) — для админ-таблицы операторов. | Список реальных участников компании с телефонным статусом (включена ли, extension, зарегистрирован ли прямо сейчас браузерный софтфон и настольный телефон) — для админ-таблицы операторов. |
| POST | /v1/companies/current/members/{memberId}/telephony/enableВключить телефонию участнику: провижинит браузерный (agent-{id}-web) и внешний (agent-{id}) SIP-профили одновременно. | Включить телефонию участнику: провижинит браузерный (agent-{id}-web) и внешний (agent-{id}) SIP-профили одновременно. |
| POST | /v1/companies/current/members/{memberId}/telephony/disableОтключить телефонию участнику: снимает оба SIP-профиля. | Отключить телефонию участнику: снимает оба SIP-профиля. |
| GET | /v1/companies/current/members/{memberId}/telephony/credentialsКреды внешнего SIP-профиля участника для админа (без входа под ним) — host/порты/кодеки/provisioning_uri; браузерный пароль не отдаётся. | Креды внешнего SIP-профиля участника для админа (без входа под ним) — host/порты/кодеки/provisioning_uri; браузерный пароль не отдаётся. |
| POST | /v1/companies/current/members/{memberId}/telephony/reset-passwordРотация SIP-паролей участника: возвращает новый пароль браузерного профиля и заодно молча ротирует внешний. | Ротация SIP-паролей участника: возвращает новый пароль браузерного профиля и заодно молча ротирует внешний. |
| POST | /v1/companies/current/members/{memberId}/telephony/extensionНазначить/снять внутренний номер участника (3–4 цифры, уникален в компании); требует включённой телефонии. | Назначить/снять внутренний номер участника (3–4 цифры, уникален в компании); требует включённой телефонии. |
| PUT | /v1/companies/current/members/{memberId}/telephony/outbound-primaryСделать исходящую схему основной для сотрудника (`scheme_id` или `null` для автоматического выбора); сотрудник должен уже состоять в схеме. | Сделать исходящую схему основной для сотрудника (`scheme_id` или `null` для автоматического выбора); сотрудник должен уже состоять в схеме. |
| POST | /v1/companies/current/members/telephony/outbound-primary/bulkСделать одну исходящую схему основной сразу для списка сотрудников (до 500); не состоящие в схеме пропускаются с `not_in_scheme`. | Сделать одну исходящую схему основной сразу для списка сотрудников (до 500); не состоящие в схеме пропускаются с `not_in_scheme`. |
| GET | /v1/softphone/credentialsСобственные креды браузерного SIP-профиля вызывающего оператора. Требует сессии/мобильного токена — company api_token получает 403. | Собственные креды браузерного SIP-профиля вызывающего оператора. Требует сессии/мобильного токена — company api_token получает 403. |
| POST | /v1/softphone/reset-passwordРотация собственного браузерного SIP-пароля. | Ротация собственного браузерного SIP-пароля. |
| GET | /v1/softphone/external-credentialsСобственные креды внешнего SIP-профиля (для настольного телефона/Zoiper/MicroSIP) с provisioning_uri для QR. | Собственные креды внешнего SIP-профиля (для настольного телефона/Zoiper/MicroSIP) с provisioning_uri для QR. |
| POST | /v1/softphone/external/reset-passwordРотация собственного внешнего SIP-пароля; заодно ротирует и браузерный. | Ротация собственного внешнего SIP-пароля; заодно ротирует и браузерный. |
| GET | /v1/softphone/colleaguesТелефонные коллеги по компании (директория набора) с extension и статусом доступности. | Телефонные коллеги по компании (директория набора) с extension и статусом доступности. |
| POST | /v1/softphone/internal-callВнутренний звонок коллеге по callee_user_id или внутреннему номеру. | Внутренний звонок коллеге по callee_user_id или внутреннему номеру. |
| POST | /v1/softphone/external-callИсходящий звонок на внешний номер через SIP-транк компании; опциональный contact_id (строго company-scoped); лимит call-originate — 20/мин. | Исходящий звонок на внешний номер через SIP-транк компании; опциональный contact_id (строго company-scoped); лимит call-originate — 20/мин. |
Примеры
Стартовая загрузка кабинета (подмножество частей)
Запрос
curl "https://api.aisar.app/v1/bootstrap?parts=subscription,chat_behavior" \
-H "Authorization: Bearer YOUR_API_TOKEN"Ответ
{
"data": {
"parts": {
"subscription": {
"id": 42,
"plan": "business",
"status": "active"
},
"chat_behavior": null
},
"status": {
"subscription": 200,
"chat_behavior": 403
},
"errors": {
"chat_behavior": {
"message": "This action is unauthorized."
}
}
}
}Приглашение нового пользователя
Запрос
curl -X POST https://api.aisar.app/v1/invitations \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"email": "new.agent@example.com",
"role": "agent"
}'Ответ
{
"data": {
"message": "Invitation sent.",
"invitation": {
"id": 77,
"guid": "b6e2a4d0-1234-4a90-9e21-5d3f0c8a9b11",
"invite_url": "https://my.aisar.app/invitation/b6e2a4d0-1234-4a90-9e21-5d3f0c8a9b11",
"email": "new.agent@example.com",
"role": "agent",
"expires_at": "2026-03-22T10:00:00.000000Z"
}
}
}Создание команды
Запрос
curl -X POST https://api.aisar.app/v1/teams \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Support",
"description": "Customer success team",
"channel_access_mode": "custom",
"channel_ids": [7, 9],
"member_ids": [5, 6]
}'Ответ
{
"data": {
"message": "Team created.",
"team": {
"id": 3,
"company_id": 1,
"name": "Support",
"description": "Customer success team",
"channel_access": { "mode": "custom", "channel_ids": [7, 9] },
"member_ids": [5, 6],
"created_at": "2026-03-15T10:00:00.000000Z",
"updated_at": "2026-03-15T10:00:00.000000Z"
}
}
}Смена состава/каналов команды разрешена только администратору (403)
Запрос
curl -X PATCH https://api.aisar.app/v1/teams/3 \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"member_ids": [5, 6, 9]
}'Ответ
{
"data": {
"message": "Only a company admin can change channel access or team membership while team access rules are enforced.",
"code": "channel_access_admin_only"
}
}Профиль и права текущего пользователя
Запрос
curl https://api.aisar.app/v1/me \
-H "Authorization: Bearer YOUR_API_TOKEN"Ответ
{
"data": {
"id": 12,
"name": "Aisha",
"lastname": "Nurlanova",
"email": "aisha@example.com",
"email_verified": true,
"phone": "+12025550102",
"phone_verified": true,
"photo": null,
"country_code": "KZ",
"language": "ru",
"timezone": "Asia/Almaty",
"current_company_id": 3,
"roles": ["admin"],
"permissions": ["company.view", "conversation.view", "message.send"],
"company": { "id": 3, "name": "Acme" },
"memberships": [
{ "company_id": 3, "company_name": "Acme", "roles": ["admin"], "status": "active" }
],
"can_edit_channel_access": true,
"pending_invitations": []
}
}Обновление профиля
Запрос
curl -X POST https://api.aisar.app/v1/me \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Aisha",
"lastname": "Nurlanova",
"phone": "+12025550102",
"country_code": "KZ",
"language": "ru",
"timezone": "Asia/Almaty"
}'Ответ
{
"data": {
"id": 12,
"name": "Aisha",
"lastname": "Nurlanova",
"email": "aisha@example.com",
"phone": "+12025550102",
"country_code": "KZ",
"language": "ru",
"timezone": "Asia/Almaty",
"current_company_id": 3
}
}Смена пароля
Запрос
curl -X POST https://api.aisar.app/v1/me/password \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"current_password": "YOUR_CURRENT_PASSWORD",
"password": "YOUR_NEW_PASSWORD",
"password_confirmation": "YOUR_NEW_PASSWORD"
}'Ответ
{
"data": {
"message": "Password updated."
}
}Обновление статуса доступности
Запрос
curl -X PUT https://api.aisar.app/v1/me/availability \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"availability": "away"
}'Ответ
{
"status": "ok"
}Переключение активной компании
Запрос
curl -X PUT https://api.aisar.app/v1/me/current-company \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"company_id": 4
}'Ответ
{
"data": {
"id": 12,
"current_company_id": 4,
"roles": ["manager"],
"permissions": ["company.view", "conversation.view"]
}
}Пользователи компании
Запрос
curl https://api.aisar.app/v1/users \
-H "Authorization: Bearer YOUR_API_TOKEN"Ответ
{
"data": [
{
"id": 12,
"name": "Aisha",
"lastname": "Nurlanova",
"email": "aisha@example.com",
"availability": "online",
"last_login_at": "2026-03-20T09:14:00.000000Z"
},
{
"id": 13,
"name": "Damir",
"lastname": "Serik",
"email": "damir@example.com",
"availability": "away",
"last_login_at": "2026-03-19T18:02:00.000000Z"
}
]
}Снимок настроек уведомлений
Запрос
curl https://api.aisar.app/v1/me/notification-preferences \
-H "Authorization: Bearer YOUR_API_TOKEN"Ответ
{
"data": {
"channels": {
"mail": true,
"push": true,
"whatsapp": true
},
"categories": {
"onboarding": { "enabled": true, "label": "Onboarding", "controllable": true },
"trial": { "enabled": true, "label": "Trial", "controllable": true },
"billing": { "enabled": true, "label": "Billing", "controllable": true },
"win_back": { "enabled": true, "label": "Win-back", "controllable": true },
"engagement": { "enabled": false, "label": "Engagement", "controllable": true },
"transactional": { "enabled": true, "label": "Transactional", "controllable": false }
},
"push_triggers": {
"chat_message_new": true,
"conversation_assigned": true,
"call_missed": true,
"call_incoming": true,
"company_unassigned": false
},
"quiet_hours": {
"enabled": false,
"start": null,
"end": null
}
}
}Отказ от WhatsApp-канала биллинговых уведомлений
Запрос
curl -X PUT https://api.aisar.app/v1/me/notification-preferences \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"channels": {
"whatsapp": false
}
}'Ответ
{
"data": {
"channels": {
"mail": true,
"push": true,
"whatsapp": false
},
"categories": {
"onboarding": { "enabled": true, "label": "Onboarding", "controllable": true },
"trial": { "enabled": true, "label": "Trial", "controllable": true },
"billing": { "enabled": true, "label": "Billing", "controllable": true },
"win_back": { "enabled": true, "label": "Win-back", "controllable": true },
"engagement": { "enabled": false, "label": "Engagement", "controllable": true },
"transactional": { "enabled": true, "label": "Transactional", "controllable": false }
},
"push_triggers": {
"chat_message_new": true,
"conversation_assigned": true,
"call_missed": true,
"call_incoming": true,
"company_unassigned": false
},
"quiet_hours": {
"enabled": false,
"start": null,
"end": null
}
}
}Тихие часы и push о неразобранных диалогах
Запрос
curl -X PUT https://api.aisar.app/v1/me/notification-preferences \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"push_triggers": {
"company_unassigned": true
},
"quiet_hours": {
"enabled": true,
"start": "22:00",
"end": "08:00"
}
}'Ответ
{
"data": {
"channels": {
"mail": true,
"push": true,
"whatsapp": true
},
"categories": {
"onboarding": { "enabled": true, "label": "Onboarding", "controllable": true },
"trial": { "enabled": true, "label": "Trial", "controllable": true },
"billing": { "enabled": true, "label": "Billing", "controllable": true },
"win_back": { "enabled": true, "label": "Win-back", "controllable": true },
"engagement": { "enabled": false, "label": "Engagement", "controllable": true },
"transactional": { "enabled": true, "label": "Transactional", "controllable": false }
},
"push_triggers": {
"chat_message_new": true,
"conversation_assigned": true,
"call_missed": true,
"call_incoming": true,
"company_unassigned": true
},
"quiet_hours": {
"enabled": true,
"start": "22:00",
"end": "08:00"
}
}
}Список операторов компании
Запрос
curl https://api.aisar.app/v1/companies/current/members/telephony \
-H "Authorization: Bearer YOUR_API_TOKEN"Ответ
{
"data": [
{
"user_id": 12,
"name": "Aisha Nurlanova",
"email": "aisha@example.com",
"photo": null,
"telephony_enabled": true,
"browser_registered": true,
"extension": "101",
"external_enabled": true,
"external_registered": true,
"outbound": {
"schemes": [
{ "id": "0199a1e2-7c3a-7f4e-9c2b-6a1d8e7b1c31", "name": "Продажи", "via": "personal", "channel_ids": [412] },
{ "id": "0199a1e2-8d4b-7f4e-9c2b-6a1d8e7b1c99", "name": "Вечерние обзвоны", "via": "team", "channel_ids": [413] }
],
"primary_scheme_id": "0199a1e2-7c3a-7f4e-9c2b-6a1d8e7b1c31",
"primary_source": "explicit",
"falls_back_to": null
}
},
{
"user_id": 13,
"name": "Damir Serik",
"email": "damir@example.com",
"photo": null,
"telephony_enabled": false,
"browser_registered": false,
"extension": null,
"external_enabled": false,
"external_registered": false,
"outbound": {
"schemes": [],
"primary_scheme_id": null,
"primary_source": null,
"falls_back_to": "legacy"
}
}
]
}Включить телефонию участнику
Запрос
curl -X POST https://api.aisar.app/v1/companies/current/members/13/telephony/enable \
-H "Authorization: Bearer YOUR_API_TOKEN"Ответ
{
"data": {
"message": "Telephony enabled.",
"telephony": {
"user_id": 13,
"sip_username": "agent-13-web",
"status": "active"
}
}
}SIP-креды участника (вид админа)
Запрос
curl https://api.aisar.app/v1/companies/current/members/13/telephony/credentials \
-H "Authorization: Bearer YOUR_API_TOKEN"Ответ
{
"data": {
"sip_username": "agent-13",
"sip_password": "Qw7Lm2VtRk4x",
"host": "sip.aisar.app",
"tcp_port": 5060,
"tls_port": 5061,
"default_transport": "TCP",
"codecs": ["opus", "PCMU", "PCMA"],
"media_encryption": "SRTP/SDES (over TLS)",
"registered": false,
"provisioning_uri": "sip:agent-13:Qw7Lm2VtRk4x@sip.aisar.app:5060;transport=tcp"
}
}