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

Контакты

Контакт — карточка человека или компании, с которой ведётся общение: телефоны, email, привязанные аккаунты каналов (contactAccounts — например конкретный WhatsApp-номер или Instagram-username), метки и статус (lead/active/inactive). Один контакт может иметь несколько привязанных аккаунтов и диалогов. API покрывает CRUD, поиск/резолв по идентификаторам, объединение дублей, управление подпиской на рассылки (opt-in/opt-out) и блокировку контакта (block/unblock).

Список: фильтры, сортировка, пагинация

Список поддерживает пагинацию (page; perPage — от 5 до 100, по умолчанию 10), полнотекстовый поиск (search), фильтр statuses[] (допустимы только active и inactive — статус lead фильтром не выбирается, попытка отфильтровать по нему даёт 422) и сортировку (sortBy: id|display_name|company|status|created_at, по умолчанию created_at; sortDir: asc|desc, по умолчанию desc).

Флаг exclude_lid_only (0/1, по умолчанию 1) по умолчанию скрывает контакты, у которых единственные привязанные аккаунты — приватные WhatsApp-идентификаторы @lid без распознанного номера; передайте 0, чтобы их показать.

Быстрый поиск

Быстрый поиск /lookup ищет по телефонам и привязанным аккаунтам (external_id, username) и возвращает до 20 совпадений.

Доступ и фото

Контакт другой компании при чтении, обновлении, удалении и по вложенным ресурсам возвращает 404 «Contact not found.». Эндпоинты фото отдают изображение потоком с откатом на profile_picture привязанного аккаунта.

Создание и обновление связей

При создании и обновлении связи (phones, emailes, contactAccounts) сохраняются целиком: PATCH полностью пересобирает набор связанных записей из тела запроса.

Звонки контакту

Телефонная пара эндпоинтов (доступна только при подключённой телефонии) отвечает на вопрос «можно ли позвонить контакту прямо сейчас»: GET /outbound-eligibility возвращает по указанному channel_id DTO из двух блоков — message (состояние 24-часового окна сообщений WhatsApp) и call (можно ли звонить и нужно ли/можно ли запросить разрешение), а POST /call-permission/request отправляет контакту запрос на разрешение звонка (WhatsApp Calling).

Для SIP-каналов звонок разрешён всегда (permission_status=not_required), разрешение не запрашивается. Лимиты Meta на запрос разрешения: не чаще 1 раза в 24 часа и не более 2 раз за скользящие 7 дней — при исчерпании POST отвечает 429 с полем eligibility, в котором заполнен request_cooldown_until (Unix-время снятия ограничения).

Блокировка контакта

POST /contacts/{contact}/block и /unblock (с 2026-09-04) — настоящая блокировка: blocked_at на контакте выставляется первым и остаётся источником истины, даже если провайдерские вызовы ниже отклонены целиком. После этого сервис best-effort блокирует контакт на каждом WhatsApp-канале компании (whatsapp и whatsapp_business), с которым у контакта есть хотя бы один тред, — канал, которому контакт никогда не писал, не трогается. Результат по каждому такому каналу попадает в provider_failures[] ответа ({channel_id, error_code, message}); пустой массив — полный успех (или провайдерских каналов не было вовсе), непустой — не отменяет локальную блокировку: blocked_at в том же ответе уже заполнен. Повторный block на уже заблокированном контакте — no-op (message: "Contact was already blocked.", provider_failures: []), без обращений к провайдерам; аналогично unblock на уже разблокированном.

GET /contacts/{contact}/block-preview отвечает на вопрос «что произойдёт, если заблокировать» ДО самой блокировки: channels[] перечисляет каналы, на которые уйдёт провайдерский блок (channel_id, channel_name, channel_type, provider_block_supported), и для каждого — window_open. Поле window_open осмысленно только для whatsapp_business: false означает, что контакт не писал последние 24 часа и Cloud API блокировку сейчас не примет (та же причина, что приходит потом как not_reachable_24h в provider_failures[]); у «серых» каналов такого ограничения нет и там всегда true. Пустой channels[] — блокировка будет только локальной. Право то же (contact:update), лимит — throttle:api-token-heavy.

Оба эндпоинта требуют право contact:update (как opt-out/opt-in) и ограничены отдельным лимитом throttle:contact-block: 6 изменений в час на контакт, 60 в час и 300 в сутки на компанию. Чтобы прочитать список номеров, заблокированных самим провайдером на конкретном канале (а не то, что заблокировано внутри AISAR), используйте GET /channels/{channel}/whatsapp/blocked-users из группы «Каналы» — авторизация там другая (channel:view).

Фото контакта и пользователя

