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; base URL, authentication and error conventions are in the HTTP API overview.
Check whether a Feishu bot is actually reachable, not just connected
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
{
"instanceId": "<instanceId — from GET /api/im-channels/status>"
}Response
{success:true,data:{state,connectedSinceMs,lastInboundAgoMs,inboundCount,lastError?}}Example
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 Assistant
Response
{success:true,data:{defaultEnabled?,defaultGuestAccess?,defaultGuestPolicy?:{allowedTools?},ownerIdHint?,ownerSetupGuideUrl?} | null}Example
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 Assistant
- Schema only. No endpoint here creates an instance from it — the user creates instances in Settings > Message Channels. Schema only (field definitions and empty defaults) — no configured instance data or secrets.
Response
{success:true,data:[{type,displayName,description,direction,configFields:[{key,label,type,placeholder?,required?}],defaultConfig}]}Example
curl -s "$HALO_URL/api/im-channels/providers" \
-H "Authorization: Bearer $HALO_TOKEN"Reconnect one IM channel instance
POST /api/im-channels/reconnect Reversible Assistant
Request body
{
"instanceId": "<instanceId — from GET /api/im-channels/status>"
}Response
{success:boolean,error?}Errors
- 400 if instanceId is missing. success:false with an error message if the instanceId is unknown.
Example
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 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
{success:true}Example
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 Assistant
- No credentials in this response. instanceId not found → {success:false,error}.
Query parameters: instanceId
Response
{success:true,data:{id,type,enabled,connected,state?,appId,appName?,reason?,identityResolution?}[] | <single instance object>}Example
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 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:<appId>:<channel>:<chatType>:<chatId>.
- 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
{success:true,data:[{appId,channel,source:"local"|"im"|"http",chatType:"direct"|"group",chatId,displayName,customName?,proactive,instanceId,pushLinks?:[{appId,autoSync}]}]}Example
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 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
{success:true,data:[{appId,channel,chatType,chatId,displayName,instanceId,pushLinks:[{appId,autoSync}]}]}Example
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 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
{
"appId": "<appId — a uuid from GET /api/apps>",
"channel": "wecom-bot",
"chatId": "<chatId — from GET /api/im-sessions>"
}Response
{success:true,data:{removed:boolean}}Example
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 Reversible Assistant
Request body
{
"appId": "<appId — a uuid from GET /api/apps>",
"channel": "wecom-bot",
"chatId": "<chatId — from GET /api/im-sessions>",
"name": "Sales Team Chat"
}Response
{success:true} or {success:false,error}Errors
- 404 if no matching session exists yet.
Example
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 Reversible Assistant
Request body
{
"appId": "<appId — a uuid from GET /api/apps>",
"channel": "wecom-bot",
"chatId": "<chatId — from GET /api/im-sessions>",
"proactive": true
}Response
{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
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 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
{
"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
{success:true} or {success:false,error}Example
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 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
{"success":true} // no dataExample
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 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
{"success":true,"data":{"wecom":{"docs":{...}},"email":{"docs":{...}}}}Example
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 Reversible Assistant
channelTypeis one ofwecom,dingtalk,feishu,emailorwebhook.- 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
{
"channelType": "wecom"
}Response
{"success":true,"data":{...delivery result}}Example
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 Reversible Assistant
- A brief connection interruption while it reconnects — no data lost.
Response
{success:true}Example
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 Assistant
- Legacy single-bot view (booleans only, no token). For per-instance detail use
GET /api/im-channels/status.
Response
{success:true,data:{configured:boolean,enabled:boolean,connected:boolean}}Example
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 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) or a new QR pairing in Settings › Message Channels.
Request body
{
"instanceId": "<instanceId — from GET /api/im-channels/status>"
}Response
{success:true} or {success:false,error}Example
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 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
{
"instanceId": "<instanceId — from GET /api/im-channels/status>",
"botToken": "..."
}Response
{success:true} or {success:false,error}Example
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":"..."}'