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 noneawaiting_scanscanningbound. 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"
    }
  ]
}