Skip to content
AISARAISAR
REST API

Channels

A channel is a connected messaging source: WhatsApp, Telegram, Instagram, SIP telephony, etc. Through the public REST API you read the channel list and its connection state, manage the lifecycle of an already-connected channel (pause/resume/reconnect/disconnect), and configure operator access rules for it. Connecting and initial provisioning happen through the wizard in the client dashboard, not through this API.

Connecting a channel

Connecting and provisioning a new channel (QR pairing, Meta OAuth, SIP setup) happens through the onboarding wizard in the client dashboard (my.aisar.app/settings/channels/add) rather than directly through this API — the wizard uses separate, provider-specific setup endpoints.

Lifecycle

A channel lifecycle is expressed by two fields:

  • setup_state (draft → pairing → ready | failed) — the initial setup stage.
  • status (connected | disconnected | connecting | reconnecting | paused | expired | banned) — the current state of an already-configured channel. banned is not terminal: once the provider lifts the account ban, the channel can be reconnected via the regular reconnect/pairing flow; if the ban still stands, the provider rejects the connection and the channel returns to banned.

WhatsApp number profile

The avatar and profile texts are read and changed through /v1/channels/{channel}/whatsapp/profile. Which fields are available depends on the connection rather than on the channel type, so the response always carries capabilities: WhatsApp Business exposes about, description, address, email, websites and category, while a QR-connected WhatsApp exposes the photo and about, with a description only when the number runs the WhatsApp Business app. Branch on capabilities and limits from the response: sending an unsupported field rejects the whole request instead of applying it in part.

WhatsApp blocked users

GET /channels/{channel}/whatsapp/blocked-users (since 2026-09-04) reads the list of numbers the PROVIDER itself has blocked on this channel — it is not AISAR's own blocked-contact list (that is managed by POST /contacts/{contact}/block and /unblock, see the "Contacts" group). The shape of numbers[] depends on the connection: WhatsApp Business (Cloud API) returns the provider's number format (e.g. +77001111111), QR-based WhatsApp (whatsmeow) returns a WhatsApp JID (e.g. 77009998877@s.whatsapp.net). Pagination is cursor-based: pass after set to the previous response's next_cursor to get the next page; next_cursor: null means the end of the list. limit ranges 1–1000, default 100.

Available only for WhatsApp channels (whatsapp/whatsapp_business) — a channel of another type returns 422 {error_code: "channel_not_whatsapp"}; a disconnected channel returns 409 {error_code: "channel_offline"}. Authorization is the channel:view permission (not contact:update, unlike block/unblock) — this reads channel state rather than acting on a contact; a foreign channel returns 403.

Temporary SIP-trunk outbound hold (`sip_outbound_gated`)

A sip_telephony channel connected via the operator equipment's IP address (see the channel-connection guide, "SIP telephony (BYO trunk)" section) may have its outbound calls temporarily held if the same address is already in use by another AISAR company with the same operator. This is reflected in the boolean sip_outbound_gated field — present on EVERY channel (not just sip_telephony), false when no hold applies. While sip_outbound_gated: true, the channel still accepts inbound calls, but POST /calls/originate and POST /softphone/external-call behave as if the company had no connected SIP channel at all. The hold lifts automatically, with no endpoint call needed, as soon as the channel's number receives its first confirmed inbound call.

Archived channels

A channel deleted with its history kept (DELETE /channels/{channel} without keep_history=false — the default, see below) does not disappear: it is soft-deleted, stamped with archived_at, and its conversation history stays readable inside AISAR. The regular GET /channels never returns it, and there is no parameter that puts it back in that list — include_archived is not part of GET /channels validation, so any value for it is silently ignored. The only way to see an archived channel is the separate GET /channels/archived list.

