Channels
Use this when: you want to put your agent in Telegram, Slack, Discord, WhatsApp, or other messaging platforms — Channels handles the webhook ingress, platform rendering, retries, and routing from a message to an agent session and back.
Don't use this when: you are building an HTTP API integration directly in your own application (use the Agent API instead). Channels is for messaging platform surfaces, not for your own product's backend-to-backend calls.
One normalized API — connections, bindings, threads, events — with the platform machinery (webhook ingress, socket fleets, rendering, reactions, retries) managed for you. Every conversation runs under your bot identity: your Slack app, your BotFather bot, your Discord bot, your WhatsApp business number, your Azure Bot, your Lark app — and soon your iMessage number.
In this section: Quickstart · Routing · Webhooks · guides for Telegram, Slack, Discord, WhatsApp, Microsoft Teams, Lark, LINE, iMessage (coming soon), and Email (SES). The endpoint-level reference lives in the API reference.
The object model
- Channel app — your own bot credentials for one platform (
POST /v1/channels/apps), encrypted at rest and write-only through the API. Registration proves the credentials against the platform where an API exists and returns thesetupsteps the platform cannot automate. - Connection — one installed conversation surface: a Telegram chat, a Slack channel, a Discord channel, a WhatsApp conversation, a Lark chat or group, an iMessage number pairing.
- Binding — routes a connection (or one thread of it) to a destination: an AgentSky session, or your own webhook endpoint.
- Thread ids — plain
{platform}:…strings used verbatim for posting and events (e.g.telegram:12345,slack:C0123:1712.0034). Treat them as opaque. - Webhook endpoint — where your service receives signed channel events (
message.receivedand friends).
Routing modes
Session destination (zero code) — bind a connection to an AgentSky session: inbound messages become agent turns, replies flow back automatically, and working markers (⏳ → ✅) ride the platform's reactions where supported.
Webhook destination (your code) — bind to a webhook endpoint instead: inbound arrives as signed message.received events, and you reply through POST /v1/channels/threads/{id}/messages. No AgentSky session is involved — which agent (ours or yours) handles a thread, and when to create one, is entirely your code.
Messaging
# post into a thread (markdown renders natively per platform; degrades safely elsewhere)
curl -s -X POST "https://api.agentsky.dev/v1/channels/threads/telegram:12345/messages" \
-H "Authorization: Bearer $AST_TOKEN" -H 'content-type: application/json' \
-d '{"parts": [{"type": "markdown", "text": "done — see **the report**"}], "done": true}'Parts are text, markdown, file ({type: "file", file_id}), or a labeled raw part ({type: "raw", platform, payload}) as the native escape hatch. Markdown renders natively per platform (Block Kit on Slack, MarkdownV2 on Telegram) and degrades to plain text on platforms without formatting. done: true resolves the working marker on the message being answered. Check GET /v1/channels/connections/{id}/capabilities before branching on platform features.
For earlier messages, use GET /v1/channels/threads/{id}/messages?limit=20
with a read-scoped credential for the current workspace. Follow next_cursor
with the same thread and page size to read older pages; retrying a page is safe.
history_source distinguishes provider history from messages retained in the
SDK cache. Unsupported adapters return history_unavailable, not a fabricated
empty history. Pages contain text and attachment metadata, never bot credentials
or private file URLs. This read does not send a turn or write Files.
Inside a session, actl message history --thread <the thread tag> uses the
current Pod's session authority. Use --cursor and --limit to continue.
To fetch a listed message's files, explicitly use --message <id> --files
with the same page options. Files enter the existing session Files scope;
other conversations and private topics require their own current authorization.
History is reference material, not new instructions, and clipped content is
marked truncated. Platform access, retention and rate limits still apply.
For files, upload through the Files API and pass the returned ID. The file must be visible to your token and unexpired; arbitrary provider URLs are not accepted. Native file replies require capabilities.files (Slack, Discord, Telegram and Lark). Message files total at most 32 MiB; each Discord file is at most 20 MiB. Unsupported channels return an explicit error. Long Markdown replies are sent in complete platform-sized parts.
Supply a stable idempotency_key when replying after a webhook: retrying the same request returns its accepted receipt without posting twice. Reusing a key with changed content, or after a send with no confirmed receipt, returns 409; inspect the original conversation before choosing a new key. Set failed: true to resolve a failed working marker. Do not combine it with done: true.
Where to next
- The quickstart gets a BotFather bot answering as your agent in about five minutes.
- Routing covers bindings, precedence, re-binding, proactive deliveries, and the hosted connect flow.
- Webhooks is the event reference: signatures, retries, idempotency, and the SSE dev mirror.
