---
title: "Message channels API: IM chats and notifications"
description: "Halo HTTP API for message channels and notifications: connect IM channels, bind chats to Digital Humans, read chat transcripts, test notifications."
type: "reference"
audience: ["developer"]
lang: "en"
canonical: "https://hello-halo.cc/docs/en/developers/api/channels.html"
last_updated: "2026-10-19T00:11:17+08:00"
source: "Halo 3.0.4 docs"
---
# API: Message channels

Halo HTTP API for message channels and notifications: connect IM channels, bind chats to Digital Humans, read chat transcripts, test notifications.

Message channels connect Digital Humans to IM platforms such as WeCom, Feishu and WeChat; notifications send alerts out. Use these operations to connect and disconnect channels, bind chats to Digital Humans, read the transcripts of bound chats, and test notifications. Create channels and enter their credentials in **Settings › Message Channels**.

Paths are relative to your Halo base URL, and every request carries the access password. Set `$HALO_URL` and `$HALO_TOKEN` as in the [quickstart](https://hello-halo.cc/docs/en/developers/quickstart.html); base URL, authentication and error conventions are in the [HTTP API overview](https://hello-halo.cc/docs/en/developers/api/).

| Operation | Endpoint |
| --- | --- |
| [Clear a digital human's chat history in a bound IM chat](https://hello-halo.cc/docs/en/developers/api/digital-human.html#post-api-apps-appid-im-chat-clear) (Digital Humans)  (Irreversible) | `POST /api/apps/:appId/im-chat/clear` |
| [Read a digital human's transcript in a bound IM chat](https://hello-halo.cc/docs/en/developers/api/digital-human.html#get-api-apps-appid-im-chat-messages) (Digital Humans) | `GET /api/apps/:appId/im-chat/messages` |
| [Stop a digital human's in-progress reply in a bound IM chat (keeps history)](https://hello-halo.cc/docs/en/developers/api/digital-human.html#post-api-apps-appid-im-chat-stop) (Digital Humans) | `POST /api/apps/:appId/im-chat/stop` |
| [Check whether a Feishu bot is actually reachable, not just connected](#post-api-feishu-bot-reachability) | `POST /api/feishu-bot/reachability` |
| [Get this build's default guest-access permission settings for new IM instances](#get-api-im-channels-permission-defaults) | `GET /api/im-channels/permission-defaults` |
| [List available IM channel provider types and their config fields](#get-api-im-channels-providers) | `GET /api/im-channels/providers` |
| [Reconnect one IM channel instance](#post-api-im-channels-reconnect) | `POST /api/im-channels/reconnect` |
| [Reload all IM channel instances from saved config](#post-api-im-channels-reload) | `POST /api/im-channels/reload` |
| [Get connection status for all IM channel instances, or one via ?instanceId=](#get-api-im-channels-status) | `GET /api/im-channels/status` |
| [List every chat thread a digital human has, its own and IM ones](#get-api-im-sessions) | `GET /api/im-sessions` |
| [List the IM chats a digital human pushes to through another digital human's bot](#get-api-im-sessions-linked) | `GET /api/im-sessions/linked` |
| [Remove a chat binding from the IM session registry](#post-api-im-sessions-remove) | `POST /api/im-sessions/remove` |
| [Set a custom display name for a chat binding](#post-api-im-sessions-set-custom-name) | `POST /api/im-sessions/set-custom-name` |
| [Toggle whether a run's result is auto-pushed to this chat when it finishes](#post-api-im-sessions-set-proactive) | `POST /api/im-sessions/set-proactive` |
| [Let a digital human push to an IM chat another digital human's bot knows, or stop it](#post-api-im-sessions-set-push-link) | `POST /api/im-sessions/set-push-link` |
| [Drop cached notification-channel access tokens](#post-api-notify-channels-clear-cache) | `POST /api/notify-channels/clear-cache` |
| [Read the product-level notification channel customisation](#get-api-notify-channels-product-config) | `GET /api/notify-channels/product-config` |
| [Send a test notification through one configured channel](#post-api-notify-channels-test) | `POST /api/notify-channels/test` |
| [Reconnect all configured WeCom bot instances](#post-api-wecom-bot-reconnect) | `POST /api/wecom-bot/reconnect` |
| [Check whether a WeCom bot channel is configured and connected](#get-api-wecom-bot-status) | `GET /api/wecom-bot/status` |
| [Clear WeChat iLink credentials for an instance and stop it](#post-api-weixin-ilink-disconnect) | `POST /api/weixin-ilink/disconnect` |
| [Set the bot token for an already-configured WeChat iLink instance](#post-api-weixin-ilink-save-token) | `POST /api/weixin-ilink/save-token` |

## Check whether a Feishu bot is actually reachable, not just connected {#post-api-feishu-bot-reachability}

`POST /api/feishu-bot/reachability`  (Assistant)

- A Feishu app awaiting administrator release connects normally and receives nothing, so "connected" alone does not mean the bot works. inboundCount 0 with a long connectedSinceMs is the signature of that case. No credentials in this response.

**Request body**

```json
{
  "instanceId": "<instanceId — from GET /api/im-channels/status>"
}
```

**Response**

```ts
{success:true,data:{state,connectedSinceMs,lastInboundAgoMs,inboundCount,lastError?}}
```

**Example**

```bash
curl -sX POST "$HALO_URL/api/feishu-bot/reachability" \
  -H "Authorization: Bearer $HALO_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"instanceId":"<instanceId — from GET /api/im-channels/status>"}'
```

## Get this build's default guest-access permission settings for new IM instances {#get-api-im-channels-permission-defaults}

`GET /api/im-channels/permission-defaults`  (Assistant)

**Response**

```ts
{success:true,data:{defaultEnabled?,defaultGuestAccess?,defaultGuestPolicy?:{allowedTools?},ownerIdHint?,ownerSetupGuideUrl?} | null}
```

**Example**

```bash
curl -s "$HALO_URL/api/im-channels/permission-defaults" \
  -H "Authorization: Bearer $HALO_TOKEN"
```

## List available IM channel provider types and their config fields {#get-api-im-channels-providers}

`GET /api/im-channels/providers`  (Assistant)

- Schema only. No endpoint here creates an instance from it — the user creates instances in Settings &gt; Message Channels. Schema only (field definitions and empty defaults) — no configured instance data or secrets.

**Response**

```ts
{success:true,data:[{type,displayName,description,direction,configFields:[{key,label,type,placeholder?,required?}],defaultConfig}]}
```

**Example**

```bash
curl -s "$HALO_URL/api/im-channels/providers" \
  -H "Authorization: Bearer $HALO_TOKEN"
```

## Reconnect one IM channel instance {#post-api-im-channels-reconnect}

`POST /api/im-channels/reconnect`  (Reversible)  (Assistant)

**Request body**

```json
{
  "instanceId": "<instanceId — from GET /api/im-channels/status>"
}
```

**Response**

```ts
{success:boolean,error?}
```

**Errors**

- 400 if instanceId is missing. success:false with an error message if the instanceId is unknown.

**Example**

```bash
curl -sX POST "$HALO_URL/api/im-channels/reconnect" \
  -H "Authorization: Bearer $HALO_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"instanceId":"<instanceId — from GET /api/im-channels/status>"}'
```

## Reload all IM channel instances from saved config {#post-api-im-channels-reload}

`POST /api/im-channels/reload`  (Reversible)  (Assistant)

- Use after a config change made through another route to apply it without restarting Halo. Stops and rebuilds any instance whose config changed — brief interruption, no data lost.

**Response**

```ts
{success:true}
```

**Example**

```bash
curl -sX POST "$HALO_URL/api/im-channels/reload" \
  -H "Authorization: Bearer $HALO_TOKEN"
```

## Get connection status for all IM channel instances, or one via ?instanceId= {#get-api-im-channels-status}

`GET /api/im-channels/status`  (Assistant)

- No credentials in this response. instanceId not found → {success:false,error}.

**Query parameters**: `instanceId`

**Response**

```ts
{success:true,data:{id,type,enabled,connected,state?,appId,appName?,reason?,identityResolution?}[] | <single instance object>}
```

**Example**

```bash
curl -s "$HALO_URL/api/im-channels/status" \
  -H "Authorization: Bearer $HALO_TOKEN"
```

## List every chat thread a digital human has, its own and IM ones {#get-api-im-sessions}

`GET /api/im-sessions`  (Assistant)

- Optional ?appId= narrows it to one digital human. This is the only listing of its threads — there is no sessions/list beside sessions/create.
- source says what a thread is: "local" = a named thread the user created in Halo, "im" = a real chat with a person on an IM platform, "http" = created through this API.
- Reading or writing one thread needs a conversationId, which is not a field here — build it from four fields of the record: app-chat:&lt;appId&gt;:&lt;channel&gt;:&lt;chatType&gt;:&lt;chatId&gt;.
- Only "local" and "http" threads accept a message from this API. An "im" thread is refused on send — this API must not put words into a real conversation with a person.

**Query parameters**: `appId`

**Response**

```ts
{success:true,data:[{appId,channel,source:"local"|"im"|"http",chatType:"direct"|"group",chatId,displayName,customName?,proactive,instanceId,pushLinks?:[{appId,autoSync}]}]}
```

**Example**

```bash
curl -s "$HALO_URL/api/im-sessions" \
  -H "Authorization: Bearer $HALO_TOKEN"
```

## List the IM chats a digital human pushes to through another digital human's bot {#get-api-im-sessions-linked}

`GET /api/im-sessions/linked`  (Assistant)

- Each record is the session as the bot's own digital human holds it (its appId is that one). The asking digital human's entry in pushLinks says whether its run results are auto-pushed there.

**Query parameters**: `appId` (required)

**Response**

```ts
{success:true,data:[{appId,channel,chatType,chatId,displayName,instanceId,pushLinks:[{appId,autoSync}]}]}
```

**Example**

```bash
curl -s "$HALO_URL/api/im-sessions/linked?appId=<appId — a uuid from GET /api/apps>" \
  -H "Authorization: Bearer $HALO_TOKEN"
```

## Remove a chat binding from the IM session registry {#post-api-im-sessions-remove}

`POST /api/im-sessions/remove`  (Reversible)  (Assistant)

- removed:false means no matching session was found — not an error. Only removes display metadata (custom name, proactive flag, other digital humans' push links to it); the chat re-registers itself on its next inbound message, message history is not stored here.

**Request body**

```json
{
  "appId": "<appId — a uuid from GET /api/apps>",
  "channel": "wecom-bot",
  "chatId": "<chatId — from GET /api/im-sessions>"
}
```

**Response**

```ts
{success:true,data:{removed:boolean}}
```

**Example**

```bash
curl -sX POST "$HALO_URL/api/im-sessions/remove" \
  -H "Authorization: Bearer $HALO_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"appId":"<appId — a uuid from GET /api/apps>","channel":"wecom-bot","chatId":"<chatId — from GET /api/im-sessions>"}'
```

## Set a custom display name for a chat binding {#post-api-im-sessions-set-custom-name}

`POST /api/im-sessions/set-custom-name`  (Reversible)  (Assistant)

**Request body**

```json
{
  "appId": "<appId — a uuid from GET /api/apps>",
  "channel": "wecom-bot",
  "chatId": "<chatId — from GET /api/im-sessions>",
  "name": "Sales Team Chat"
}
```

**Response**

```ts
{success:true} or {success:false,error}
```

**Errors**

- 404 if no matching session exists yet.

**Example**

```bash
curl -sX POST "$HALO_URL/api/im-sessions/set-custom-name" \
  -H "Authorization: Bearer $HALO_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"appId":"<appId — a uuid from GET /api/apps>","channel":"wecom-bot","chatId":"<chatId — from GET /api/im-sessions>","name":"Sales Team Chat"}'
```

## Toggle whether a run's result is auto-pushed to this chat when it finishes {#post-api-im-sessions-set-proactive}

`POST /api/im-sessions/set-proactive`  (Reversible)  (Assistant)

**Request body**

```json
{
  "appId": "<appId — a uuid from GET /api/apps>",
  "channel": "wecom-bot",
  "chatId": "<chatId — from GET /api/im-sessions>",
  "proactive": true
}
```

**Response**

```ts
{success:true} or {success:false,error}
```

**Errors**

- 404 if no matching session exists yet — the chat must have messaged the digital human at least once.

**Example**

```bash
curl -sX POST "$HALO_URL/api/im-sessions/set-proactive" \
  -H "Authorization: Bearer $HALO_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"appId":"<appId — a uuid from GET /api/apps>","channel":"wecom-bot","chatId":"<chatId — from GET /api/im-sessions>","proactive":true}'
```

## Let a digital human push to an IM chat another digital human's bot knows, or stop it {#post-api-im-sessions-set-push-link}

`POST /api/im-sessions/set-push-link`  (Reversible)  (Assistant)

- link:{autoSync} adds the chat to the digital human's push targets or updates it; autoSync:true also pushes its run results there. link:null removes it.
- Replies in that chat still go to the digital human the bot answers for. 404 when the session does not exist, is not an IM chat, or is the digital human's own.

**Request body**

```json
{
  "appId": "<appId of the digital human that pushes>",
  "session": {
    "appId": "<the session's own appId>",
    "channel": "wecom-bot",
    "chatId": "<chatId — from GET /api/im-sessions>"
  },
  "link": {
    "autoSync": false
  }
}
```

**Response**

```ts
{success:true} or {success:false,error}
```

**Example**

```bash
curl -sX POST "$HALO_URL/api/im-sessions/set-push-link" \
  -H "Authorization: Bearer $HALO_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"appId":"<appId of the digital human that pushes>","session":{"appId":"<the session'\''s own appId>","channel":"wecom-bot","chatId":"<chatId — from GET /api/im-sessions>"},"link":{"autoSync":false}}'
```

## Drop cached notification-channel access tokens {#post-api-notify-channels-clear-cache}

`POST /api/notify-channels/clear-cache`  (Reversible)  (Assistant)

- Worth trying when a channel that used to work starts failing to authenticate — the next send fetches a fresh token. It changes no configuration.

**Response**

```ts
{"success":true}  // no data
```

**Example**

```bash
curl -sX POST "$HALO_URL/api/notify-channels/clear-cache" \
  -H "Authorization: Bearer $HALO_TOKEN"
```

## Read the product-level notification channel customisation {#get-api-notify-channels-product-config}

`GET /api/notify-channels/product-config`  (Assistant)

- Presentation only — help links this build shows per channel. data is null when the build customises nothing. It does not say which channels the user configured.

**Response**

```ts
{"success":true,"data":{"wecom":{"docs":{...}},"email":{"docs":{...}}}}
```

**Example**

```bash
curl -s "$HALO_URL/api/notify-channels/product-config" \
  -H "Authorization: Bearer $HALO_TOKEN"
```

## Send a test notification through one configured channel {#post-api-notify-channels-test}

`POST /api/notify-channels/test`  (Reversible)  (Assistant)

- `channelType` is one of `wecom`, `dingtalk`, `feishu`, `email` or `webhook`.
- Delivers a real message: a fixed test text through that channel. The text cannot be changed, so this is a test, not a way to send notifications.
- "No notification channels configured" means none is set up yet; channels are set up in **Settings › Message Channels**.

**Request body**

```json
{
  "channelType": "wecom"
}
```

**Response**

```ts
{"success":true,"data":{...delivery result}}
```

**Example**

```bash
curl -sX POST "$HALO_URL/api/notify-channels/test" \
  -H "Authorization: Bearer $HALO_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"channelType":"wecom"}'
```

## Reconnect all configured WeCom bot instances {#post-api-wecom-bot-reconnect}

`POST /api/wecom-bot/reconnect`  (Reversible)  (Assistant)

- A brief connection interruption while it reconnects — no data lost.

**Response**

```ts
{success:true}
```

**Example**

```bash
curl -sX POST "$HALO_URL/api/wecom-bot/reconnect" \
  -H "Authorization: Bearer $HALO_TOKEN"
```

## Check whether a WeCom bot channel is configured and connected {#get-api-wecom-bot-status}

`GET /api/wecom-bot/status`  (Assistant)

- Legacy single-bot view (booleans only, no token). For per-instance detail use [`GET /api/im-channels/status`](#get-api-im-channels-status).

**Response**

```ts
{success:true,data:{configured:boolean,enabled:boolean,connected:boolean}}
```

**Example**

```bash
curl -s "$HALO_URL/api/wecom-bot/status" \
  -H "Authorization: Bearer $HALO_TOKEN"
```

## Clear WeChat iLink credentials for an instance and stop it {#post-api-weixin-ilink-disconnect}

`POST /api/weixin-ilink/disconnect`  (Reversible)  (Assistant)

- Clears the WeChat Bot credentials of this channel and stops it. Reconnecting needs the same token again ([`POST /api/weixin-ilink/save-token`](#post-api-weixin-ilink-save-token)) or a new QR pairing in **Settings › Message Channels**.

**Request body**

```json
{
  "instanceId": "<instanceId — from GET /api/im-channels/status>"
}
```

**Response**

```ts
{success:true} or {success:false,error}
```

**Example**

```bash
curl -sX POST "$HALO_URL/api/weixin-ilink/disconnect" \
  -H "Authorization: Bearer $HALO_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"instanceId":"<instanceId — from GET /api/im-channels/status>"}'
```

## Set the bot token for an already-configured WeChat iLink instance {#post-api-weixin-ilink-save-token}

`POST /api/weixin-ilink/save-token`  (Reversible)  (Assistant)

- Optional fields: baseUrl, accountId.
- instanceId must already exist (created earlier in the Settings UI) — this route cannot create a new instance, only fill in its token.
- Never echoes the token back in the response.
- {success:false,error:"..."} when instanceId is not found in config.
- Takes over that channel identity immediately — a wrong token silently hijacks it until corrected.

**Request body**

```json
{
  "instanceId": "<instanceId — from GET /api/im-channels/status>",
  "botToken": "..."
}
```

**Response**

```ts
{success:true} or {success:false,error}
```

**Example**

```bash
curl -sX POST "$HALO_URL/api/weixin-ilink/save-token" \
  -H "Authorization: Bearer $HALO_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"instanceId":"<instanceId — from GET /api/im-channels/status>","botToken":"..."}'
```