GET /channels/archived returns the same ChannelListResource as the regular list, with archived_at and messages_count filled in (the channel's message count, including messages later deleted individually). The moment of archiving is marked by the channel.archived webhook event (see the webhook events reference) — this list is where the channel that event notified you about can be found afterwards. GET /channels/{channel} by an archived channel's id returns 404 — that is expected (route-model binding does not resolve a soft-deleted model), not a sign that the channel was lost.

Permanently and irreversibly deleting an archived channel together with its history is DELETE /channels/archived/{channelId} (channelId is a plain integer: a soft-deleted channel is not resolved via route-model binding, so the path takes the id directly rather than {channel}). A 200 response means the deletion was queued as a background job, not that it has already finished.

Lifecycle request bodies

Pause/resume/disconnect take no request body, whereas reconnect (POST /channels/{channel}/reconnect) accepts a provider-specific body for OAuth/bot channels (Instagram/Threads — { code }, WhatsApp Business — { authorization_code, waba_id, phone_number_id }, Telegram Bot — { bot_token }), otherwise 422; WhatsApp/Telegram QR and Live Chat take an empty body. The full per-type contract is covered in the partner documentation (channel-connection guide, "Direct API" section).

provider_key and channel type

The provider_key field is a public, neutral provider identifier; internal runtime-library names are never exposed. The full list of values and the QR-channel mapping rules live in the «Types: messaging» reference, the Channel type. To distinguish the channel type in code, rely on channel_type.key rather than the display name.

Channel log pagination

GET /channels/{channel}/logs is paginated: page/per_page (the perPage alias is accepted too), capped at 500, defaulting to 200. Until 2026-08-25 the endpoint silently ignored every paging parameter and always returned the newest 200 rows — there was no way to look further back into the history. Sorting is created_at desc, then id desc: the runtime writes bursts of log rows within the same second, and without the id tiebreaker the same row could repeat on one page and vanish from another. The response is wrapped like other paginated lists: items in data, paging info in meta (current_page, last_page, per_page, total); the shape of an individual log entry is unchanged.

Send-failure codes in the channel log

Besides connection codes (connection.ready, connection.lost, etc.), GET /channels/{channel}/logs (since 2026-08-26) records two outbound-message-fate entries. message.send.failed.{error_code}error level: one entry per send that exhausted all retries (not per attempt, or a broadcast would flood the channel log), {error_code} is the last attempt's error code or unknown; context carries message_id, attempts, error_code and a truncated response (only reason/status/response from the runtime/provider response — the runtime's internal address is stripped from response for security reasons). message.status.failed_suppressedwarning level: the provider sent a failure receipt AFTER the message had already reached delivered/read/played — the message's status is not downgraded (see the message.status.updated event), but the provider-side failure is still recorded here; context carries message_id and reason (a human-readable cause).

sip_status in the eligibility response

sip_status inside eligibility.calling (in the GET .../whatsapp-calling response, and also POST .../whatsapp-calling/enable|disable) is an AISAR-derived field, not Meta's raw status: ENABLED once calling is turned on for the channel (config.calling_did is set), NOT_SET when calling isn't enabled, UNKNOWN when the Graph API call itself failed (see the top-level unknown_reason/detail). To decide whether calling is actually available right now, rely on active (true only when can_receive_call_sip === "AVAILABLE") and can_receive_call_sip itself — both always reflect Meta's current state, while sip_status is a local mirror of AISAR's last successful enable/disable and can miss a change made directly in WhatsApp Manager.

Endpoints

MethodPath
GET/v1/channels

List the company's channels (setup drafts excluded by default).

GET/v1/channels/{channel}

Get a channel by id along with its provider configuration.

POST/v1/channels

Create a channel (optionally starting pairing right away); channels are usually connected through the in-dashboard wizard.

PATCH/v1/channels/{channel}

Update channel settings: name, description, account_name and the allow_broadcasts / allow_automations / allow_inbound_handling toggles. POST and PUT on this path are accepted too.

GET/v1/channel-types

Reference list of channel types with their capabilities and providers.

POST/v1/channels/{channel}/connect/start

Start connecting a runtime channel (WhatsApp/Telegram/Instagram via QR): body { "mode": "qr" | "code" | "phone", "phone_number"? }. Then poll GET /channels/{channel}/connection.

GET/v1/channels/{channel}/connection

Connection-state snapshot for polling a custom QR UI: connection.status (draft, qr_required, pairing_required, authorizing, 2fa_required, connected, reconnecting, expired, error, disconnected), a qr object, pairing_code (for code mode), and pairing_state.

