Channels API
Channels connect Stagent to external messaging platforms. Each channel configuration stores credentials and connection details for a Slack workspace, Telegram bot, or generic webhook. Inbound endpoints receive messages from these platforms and route them through the gateway for agent processing.
Quick Start
Configure a Slack channel, test the connection, and send a message — a typical integration flow:
// 1. Create a Slack channel configuration
const createRes: Response = await fetch('/api/channels', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
name: 'Engineering Alerts',
channelType: 'slack',
config: {
webhookUrl: 'https://hooks.slack.com/services/T0001/B0001/xxxx',
},
}),
});
const channel: { id: string; channelType: string; testStatus: string } = await createRes.json();
// -> { id: "ch-3a4b-5c6d", channelType: "slack", testStatus: "untested", ... }
// 2. Test the connection — sends a test message through the adapter
const testRes: Response = await fetch(`/api/channels/${channel.id}/test`, {
method: 'POST',
});
const test: { testStatus: string } = await testRes.json();
console.log(`Test: ${test.testStatus}`); // "ok" or "failed"
// 3. Enable bidirectional messaging (receive Slack events too)
await fetch(`/api/channels/${channel.id}`, {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ direction: 'bidirectional' }),
});
// 4. List all configured channels
const listRes: Response = await fetch('/api/channels');
const channels: Array<{ name: string; channelType: string; status: string; testStatus: string }> = await listRes.json();
channels.forEach((ch) => {
console.log(`${ch.name} (${ch.channelType}) — ${ch.status} [${ch.testStatus}]`);
}); Base URL
/api/channels
Endpoints
List Channels
/api/channels Retrieve all channel configurations, newest first. Sensitive fields are masked in the response.
Response Body (Array)
| Field | Type | Req | Description |
|---|---|---|---|
| id | string (UUID) | * | Channel configuration identifier |
| channelType | enum | * | slack, telegram, or webhook |
| name | string | * | Channel display name |
| config | object | * | Channel-specific config (tokens masked) |
| status | enum | * | active or disabled |
| direction | enum | * | outbound or bidirectional |
| testStatus | enum | * | untested, ok, or failed |
| createdAt | ISO 8601 | * | Creation timestamp |
| updatedAt | ISO 8601 | * | Last modification |
List all channels to see their status and test results — tokens are masked in the response:
// List all channels and check their health
const res: Response = await fetch('http://localhost:3000/api/channels');
const channels: Array<{ name: string; channelType: string; status: string; direction: string; testStatus: string }> = await res.json();
channels.forEach((ch) => {
console.log(`${ch.name} (${ch.channelType})`);
console.log(` Status: ${ch.status}, Direction: ${ch.direction}`);
console.log(` Test: ${ch.testStatus}`);
}); Example response:
[
{
"id": "ch-3a4b-5c6d",
"channelType": "slack",
"name": "Engineering Alerts",
"config": { "webhookUrl": "https://hooks.slack.com/services/T.../B.../****" },
"status": "active",
"direction": "outbound",
"testStatus": "ok",
"createdAt": "2026-04-03T09:00:00.000Z",
"updatedAt": "2026-04-03T09:05:00.000Z"
}
] Create Channel
/api/channels Create a new channel configuration. Required config fields depend on the channel type.
Request Body
| Field | Type | Req | Description |
|---|---|---|---|
| name | string | * | Channel display name |
| channelType | enum | * | slack, telegram, or webhook |
| config | object | * | Channel-specific configuration |
Slack config requires: webhookUrl
Telegram config requires: botToken, chatId
Webhook config requires: url
Response 201 Created — Channel object (tokens masked)
Create a Slack channel for sending task completion alerts to your team:
// Configure a Slack channel
const res: Response = await fetch('http://localhost:3000/api/channels', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
name: 'Engineering Alerts',
channelType: 'slack',
config: { webhookUrl: 'https://hooks.slack.com/services/T0001/B0001/xxxx' },
}),
});
const channel: { id: string; testStatus: string } = await res.json();
console.log(`Channel created: ${channel.id} [${channel.testStatus}]`); Create a Telegram channel for mobile notifications:
// Configure a Telegram bot channel
const res: Response = await fetch('http://localhost:3000/api/channels', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
name: 'Mobile Alerts',
channelType: 'telegram',
config: { botToken: '7123456789:AAF...', chatId: '-1001234567890' },
}),
});
const channel: { id: string; testStatus: string } = await res.json(); Errors: 400 — Missing required fields
Get Channel
/api/channels/{id} Retrieve a single channel configuration by ID.
Response 200 — Channel object (tokens masked)
// Retrieve a single channel by ID
const res: Response = await fetch('http://localhost:3000/api/channels/ch-3a4b-5c6d');
const channel: { name: string; status: string; testStatus: string } = await res.json();
console.log(`${channel.name}: ${channel.status} (${channel.testStatus})`); Errors: 404 — Channel not found
Update Channel
/api/channels/{id} Update channel fields. Changing config resets testStatus to untested.
Request Body (all optional)
| Field | Type | Req | Description |
|---|---|---|---|
| name | string | — | Updated display name |
| config | object | — | Updated channel config (resets test status) |
| status | enum | — | active or disabled |
| direction | enum | — | outbound or bidirectional |
Response 200 — Updated channel object
Disable a channel temporarily or switch it to bidirectional mode:
// Enable bidirectional messaging
await fetch('http://localhost:3000/api/channels/ch-3a4b-5c6d', {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ direction: 'bidirectional' }),
}); Errors: 400 — Invalid field values, 404 — Not found
Delete Channel
/api/channels/{id} Permanently delete a channel configuration.
Response 200 — { "deleted": true }
// Permanently delete a channel configuration
await fetch('http://localhost:3000/api/channels/ch-3a4b-5c6d', { method: 'DELETE' }); Errors: 404 — Channel not found
Test Channel Connection
/api/channels/{id}/test Test connectivity to a channel by sending a test message through its adapter. Updates the channel's testStatus field.
Response Body
| Field | Type | Req | Description |
|---|---|---|---|
| testStatus | enum | * | ok or failed |
| error | string | — | Error message if test failed |
Send a test message to verify the channel is configured correctly — always test after creating or updating config:
// Test connectivity — sends a test message through the adapter
const res: Response = await fetch('http://localhost:3000/api/channels/ch-3a4b-5c6d/test', {
method: 'POST',
});
const { testStatus, error }: { testStatus: string; error?: string } = await res.json();
if (testStatus === 'ok') {
console.log('Channel is working — test message sent');
} else {
console.error(`Test failed: ${error}`);
} Example response (success):
{
"testStatus": "ok"
}Example response (failure):
{
"testStatus": "failed",
"error": "Webhook returned 403 — check the webhook URL is still valid"
}Errors: 404 — Channel not found, 500 — Adapter error
Inbound Webhooks
Slack Inbound
/api/channels/inbound/slack?configId={id} Receives Slack Events API callbacks. Handles url_verification challenges and event_callback messages. Verifies request signatures using the channel's signingSecret. Responds within 3 seconds per Slack requirements.
Query Parameters
| Param | Type | Req | Description |
|---|---|---|---|
| configId | string | * | Channel config ID to route the message to |
Response 200 — { "ok": true } (or challenge response)
Errors: 400 — Missing configId or invalid JSON, 401 — Missing signingSecret, 403 — Invalid signature, 404 — Channel not found
Telegram Inbound
/api/channels/inbound/telegram?configId={id}&secret={token} Receives Telegram Bot API webhook updates. Verifies the shared secret token. Bot messages are silently skipped to prevent loops.
Query Parameters
| Param | Type | Req | Description |
|---|---|---|---|
| configId | string | * | Channel config ID to route the message to |
| secret | string | * | Shared webhook secret for verification |
Response 200 — { "ok": true }
Errors: 400 — Missing configId, 401 — Missing webhookSecret in config, 403 — Invalid secret, 404 — Channel not found
Telegram Poll (Internal)
/api/channels/inbound/telegram/poll?configId={id} Poll Telegram getUpdates API for local development when webhooks can't reach localhost. Internal-only — requires x-stagent-internal: poll header.
Query Parameters
| Param | Type | Req | Description |
|---|---|---|---|
| configId | string | * | Channel config ID to poll for |
Request Body
| Field | Type | Req | Description |
|---|---|---|---|
| offset | number | — | Telegram update offset for pagination |
Response Body
| Field | Type | Req | Description |
|---|---|---|---|
| processed | number | * | Number of messages processed |
| total | number | * | Total updates received from Telegram |
| nextOffset | number | * | Offset for the next poll request |
Poll for Telegram messages during local development when webhooks cannot reach localhost:
// Poll Telegram for local development
const res: Response = await fetch('http://localhost:3000/api/channels/inbound/telegram/poll?configId=ch-3a4b-5c6d', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-stagent-internal': 'poll',
},
body: JSON.stringify({ offset: 0 }),
});
const poll: { processed: number; total: number; nextOffset: number } = await res.json();
console.log(`Processed ${poll.processed} of ${poll.total} updates`);
console.log(`Next offset: ${poll.nextOffset}`); Errors: 401 — Missing internal header, 403 — Channel not active, 502 — Telegram API error
Channel Types
| Type | Required Config | Direction |
|---|---|---|
| slack | webhookUrl, signingSecret (for inbound) | Outbound or bidirectional |
| telegram | botToken, chatId, webhookSecret (for inbound) | Outbound or bidirectional |
| webhook | url | Outbound only |