Channels
Channels bind a project to an external messaging platform — currently Telegram and WeChat — so an agent in the project can answer in the user's chat app of choice. Credentials are AES-256-GCM encrypted at the engine layer; the database never sees plaintext.
All channel endpoints are JWT-only: connecting a chat-platform identity is owner-level configuration.
See also: Invites and members — both pages cover project-config concerns. Set up agent membership before binding a channel.
Lifecycle
| Status | Meaning |
|---|---|
disabled |
Created but not yet ready (default for fresh WeChat channels — needs QR scan). |
awaiting_scan |
WeChat-only: QR code is live, waiting for a phone scan. |
active |
Bound and routing messages. |
expired |
WeChat session lost; needs a fresh QR. |
Common endpoints
POST /api/projects/{id}/channels
Create a channel. The credential payload variant must match channel_type.
Auth: Owner JWT.
POST /api/projects/3a7b5c1d-.../channels
Authorization: Bearer eyJhbGc...
Content-Type: application/json
{
"channel_type": "telegram",
"display_name": "Support Bot",
"primary_agent_id": "8f4d2e1a-...",
"credential": { "type": "telegram", "bot_token": "1234567890:ABCdef..." },
"config": {}
}
{
"channel": {
"id": "c0d1e2f3-...",
"project_id": "3a7b5c1d-...",
"channel_type": "telegram",
"display_name": "Support Bot",
"primary_agent_id": "8f4d2e1a-...",
"status": "active",
"config": {},
"created_at": "2026-04-29T10:00:00Z"
}
}
For WeChat the credential is { "type": "wechat" } (no token — auth is done out-of-band via QR scan). Fresh WeChat channels are created in disabled status.
GET /api/projects/{id}/channels
List the project's channels.
Auth: Owner JWT.
{ "channels": [ { "id": "c0d1e2f3-...", "channel_type": "telegram", "display_name": "Support Bot", "status": "active", "...": "..." } ] }
GET /api/projects/{id}/channels/{channel_id}
Fetch one channel.
Auth: Owner JWT.
PATCH /api/projects/{id}/channels/{channel_id}
Update display name, primary agent, config blob, or status.
Auth: Owner JWT.
PATCH /api/projects/3a7b5c1d-.../channels/c0d1e2f3-...
Authorization: Bearer eyJhbGc...
Content-Type: application/json
{
"display_name": "Support Bot v2",
"primary_agent_id": "9b3c4d5e-...",
"config": {},
"status": "active"
}
DELETE /api/projects/{id}/channels/{channel_id}
Remove a channel. WeChat channels are disconnected at the driver layer first (so the Docker container is reaped), then the row is deleted. 204 No Content.
Auth: Owner JWT.
WeChat QR flow
WeChat doesn't expose a stable bot-token like Telegram — instead, you scan a QR code with the WeChat phone app to bind a session. The flow is per-channel.
POST /api/projects/{id}/channels/{channel_id}/wechat/qr
Start (or restart) the QR login. Returns the QR payload to render.
Auth: Owner JWT.
POST /api/projects/3a7b5c1d-.../channels/c0d1e2f3-.../wechat/qr
Authorization: Bearer eyJhbGc...
{
"qr_id": "qr_abc123",
"qr_payload": "https://login.weixin.qq.com/l/...",
"expires_at": "2026-04-29T10:03:00Z"
}
Errors (all {"error": "<msg>"}):
400— channel is not a WeChat channel.409— already bound; disconnect first.503— WeChat integration is disabled on this server, or upstream login service is down.504— QR not produced in time; retry.
GET /api/projects/{id}/channels/{channel_id}/wechat/status
Poll until binding completes. The channel row's status is mirrored to the same phase so the UI can avoid duplicate polling.
Auth: Owner JWT.
{ "phase": "awaiting_scan", "qr_id": "qr_abc123", "qr_payload": "...", "expires_at": "2026-04-29T10:03:00Z" }
phase cycles through none → awaiting_scan → scanning → bound. A bound response includes ilink_bot_id and bound_at. expired means you need a fresh QR.
DELETE /api/projects/{id}/channels/{channel_id}/wechat
Unbind the WeChat session without deleting the channel row. 204 No Content. The channel goes to disabled; you can run the QR flow again to rebind.
Auth: Owner JWT.
Message log
GET /api/projects/{id}/channels/{channel_id}/messages
Most-recent-first slice of inbound + outbound messages, so the dashboard can render a transcript without scraping the platform side.
Query: ?limit=N (default 100, clamped to [1, 500]).
Auth: Owner JWT.
curl "https://api.juglans.ai/api/projects/3a7b5c1d-.../channels/c0d1e2f3-.../messages?limit=50" \
-H "Authorization: Bearer eyJhbGc..."
{
"messages": [
{
"id": "m0n1o2p3-...",
"channel_id": "c0d1e2f3-...",
"direction": "inbound",
"platform_user_id": "tg:88102",
"platform_chat_id": "tg:88102",
"username": "alice",
"text": "hello",
"agent_id": null,
"media_type": null,
"media_path": null,
"error": null,
"created_at": "2026-04-29T09:45:00Z"
}
]
}