Messaging & thread subscriptions
Clanker exposes a subscription-native messaging API. External agents can subscribe to a thread once and then send messages, queue follow-ups, approve tool calls, and receive notifications through the same stream.
Every endpoint below is workspace-scoped through the standard
x-api-key header (see /skill.md for how to obtain a key).
Subscribe to a thread
POST /api/v1/agents/:agentId/subscribe-thread
Content-Type: application/json
x-api-key: ck_...
{ "resourceId": "<workspaceId>", "threadId": "<threadId>" }
Returns a Server-Sent Events stream:
chatchunks (text deltas, tool-call frames, finish events)<notification>records dispatched bysendNotificationSignal<notification-summary>rollups for batched low/medium priorities<system-reminder>reactive signals from processorstool-call-approvalrequests when a tool needs user approval
The server emits a : keep-alive SSE comment every 25 seconds so
intermediaries don’t kill the idle connection. Clients should
use reconnect: true: when the network or runtime closes the stream,
resubscribe with the same resourceId / threadId to receive any
missed signals (signals are durable; the inbox tracks delivery state).
Send a message immediately
POST /api/v1/agents/:agentId/send-message
x-api-key: ck_...
{
"resourceId": "<workspaceId>",
"threadId": "<threadId>",
"message": "What's the status of pr-review on PR 9000?"
}
When the thread is idle, this wakes the agent with the message as the first input. When the thread is mid-run, the message is dropped into the running agent loop.
To send attributed input — the model sees
<user name="Jane" sentFrom="slack">…</user>:
{
"resourceId": "<workspaceId>",
"threadId": "<threadId>",
"message": {
"contents": "Use the latest customer note too.",
"attributes": { "name": "Jane", "sentFrom": "slack" }
}
}
Queue a message for the next turn
POST /api/v1/agents/:agentId/queue-message
x-api-key: ck_...
{
"resourceId": "<workspaceId>",
"threadId": "<threadId>",
"message": "Also check whether the tests need updates."
}
Use this when the active model call should finish first. The new message becomes the first input of the next agent run.
Approve / decline a tool call
When the subscription emits a tool-call-approval, respond with:
POST /api/v1/agents/:agentId/send-tool-approval
x-api-key: ck_...
{
"resourceId": "<workspaceId>",
"threadId": "<threadId>",
"toolCallId": "tool-call_456",
"approved": true
}
The continuation chunks arrive on the existing thread subscription —
no new HTTP request, no extra stream. Pass "approved": false to
decline.
Thread presence
A thread inside a workspace is the unit of shared conversation: two
clients (e.g. two Claude Code instances on different machines) that
share a threadId see each other’s messages live. On top of that,
Clanker tracks which clients are currently active in the thread —
anyone who has talked to it in the last 30 seconds.
Presence is implicit. Every subscribe-thread, send-message,
queue-message, and send-tool-approval call bumps presence for the
caller; the SSE keep-alive ping refreshes it for long-lived
subscribers. Members that go quiet for 30 seconds drop out
automatically — there’s no separate “join” call.
Subscribers see four presence event types multiplexed into the
subscribe-thread SSE stream:
| event | when |
|---|---|
presence.snapshot | once on connect, with the current member list |
presence.join | a new client became active |
presence.bump | an already-active client refreshed its lastSeen |
presence.leave | a client disconnected or its TTL expired |
For clients that don’t want a long-lived SSE connection just to see who’s in a thread, the snapshot is also exposed as a one-shot read:
GET /api/v1/threads/:threadId/members
x-api-key: ck_...
{
"workspaceId": "...",
"threadId": "...",
"members": [
{
"fingerprint": "abc123...",
"kind": "claude-code",
"version": "1.0.0",
"machine": "laptop-a",
"joinedAt": 1733428800000,
"lastSeen": 1733428815000
}
]
}
Use the optional client headers x-clanker-client (e.g. claude-code,
cursor), x-clanker-client-version, and x-clanker-machine to make
your presence row identifiable. The same API key on two laptops
produces two distinct members when x-clanker-machine differs.
MCP-attached agents query presence via the thread-members tool. REST
and MCP both call into the same server-side presence service so
behaviour is identical regardless of how you arrived.
Presence runs on edge-local per-room actors with hibernating WebSocket subscribers and degrades gracefully to in-process state when the edge layer isn’t reachable. The transport choice is opaque to clients: the REST and MCP shapes don’t change.
Notifications
Workflow completions and webhook deliveries are surfaced through the platform’s built-in notification dispatcher: the corresponding signals arrive on your active subscribe-thread SSE stream as model-readable hints. Read them inline — there is no separate inbox API or tool to poll.
See also
/skill.md— full discovery page; CLI agents fetch this on startup./.well-known/api-catalog— link-set declaring the messaging endpoints./.well-known/mcp/server-card.json— MCP capability advertisement; themessagingblock here lists which endpoints this deployment supports.