GET /contacts/{contact}/photo и GET /users/{user}/photo (с 2026-08-24) всегда отдают webp-превью шириной до 256px, если исходник — декодируемое изображение шире 256px; в остальных случаях (уже ≤256px, либо GD не смог декодировать формат) отдаётся оригинал потоком, как раньше. Оригинал этих двух роутов больше не запросить явно — если он понадобится, потребуется отдельный параметр. Оба роута — signed и под лимитом throttle:signed-media (2000 запросов/мин на IP), общим с GET .../attachments/{attachment}/media.

Эндпоинты

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

Список контактов компании (query: search, statuses[]=active|inactive, sortBy, sortDir, exclude_lid_only, page, perPage).

GET/v1/contacts/lookup

Быстрый поиск контактов по телефону, external_id или username канала.

GET/v1/contacts/channel-options

Список типов каналов, доступных для привязки аккаунта к контакту.

GET/v1/contacts/{contact}

Карточка одного контакта (phones, emailes, contactAccounts, tags).

POST/v1/contacts

Создать контакт (с телефонами, email и привязанными аккаунтами каналов).

POST/v1/contacts/resolve-identifiers

Разрешить пакет идентификаторов (телефон/username/external_id) в id существующих контактов.

PATCH/v1/contacts/{contact}

Обновить контакт (полностью пересохраняет связанные телефоны/email/аккаунты).

DELETE/v1/contacts/{contact}

Удалить контакт (мягкое удаление).

POST/v1/contacts/{contact}/merge

Объединить контакт с другим (все диалоги и аккаунты переносятся в целевой).

POST/v1/contacts/{contact}/tags

Синхронизировать метки контакта (полная замена набора).

POST/v1/contacts/{contact}/opt-out

Отписать контакт от рассылок.

POST/v1/contacts/{contact}/opt-in

Вернуть контакт в рассылки (снять opt-out).

GET/v1/contacts/{contact}/block-preview

Что затронет блокировка до её применения: каналы, куда уйдёт блок, и открыто ли на них 24-часовое окно WhatsApp.

POST/v1/contacts/{contact}/block

Заблокировать контакт (body: reason?) — ставит blocked_at и best-effort блокирует его на каждом WhatsApp-канале, с которым у него есть переписка.

POST/v1/contacts/{contact}/unblock

Снять блокировку контакта — очищает blocked_at и best-effort разблокирует его на затронутых WhatsApp-каналах.

GET/v1/contacts/{contact}/deals

Список сделок контакта (требует включённый функционал сделок).

GET/v1/contacts/{contact}/outbound-eligibility

Проверить право на исходящий звонок контакту по каналу (query: channel_id) — окно сообщений и разрешение на звонок.

POST/v1/contacts/{contact}/call-permission/request

Запросить у контакта разрешение на звонок (WhatsApp Calling; body: channel_id).

GET/v1/contacts/{contact}/photo

Фото контакта (fallback на profile_picture привязанного аккаунта) — webp-превью до 256px, если исходник декодируется и шире; иначе оригинал потоком.

GET/v1/users/{user}/photo

Фото пользователя компании — webp-превью до 256px, если исходник декодируется и шире; иначе оригинал потоком.

Примеры

Создание контакта

Запрос

bash
curl -X POST "https://api.aisar.app/v1/contacts" \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "firstName": "John",
    "lastName": "Doe",
    "phones": [{ "phone": "+77001234567", "type": "mobile" }],
    "emailes": [{ "email": "john@example.com", "type": "work" }]
  }'

Ответ

json
{
  "data": {
    "id": 45,
    "company_id": 1,
    "firstName": "John",
    "lastName": "Doe",
    "displayName": "John Doe",
    "status": "lead",
    "phones": [{ "phone": "+77001234567", "type": "mobile" }],
    "emailes": [{ "email": "john@example.com", "type": "work" }],
    "contactAccounts": [],
    "merged_into_contact_id": null,
    "blocked_at": null,
    "blocked_reason": null,
    "blocked_by_user_id": null
  }
}

Поиск контакта по телефону

Запрос

bash
curl -X GET "https://api.aisar.app/v1/contacts/lookup?q=%2B77001234567" \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Accept: application/json"

Ответ

json
{
  "data": [
    {
      "id": 45,
      "displayName": "John Doe",
      "status": "lead",
      "phones": [{ "phone": "+77001234567", "type": "mobile" }]
    }
  ]
}

Список контактов с фильтром по статусу

Запрос

bash
curl -X GET "https://api.aisar.app/v1/contacts?perPage=10&statuses=active,inactive&sortBy=created_at&sortDir=desc" \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Accept: application/json"

Ответ

json
{
  "data": {
    "items": [
      {
        "id": 45,
        "displayName": "John Doe",
        "status": "active",
        "phones": [{ "phone": "+77001234567", "type": "mobile" }],
        "tags": []
      }
    ],
    "pagination": { "page": 1, "perPage": 10, "total": 120, "lastPage": 12 }
  }
}