POST/v1/channels/{channel}/auth-code

Submit an SMS code or a 2FA password to the runtime (Telegram Personal login).

POST/v1/channels/{channel}/disconnect

Disconnect a channel (stop sending/receiving messages).

POST/v1/channels/{channel}/reconnect

Reconnect a previously disconnected channel. The request body depends on the channel type: OAuth/bot channels require provider fields (Instagram/Threads — { code }, WhatsApp Business — { authorization_code, waba_id, phone_number_id }, Telegram Bot — { bot_token }), otherwise 422; WhatsApp/Telegram QR and Live Chat take an empty body. Requires channel:update; a foreign channel returns 404. Full per-type contract — in the partner documentation (channel-connection guide, "Direct API" section).

POST/v1/channels/{channel}/pause

Pause a channel (temporarily, without tearing down the session).

POST/v1/channels/{channel}/resume

Resume a paused channel.

DELETE/v1/channels/{channel}

Delete a channel (soft delete). By default (`keep_history` omitted or `true`) the conversation history is kept and the channel becomes archived — retrievable via GET /channels/archived; `keep_history=false` deletes the channel together with its history for good.

GET/v1/channels/archived

Archived channels — deleted with history kept (see "Archived channels" above). Not returned by the regular list; there is no parameter that adds them back.

DELETE/v1/channels/archived/{channelId}

Permanently and irreversibly delete an archived channel together with its history (runs as a background job).

GET/v1/channels/{channel}/logs

Channel logs (connection events, errors), paginated: `page`/`per_page` (`perPage` accepted too; max 500, default 200), sorted by `created_at` desc, then `id` desc.

GET/v1/channels/{channel}/whatsapp-calling

WhatsApp calling readiness (eligibility): Meta prerequisites and blocking issues. Meta/WABA channels only (otherwise 404); requires telephony-management rights (otherwise 403).

POST/v1/channels/{channel}/whatsapp-calling/enable

Enable WhatsApp calling for the number. Meta/WABA channels, telephony rights. Unmet prerequisites return HTTP 422 with ok:false and error_code.

POST/v1/channels/{channel}/whatsapp-calling/disable

Disable WhatsApp calling for the number. Meta/WABA channels, telephony rights.

GET/v1/channels/{channel}/whatsapp/profile

The WhatsApp number's profile: the avatar and the texts a contact sees on the business card. The response carries `capabilities` — the fields this particular channel supports — and `limits` with length ceilings; branch on those, not on the channel type. When the provider is unreachable, the last saved copy is returned with `stale: true`. Requires channel:view.

POST/v1/channels/{channel}/whatsapp/profile

Update the number's profile. Send only what changes; a field the channel does not support rejects the whole request with HTTP 422 and `field_not_supported`. The avatar goes as a multipart `picture` file (JPG/PNG/WebP, up to 5 MB, at least 192 px per side) — we crop it to a square and strip the capture metadata ourselves. `about` cannot be empty. Requires channel:update; the change rate is limited.

DELETE/v1/channels/{channel}/whatsapp/profile/picture

Clear the number's avatar. Not supported by every provider: check `capabilities.picture_remove` in the GET response — a WhatsApp Business photo can only be replaced. Requires channel:update.

GET/v1/channels/{channel}/whatsapp/blocked-users

List of numbers the provider has blocked on this WhatsApp channel (query: after, limit) — not AISAR's own blocked-contact list. WhatsApp channels only; requires channel:view.

GET/v1/channels/{channel}/access

Operator/team access rules for the channel.

POST/v1/channels/{channel}/access

Update channel access rules (allow/block per team or user).

GET/v1/channels/{channel}/templates/sendable

Templates available for this channel's in-chat picker. For WhatsApp Business — channel-bound templates with `status=published` and `meta_status=approved` (sent via POST /messages/send-template). For every other channel type — the company's published templates of the same channel_type (not bound to a channel, no Meta moderation); the frontend inserts their filled text into the chat input as a plain message rather than through the template-send API.

Examples

List connected channels

