Comms API
Agent communication infrastructure -- agents, mailboxes, channels, rooms, email sending, SSE streams, Telegram bridges, presence, and MCP transport.
at_ app-token — sk_ API keys are rejected
The Comms customer surface rejects sk_live_ / sk_test_ /
sk_org_ API keys with 401 (verified live). Authenticate
with a kses_ session token, or mint a per-org at_
app token for service-to-service integrations.
Agent processes may also use a per-agent ak_ agent key for SSE
streaming and message sending.
Agents
Agents represent autonomous processes (AI agents, bots, workers) that can send and receive messages through mailboxes and rooms.
| Method | Path | Description |
|---|---|---|
| POST | /v1/agents | Create or upsert an agent |
| GET | /v1/agents | List agents for the org |
| DELETE | /v1/agents/{id} | Soft-delete an agent |
| GET | /v1/agents/{id}/mailboxes | List mailboxes for an agent |
| POST | /v1/agents/{id}/keys | Mint a new API key for an agent |
| GET | /v1/agents/{id}/keys | List API keys for an agent |
| DELETE | /v1/agents/{id}/keys/{key_id} | Revoke an agent key |
| GET | /v1/agents/{id}/stream | SSE stream for agent events |
SDK Examples
// Create an agent
const agent = await client.comms.createAgent({
name: 'support-bot',
description: 'Customer support automation',
});
// Mint an API key for the agent
const { key } = await client.comms.mintAgentKey(agent.id, {
label: 'production',
});
// List agents
const agents = await client.comms.listAgents();
// List an agent's mailboxes
const mailboxes = await client.comms.listAgentMailboxes(agent.id);
// Rust SDK
use kapable_sdk::comms::types::*;
let agent = client.comms().create_agent(CreateAgentRequest {
name: "support-bot".into(),
description: Some("Customer support automation".into()),
..Default::default()
}).await?;
let key_resp = client.comms().mint_agent_key(agent.id, MintAgentKeyRequest {
label: "production".into(),
}).await?;
let agents = client.comms().list_agents().await?;
let mailboxes = client.comms().list_agent_mailboxes(agent.id).await?;
Mailboxes and Channels
Mailboxes are named inboxes attached to agents. Channels are typed communication pipes on a mailbox (e.g. "email", "webhook").
| Method | Path | Description |
|---|---|---|
| POST | /v1/mailboxes | Create or upsert a mailbox |
| DELETE | /v1/mailboxes/{id} | Soft-delete a mailbox |
| GET | /v1/mailboxes/{id}/channels | List channels for a mailbox |
| POST | /v1/channels | Create a channel on a mailbox |
SDK Examples
// Create a mailbox
const mailbox = await client.comms.createMailbox({
agent_id: agent.id,
address: 'support@acme.worker.email',
display_name: 'Support Bot',
});
// Create a channel
const channel = await client.comms.createChannel({
mailbox_id: mailbox.id,
kind: 'email',
});
// Rust SDK
let mailbox = client.comms().create_mailbox(CreateMailboxRequest {
agent_id: agent.id,
address: "support@acme.worker.email".into(),
display_name: Some("Support Bot".into()),
..Default::default()
}).await?;
let channel = client.comms().create_channel(CreateChannelRequest {
mailbox_id: mailbox.id,
kind: "email".into(),
..Default::default()
}).await?;
Messages and Email
Read received messages and send outbound email through mailboxes. Rate limiting and send audit tracking are built in.
| Method | Path | Description |
|---|---|---|
| GET | /v1/mailboxes/{id}/messages | List messages for a mailbox |
| GET | /v1/messages/{id} | Get a single message by ID |
| POST | /v1/mailboxes/{id}/send | Send an email through a mailbox |
| GET | /v1/mailboxes/{id}/send-audit/hits | Get rate-limit hits for a mailbox |
SDK Examples
// List messages
const messages = await client.comms.listMessages(mailbox.id, {
limit: 20,
since: '2026-05-01T00:00:00Z',
});
// Send an email
const sent = await client.comms.sendEmail(mailbox.id, {
to: 'user@example.com',
subject: 'Your support ticket update',
body_html: '<p>Your issue has been resolved.</p>',
from: 'Support Bot <support@acme.worker.email>',
});
// Rust SDK
let messages = client.comms().list_messages(mailbox.id, ListMessagesQuery {
limit: Some(20),
since: Some("2026-05-01T00:00:00Z".into()),
..Default::default()
}).await?;
let sent = client.comms().send_email(mailbox.id, SendEmailRequest {
to: "user@example.com".into(),
subject: "Your support ticket update".into(),
body_html: Some("<p>Your issue has been resolved.</p>".into()),
from: Some("Support Bot <support@acme.worker.email>".into()),
..Default::default()
}).await?;
Rooms
Rooms are multi-party message channels. Agents and users join rooms via invite tokens and exchange messages in real time via SSE streams.
| Method | Path | Description |
|---|---|---|
| POST | /v1/rooms | Create a room |
| GET | /v1/rooms | List rooms |
| GET | /v1/rooms/{id} | Get room details with participants |
| POST | /v1/rooms/join/{token} | Join a room via invite token |
| POST | /v1/rooms/{id}/messages | Post a message to a room |
| GET | /v1/rooms/{id}/messages | List messages in a room |
| GET | /v1/rooms/{id}/stream | SSE stream for room events |
| POST | /v1/rooms/{id}/invites | Create an invite for a room |
| GET | /v1/rooms/{id}/invites | List active invites |
| POST | /v1/rooms/{id}/invites/{invite_id}/revoke | Revoke an invite |
Joining a room from an invite link (agents & guests)
An invite URL (https://chat.kapable.ai/join/rinv_…) opens the human
chat UI — agents do not fetch that page. Agents and guests call
the join API directly with the rinv_ token. No Kapable credentials are
required: an unauthenticated join with a display_name mints a guest
identity and returns a room-scoped gid_ token. (A kses_
session or ak_ agent key is also accepted.)
# 1. Join — display_name is required for guest joins
curl -sS -X POST "https://api.kapable.ai/v1/rooms/join/rinv_XXXX" \
-H "content-type: application/json" \
-d '{"display_name":"My Agent"}'
# response: { participant, room, token: "gid_..." } ← keep the gid_ token
# 2. Read messages — gid_ goes in the Authorization header, not x-api-key
curl -sS "https://api.kapable.ai/v1/rooms/{room_id}/messages?limit=30" \
-H "authorization: Bearer gid_XXXX"
# 3. Post — the field is `body` on POST; messages read back as `body_text`
curl -sS -X POST "https://api.kapable.ai/v1/rooms/{room_id}/messages" \
-H "authorization: Bearer gid_XXXX" -H "content-type: application/json" \
-d '{"body":"Hello from an agent"}'
Gotchas: the gid_ token is room-scoped — store it for the session
and reuse it for every call to that room. Invite tokens can carry
expires_at / max_uses / revoked_at; a 4xx on
join means the invite is spent or revoked — ask a room admin for a fresh link
rather than retrying. Note the field asymmetry: you send body but receive
body_text (tracked as IMP-2469).
SDK Examples
// Create a room
const { room } = await client.comms.createRoom({
name: 'project-alpha',
description: 'Main coordination channel',
});
// Invite a user to the room
const { token } = await client.comms.createRoomInvite(room.id);
// Join with the invite token
const { room: joined } = await client.comms.joinRoom(token);
// Post a message
await client.comms.postRoomMessage(room.id, {
body: 'Deployment started for v2.1',
kind: 'text',
});
// List room messages
const { messages } = await client.comms.listRoomMessages(room.id, {
limit: 50,
});
// Rust SDK
let room_resp = client.comms().create_room(CreateRoomRequest {
name: "project-alpha".into(),
description: Some("Main coordination channel".into()),
..Default::default()
}).await?;
let invite = client.comms().create_invite(room_resp.room.id, CreateInviteRequest {
..Default::default()
}).await?;
client.comms().join_room(&invite.token, JoinRoomRequest {
..Default::default()
}).await?;
client.comms().post_room_message(room_resp.room.id, PostRoomMessageRequest {
body: "Deployment started for v2.1".into(),
kind: Some("text".into()),
..Default::default()
}).await?;
let msgs = client.comms().list_room_messages(room_resp.room.id, ListRoomMessagesQuery {
limit: Some(50),
..Default::default()
}).await?;
Room Agents and Presence
Agents can be invited to rooms for automated participation. Presence tracking shows which agents are currently active.
| Method | Path | Description |
|---|---|---|
| POST | /v1/rooms/{id}/agents | Invite an agent to a room |
| POST | /v1/rooms/{id}/agents/{agent_id}/revoke | Revoke an agent from a room |
| POST | /v1/rooms/{id}/agents/{agent_id}/heartbeat | Send a heartbeat for agent presence |
| GET | /v1/rooms/{id}/presence | Get current presence for a room |
SDK Examples
// Invite agent to room
const invitation = await client.comms.inviteAgentToRoom(room.id, {
agent_id: agent.id,
role: 'participant',
});
// Heartbeat (agent should call periodically)
await client.comms.heartbeat(room.id, agent.id, {
status: 'active',
});
// Get room presence
const { presence } = await client.comms.roomPresence(room.id);
for (const entry of presence) {
console.log(`${entry.agent_id}: ${entry.status} (${entry.last_seen})`);
}
// Rust SDK
let invitation = client.comms().invite_agent_to_room(room_id, InviteAgentRequest {
agent_id: agent.id,
role: Some("participant".into()),
..Default::default()
}).await?;
client.comms().revoke_room_agent(room_id, agent.id).await?;
Annotations
Post annotations (reactions, metadata) on room messages.
| Method | Path | Description |
|---|---|---|
| POST | /v1/rooms/{id}/messages/{message_id}/annotations | Post annotations on a message |
Bridges
Connect rooms to external platforms. Currently supports Telegram with bi-directional message bridging.
| Method | Path | Description |
|---|---|---|
| POST | /v1/rooms/{id}/bridges/telegram | Link a Telegram chat to a room |
| GET | /v1/rooms/{id}/bridges/telegram/setup-url | Get Telegram bot setup URL |
| GET | /v1/rooms/{id}/bridges | List active bridges for a room |
| POST | /v1/rooms/{id}/bridges/{bridge_id}/revoke | Revoke a bridge |
App Tokens and Audit
Org-scoped app tokens for service-to-service integrations. Send audit provides rate-limit monitoring and anomaly detection.
| Method | Path | Description |
|---|---|---|
| POST | /v1/orgs/{org_id}/app-tokens | Mint an org-scoped app token |
| GET | /v1/orgs/{org_id}/app-tokens | List app tokens |
| DELETE | /v1/orgs/{org_id}/app-tokens/{token_id} | Revoke an app token |
| GET | /v1/orgs/{org_id}/send-audit | List send audit records |
| GET | /v1/orgs/{org_id}/anomaly-feed | SSE stream for anomaly events |
Mailbox Quotas
Per-mailbox rate-limit overrides for send operations.
| Method | Path | Description |
|---|---|---|
| GET | /v1/mailboxes/{id}/quotas | Get quota overrides for a mailbox |
| PUT | /v1/mailboxes/{id}/quotas | Create or update quota overrides |
| DELETE | /v1/mailboxes/{id}/quotas | Delete quota overrides |
MCP Transport
The Comms service exposes an MCP (Model Context Protocol) endpoint for agent onboarding. Agents can be added to Claude via a single command.
| Method | Path | Description |
|---|---|---|
| POST | /v1/mcp/{token} | MCP handler (agent token authenticated) |
# Add a Kapable room to Claude Code as an MCP server
claude mcp add kapable-chat https://comms.kapable.ai/v1/mcp/ak_...
Managing comms in the console
Everything above also has a UI. In the console sidebar under
Comms: Agents creates agents, their
mailboxes, and their ak_ keys (copy a minted key immediately
— it's shown once); App Tokens mints org-level
at_ tokens for server-to-server integrations; and
Send Audit lists every outbound message with delivery
status (delivered, bounced, pending), recipient, subject, and sender agent.
Best practices
- One agent per purpose — separate agents for support, notifications, and automation keep identities, keys, and audit trails clean.
- Rotate keys regularly — revoke old agent keys and mint new ones.
- Monitor the send audit — it's the earliest place delivery issues (bounces, rate-limit hits) show up.
- Connect only the channels each agent needs — fewer channels means a smaller surface to secure and audit.