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

GET /api/channels

Retrieve all channel configurations, newest first. Sensitive fields are masked in the response.

Response Body (Array)

FieldTypeReqDescription
idstring (UUID)*Channel configuration identifier
channelTypeenum*slack, telegram, or webhook
namestring*Channel display name
configobject*Channel-specific config (tokens masked)
statusenum*active or disabled
directionenum*outbound or bidirectional
testStatusenum*untested, ok, or failed
createdAtISO 8601*Creation timestamp
updatedAtISO 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

POST /api/channels

Create a new channel configuration. Required config fields depend on the channel type.

Request Body

FieldTypeReqDescription
namestring*Channel display name
channelTypeenum*slack, telegram, or webhook
configobject*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

GET /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

PATCH /api/channels/{id}

Update channel fields. Changing config resets testStatus to untested.

Request Body (all optional)

FieldTypeReqDescription
namestringUpdated display name
configobjectUpdated channel config (resets test status)
statusenumactive or disabled
directionenumoutbound 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

DELETE /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

POST /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

FieldTypeReqDescription
testStatusenum*ok or failed
errorstringError 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

POST /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

POST /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)

POST /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

FieldTypeReqDescription
offsetnumberTelegram update offset for pagination

Response Body

FieldTypeReqDescription
processednumber*Number of messages processed
totalnumber*Total updates received from Telegram
nextOffsetnumber*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

TypeRequired ConfigDirection
slackwebhookUrl, signingSecret (for inbound)Outbound or bidirectional
telegrambotToken, chatId, webhookSecret (for inbound)Outbound or bidirectional
webhookurlOutbound only