Request

bash
curl -X GET "https://api.aisar.app/v1/channels?status=connected" \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Accept: application/json"

Response

json
{
  "data": [
    {
      "id": 3,
      "company_id": 1,
      "channel_type_id": 1,
      "provider_key": "whatsapp",
      "name": "Основной WhatsApp",
      "account_name": "+77001234567",
      "status": "connected",
      "setup_state": "ready",
      "allow_broadcasts": true,
      "allow_automations": true,
      "allow_inbound_handling": true,
      "connected_at": "2026-01-15T09:00:00Z",
      "last_inbound_at": "2026-02-09T18:00:00Z",
      "channel_type": { "id": 1, "key": "whatsapp", "name": "WhatsApp" }
    }
  ]
}

Pause a channel

Request

bash
curl -X POST "https://api.aisar.app/v1/channels/3/pause" \
  -H "Authorization: Bearer YOUR_API_TOKEN"

Response

json
{
  "data": {
    "message": "Channel status updated.",
    "channel": {
      "id": 3,
      "status": "paused",
      "status_changed_at": "2026-02-09T18:30:00Z"
    }
  }
}

Reconnect a channel (Instagram — with a body)

Request

bash
curl -X POST "https://api.aisar.app/v1/channels/9/reconnect" \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "code": "AQB..." }'

Response

json
{
  "data": {
    "message": "Channel reconnected.",
    "channel": {
      "id": 9,
      "status": "connected",
      "setup_state": "ready"
    }
  }
}

Rename a channel and toggle its flags

Request

bash
curl -X PATCH "https://api.aisar.app/v1/channels/3" \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Отдел продаж — WhatsApp",
    "allow_broadcasts": false,
    "allow_automations": true
  }'

Response

json
{
  "data": {
    "message": "Channel updated.",
    "channel": {
      "id": 3,
      "name": "Отдел продаж — WhatsApp",
      "allow_broadcasts": false,
      "allow_automations": true,
      "allow_inbound_handling": true,
      "status": "connected",
      "setup_state": "ready"
    }
  }
}

A channel with its provider config

Request

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

Response

json
{
  "data": {
    "channel": {
      "id": 3,
      "company_id": 1,
      "channel_type_id": 1,
      "provider_key": "whatsapp",
      "name": "Основной WhatsApp",
      "account_name": "+77000000001",
      "status": "connected",
      "setup_state": "ready",
      "channel_type": { "id": 1, "key": "whatsapp", "name": "WhatsApp" }
    },
    "provider_config": {
      "mark_online_on_connect": {
        "value": true,
        "label": "Отмечать онлайн при подключении",
        "description": "Показывать аккаунт онлайн, пока канал подключён"
      }
    }
  }
}

Create a channel and start pairing

Request

bash
curl -X POST "https://api.aisar.app/v1/channels" \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "channel_type_id": 1,
    "provider_key": "whatsapp",
    "name": "Sales WhatsApp",
    "account_name": "+77000000002",
    "start_pairing": true,
    "pairing_mode": "qr"
  }'

Response

json
{
  "data": {
    "message": "Channel created.",
    "channel": {
      "id": 7,
      "provider_key": "whatsapp",
      "status": "connecting",
      "setup_state": "pairing"
    },
    "pairing": {
      "mode": "qr",
      "request_id": "00000000-0000-0000-0000-000000000000"
    }
  }
}

Poll the connection state (QR)

Request

bash
curl -X GET "https://api.aisar.app/v1/channels/7/connection" \
  -H "Authorization: Bearer YOUR_API_TOKEN"

Response

json
{
  "data": {
    "channel_id": 7,
    "setup_state": "pairing",
    "connection": {
      "status": "qr_required",
      "mode": "qr",
      "qr": {
        "value": "2@EXAMPLE_QR_PAIRING_PAYLOAD_REDACTED",
        "expires_at": "2026-02-09T18:00:20Z",
        "expires_in_seconds": 18,
        "is_expired": false,
        "attempt": 1
      },
      "pairing_code": null,
      "pairing_code_expires_at": null,
      "pairing_state": "qr",
      "last_error_message": null,
      "retry_count": null,
      "next_retry_at": null,
      "status_message": null,
      "updated_at": "2026-02-09T18:00:05Z"
    },
    "metrics": {}
  }
}