Карточка контакта

Запрос

bash
curl -X GET "https://api.aisar.app/v1/contacts/45" \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Accept: application/json"

Ответ

json
{
  "data": {
    "id": 45,
    "company_id": 10,
    "firstName": "John",
    "lastName": "Doe",
    "displayName": "John Doe",
    "status": "lead",
    "phones": [{ "phone": "+77001234567", "type": "mobile" }],
    "emailes": [{ "email": "john@example.com", "type": "work" }],
    "contactAccounts": [
      {
        "id": 88,
        "channel_type": "whatsapp",
        "external_id": "77001234567",
        "username": null,
        "profile_picture": null
      }
    ],
    "tags": [{ "id": 3, "name": "VIP" }],
    "merged_into_contact_id": null,
    "blocked_at": null,
    "blocked_reason": null,
    "blocked_by_user_id": null
  }
}

Синхронизация меток контакта

Запрос

bash
curl -X POST "https://api.aisar.app/v1/contacts/45/tags" \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "tag_ids": [1, 2, 3]
  }'

Ответ

json
{
  "data": {
    "tags": [
      { "id": 1, "name": "Lead" },
      { "id": 2, "name": "Newsletter" },
      { "id": 3, "name": "VIP" }
    ]
  }
}

Сделки контакта

Запрос

bash
curl -X GET "https://api.aisar.app/v1/contacts/45/deals?perPage=20" \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Accept: application/json"

Ответ

json
{
  "data": {
    "items": [
      {
        "id": 7,
        "title": "Website enquiry",
        "status": "open",
        "amount": 150000,
        "funnel_id": 2,
        "stage_id": 5
      }
    ],
    "pagination": { "page": 1, "perPage": 20, "total": 3, "lastPage": 1 }
  }
}

Право на исходящий звонок (WhatsApp)

Запрос

bash
curl -X GET "https://api.aisar.app/v1/contacts/45/outbound-eligibility?channel_id=7" \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Accept: application/json"

Ответ

json
{
  "data": {
    "eligibility": {
      "message": {
        "window_open": true,
        "window_expires_at": 1710500000,
        "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/contacts/45/call-permission/request" \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "channel_id": 7 }'

Ответ

json
{
  "data": {
    "eligibility": {
      "message": {
        "window_open": true,
        "window_expires_at": 1710500000,
        "requires_template": false
      },
      "call": {
        "channel_kind": "whatsapp",
        "can_call": false,
        "permission_status": "pending",
        "permission_expires_at": null,
        "can_request_permission": false,
        "request_cooldown_until": 1710586400,
        "attempts_remaining": 5,
        "has_whatsapp_account": true
      }
    }
  }
}

Лимит запросов разрешения исчерпан (429)

Запрос

bash
curl -X POST "https://api.aisar.app/v1/contacts/45/call-permission/request" \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "channel_id": 7 }'

Ответ

json
{
  "data": {
    "message": "Call-permission request limit reached.",
    "eligibility": {
      "message": {
        "window_open": true,
        "window_expires_at": 1710500000,
        "requires_template": false
      },
      "call": {
        "channel_kind": "whatsapp",
        "can_call": false,
        "permission_status": "pending",
        "permission_expires_at": null,
        "can_request_permission": false,
        "request_cooldown_until": 1710586400,
        "attempts_remaining": 5,
        "has_whatsapp_account": true
      }
    }
  }
}

Блокировка контакта

Запрос

bash
curl -X POST "https://api.aisar.app/v1/contacts/45/block" \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "reason": "Оскорбления в переписке" }'

Ответ

json
{
  "data": {
    "message": "Contact blocked.",
    "contact_id": 45,
    "blocked_at": "2026-09-04T10:15:00Z",
    "blocked_reason": "Оскорбления в переписке",
    "provider_failures": []
  }
}

Блокировка контакта — провайдер отклонил один из каналов

Запрос

bash
curl -X POST "https://api.aisar.app/v1/contacts/45/block" \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{}'

Ответ

json
{
  "data": {
    "message": "Contact blocked.",
    "contact_id": 45,
    "blocked_at": "2026-09-04T10:15:00Z",
    "blocked_reason": null,
    "provider_failures": [
      { "channel_id": 9, "error_code": "channel_offline", "message": "The channel is not connected right now — connect it and try again." }
    ]
  }
}

Снятие блокировки

Запрос

bash
curl -X POST "https://api.aisar.app/v1/contacts/45/unblock" \
  -H "Authorization: Bearer YOUR_API_TOKEN"

Ответ

json
{
  "data": {
    "message": "Contact unblocked.",
    "contact_id": 45,
    "blocked_at": null,
    "blocked_reason": null,
    "provider_failures": []
  }
}