Submit an auth code (Telegram Personal)

Request

bash
curl -X POST "https://api.aisar.app/v1/channels/8/auth-code" \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "code": "12345",
    "phone_code_hash": "EXAMPLE_PHONE_CODE_HASH"
  }'

Response

json
{
  "data": {
    "message": "Auth code submitted.",
    "channel_id": 8
  }
}

Channel logs

Request

bash
curl -X GET "https://api.aisar.app/v1/channels/3/logs" \
  -H "Authorization: Bearer YOUR_API_TOKEN"

Response

json
{
  "data": [
    {
      "id": "1042",
      "level": "info",
      "code": "connection.ready",
      "message": "Channel connected",
      "context": {},
      "created_at": "2026-02-09T18:00:00Z"
    }
  ],
  "meta": { "current_page": 1, "last_page": 1, "per_page": 200, "total": 1 }
}

Channel logs — paging through the history

Request

bash
curl -X GET "https://api.aisar.app/v1/channels/3/logs?per_page=50&page=2" \
  -H "Authorization: Bearer YOUR_API_TOKEN"

Response

json
{
  "data": [
    {
      "id": "992",
      "level": "error",
      "code": "connection.lost",
      "message": "Runtime session dropped",
      "context": { "reason": "device_removed" },
      "created_at": "2026-02-08T21:14:07Z"
    }
  ],
  "meta": { "current_page": 2, "last_page": 3, "per_page": 50, "total": 121 }
}

Channel logs — a message send failure

Request

bash
curl -X GET "https://api.aisar.app/v1/channels/3/logs" \
  -H "Authorization: Bearer YOUR_API_TOKEN"

Response

json
{
  "data": [
    {
      "id": "1108",
      "level": "error",
      "code": "message.send.failed.rate_limit_hit",
      "message": "Too many messages sent, please wait before sending more",
      "context": {
        "message_id": 1042,
        "attempts": 3,
        "error_code": "rate_limit_hit",
        "response": "{\"status\":429,\"reason\":\"rate_limited\"}"
      },
      "created_at": "2026-08-26T10:02:11Z"
    },
    {
      "id": "1107",
      "level": "warning",
      "code": "message.status.failed_suppressed",
      "message": "Delivery failure receipt arrived after the message was already delivered",
      "context": {
        "message_id": 1039,
        "reason": "Message expired (131053)"
      },
      "created_at": "2026-08-26T09:47:03Z"
    }
  ],
  "meta": { "current_page": 1, "last_page": 1, "per_page": 200, "total": 2 }
}

List archived channels

Request

bash
# GET /channels никогда не возвращает архивные каналы, и параметра
# ?include_archived=1 не существует — если его передать, валидация молча
# его проигнорирует. Единственный способ увидеть архивный канал — этот список.
curl -X GET "https://api.aisar.app/v1/channels/archived" \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Accept: application/json"

Response

json
{
  "data": [
    {
      "id": 12,
      "name": "WhatsApp — старый номер",
      "account_name": "+77001112233",
      "status": "disconnected",
      "provider_key": "whatsapp",
      "setup_state": "ready",
      "archived_at": "2026-08-20T09:15:00.000000Z",
      "messages_count": 4213,
      "channel_type": { "id": 1, "key": "whatsapp", "name": "WhatsApp" }
    }
  ]
}

Permanently delete an archived channel

Request

bash
curl -X DELETE "https://api.aisar.app/v1/channels/archived/12" \
  -H "Authorization: Bearer YOUR_API_TOKEN"

Response

json
{
  "data": {
    "message": "Archived channel purged."
  }
}

WhatsApp calling readiness (eligibility)

Request

bash
curl -X GET "https://api.aisar.app/v1/channels/3/whatsapp-calling" \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Accept: application/json"

Response

json
{
  "data": {
    "eligibility": {
      "calling": {
        "sip_status": "NOT_SET",
        "can_receive_call_sip": null,
        "active": false
      },
      "prerequisites": {
        "business_verified": false,
        "payment_ok": true,
        "name_approved": true,
        "number_registered": true
      },
      "blocking_issues": [
        {
          "key": "business_verification",
          "severity": "error",
          "title": "Business is not verified with Meta",
          "detail": "The business owner must complete Business Verification in Meta Business Manager.",
          "fix_url": "https://business.facebook.com/settings/security"
        }
      ],
      "can_enable": false,
      "name_status": "APPROVED",
      "checked_at": "2026-02-09T18:00:00Z"
    }
  }
}

Enable WhatsApp calling

Request

bash
curl -X POST "https://api.aisar.app/v1/channels/3/whatsapp-calling/enable" \
  -H "Authorization: Bearer YOUR_API_TOKEN"

Response

json
{
  "data": {
    "ok": true,
    "error": null,
    "error_code": null,
    "outbound_trunk_provisioned": true,
    "eligibility": {
      "calling": {
        "sip_status": "ENABLED",
        "can_receive_call_sip": "AVAILABLE",
        "active": true
      },
      "prerequisites": {
        "business_verified": true,
        "payment_ok": true,
        "name_approved": true,
        "number_registered": true
      },
      "blocking_issues": [],
      "can_enable": true,
      "name_status": "APPROVED",
      "checked_at": "2026-02-09T18:05:00Z"
    }
  }
}

Channel types and providers catalog

Request

bash
curl -X GET "https://api.aisar.app/v1/channel-types?published=true" \
  -H "Authorization: Bearer YOUR_API_TOKEN"

Response

json
{
  "data": [
    {
      "id": 1,
      "key": "whatsapp",
      "name": "WhatsApp",
      "description": "WhatsApp через QR-сопряжение",
      "category": "messaging",
      "order_id": 1,
      "badge_label": "Popular",
      "badge_variant": "primary",
      "is_published": true,
      "capabilities": { "send_text": true, "send_media": true },
      "limits": {},
      "provider_config": {
        "mark_online_on_connect": {
          "value": true,
          "label": "Отмечать онлайн при подключении",
          "description": "Показывать аккаунт онлайн, пока канал подключён"
        }
      }
    }
  ]
}

Templates for the chat picker (non-WABA channel)

Request

bash
# Для WhatsApp/Telegram/Instagram и т.п. отдаются опубликованные шаблоны
# компании того же channel_type — без привязки к каналу (channel_id: null)
# и без модерации Meta (meta_status: null). Фронт вставляет body как готовый
# текст в поле ввода, а не отправляет через POST /messages/send-template.
curl -X GET "https://api.aisar.app/v1/channels/3/templates/sendable" \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Accept: application/json"

Response

json
{
  "data": [
    {
      "id": 58,
      "company_id": 1,
      "template_group_id": 58,
      "channel_type_id": 1,
      "channel_type": { "id": 1, "key": "whatsapp", "name": "WhatsApp" },
      "channel": null,
      "name": "greeting",
      "slug": "greeting",
      "status": "published",
      "meta_status": null,
      "meta_status_reason": null,
      "last_synced_at": null,
      "language": "ru",
      "category": null,
      "body": "Здравствуйте, {{first_name}}! Чем можем помочь?",
      "parameters": [
        { "id": 101, "name": "first_name", "type": "text", "sample_value": "Иван", "position": 1 }
      ],
      "buttons": [],
      "uses_count": 12
    }
  ]
}

Blocked numbers (WhatsApp Business/Cloud)

Request

bash
curl -X GET "https://api.aisar.app/v1/channels/3/whatsapp/blocked-users?limit=50" \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Accept: application/json"

Response

json
{
  "data": {
    "numbers": ["+77001111111"],
    "next_cursor": "CURSOR_1"
  }
}

Blocked numbers (QR-based WhatsApp)

Request

bash
curl -X GET "https://api.aisar.app/v1/channels/7/whatsapp/blocked-users" \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Accept: application/json"

Response

json
{
  "data": {
    "numbers": ["77009998877@s.whatsapp.net"],
    "next_cursor": null
  }
}