Executions API Endpoints
REST API endpoints for skill executions and streaming. Clanker uses a Sessions API (Managed Agents) pattern: create an idle execution, open an SSE stream, then send a user.message event to start the run.
Sessions API Flow (three steps)
Step 1 — Create an idle execution
POST /api/v1/executions
Creates an idle execution record for a skill. The skill does not run yet.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
skill_slug | string | Yes | Slug of the installed skill to run |
input | string | Yes | Input text for the skill (must be non-empty) |
source | string | No | Source identifier (default: api) |
interface | string | No | Client interface label (see Interface Field section) |
Example
curl -X POST https://clanker.net/api/v1/executions \
-H "Content-Type: application/json" \
-H "x-api-key: ck_live_xxxxx" \
-d '{ "skill_slug": "readme-generator", "input": "Create a README for my Node.js API" }'
Response
{
"id": "exec_abc123",
"skill_slug": "readme-generator",
"skill_name": "README Generator",
"status": "idle"
}
Step 2 — Open the live stream
GET /api/v1/executions/:id/stream-token
Mint a short-lived signed URL for a Server-Sent Events (SSE) stream, then open it before sending input. The stream replays all events from the beginning (gap-free), then delivers live events as they arrive. Append ?lastSeq=<n> to resume after sequence n (omit it, or use 0, for a full replay). Each SSE frame carries the sequence in its id: field and the JSON event in data:. The stream ends automatically once session.status_idle is received.
Response
{ "sseUrl": "https://sandbox.clanker.net/executions/exec_abc123/events?ts=…&sig=…" }
Example
const { sseUrl } = await fetch(
`https://clanker.net/api/v1/executions/${executionId}/stream-token`,
{ headers: { 'x-api-key': 'ck_live_xxxxx' } },
).then((r) => r.json());
const es = new EventSource(sseUrl); // full replay from seq 1, then live
es.onmessage = (event) => {
const frame = JSON.parse(event.data);
if (frame.type === 'agent.message') {
// Render content blocks
for (const block of frame.payload.content) {
if (block.type === 'text') process.stdout.write(block.text);
}
}
if (frame.type === 'session.status_idle') {
console.log('Done. Stop reason:', frame.payload?.stop_reason?.type);
es.close();
}
};
Event types
| Type | Direction | Payload | Description |
|---|---|---|---|
session.status_running | server → client | {} | Execution started running |
session.status_idle | server → client | { stop_reason: { type } } | Execution ended (see stop reasons below) |
agent.message | server → client | { content: [{ type, text }] } | Text output from the agent |
agent.tool_use | server → client | { name, tool, label } | Tool call in progress |
agent.thinking | server → client | {} | Extended thinking block (internal) |
agent.info | server → client | varies | Informational status message |
execution.result | server → client | { summary, cost, tokenUsage } | Final result summary |
span.model_request_end | server → client | { usage: { input_tokens, output_tokens } } | Token usage for one model call |
user.message | client → server | { content: [...] } | Echoed back when client sends input |
user.interrupt | client → server | {} | Echoed back when client cancels |
Stop reason types (in session.status_idle payload)
| Type | Description |
|---|---|
end_turn | Agent finished normally |
interrupt | User cancelled the execution |
limit_reached | Execution credit limit hit |
error | Execution failed |
Step 3 — Send input to start the run
POST /api/v1/executions/:executionId/events
Send a user.message event to trigger the execution. The server emits session.status_running, runs the skill, streams agent events, and finally emits session.status_idle when done.
Request Body
{
"events": [
{
"type": "user.message",
"content": [{ "type": "text", "text": "Create a README for my Node.js API" }]
}
]
}
To cancel a running execution via this endpoint, send user.interrupt instead:
{
"events": [{ "type": "user.interrupt" }]
}
Response
{
"data": [
{ "id": "evt_abc", "type": "user.message", "sequence": 1, "payload": { "content": [{ "type": "text", "text": "Create a README for my Node.js API" }] } },
{ "id": "evt_def", "type": "session.status_running", "sequence": 2, "payload": {} }
]
}
Execution Status
Get the current status and output blocks of an execution.
GET /api/v1/executions/:executionId/status
Example
curl https://clanker.net/api/v1/executions/exec_abc123/status \
-H "x-api-key: ck_live_xxxxx"
Response
{
"executionId": "exec_abc123",
"status": "working",
"skillSlug": "readme-generator",
"skillName": "README Generator",
"startedAt": "2024-01-15T10:30:00.000Z",
"outputBlocks": [...]
}
Execution History
GET /api/v1/executions
Query Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
limit | number | 10 | Max results |
offset | number | 0 | Pagination offset |
channels | string | — | Comma-separated source filter (e.g. api,mcp) |
interfaces | string | — | Comma-separated interface filter (e.g. mcp-vscode,whatsapp-bot) |
Response
{
"items": [
{
"id": 1,
"executionId": "exec_abc123",
"skillSlug": "readme-generator",
"skillName": "README Generator",
"status": "completed",
"cost": 45,
"source": "api",
"interface": "mcp-vscode",
"inputText": "Create a README...",
"startedAt": "2024-01-15T10:30:00.000Z",
"completedAt": "2024-01-15T10:31:30.000Z"
}
],
"hasMore": true,
"offset": 0,
"limit": 10
}
Rerun Execution
Rerun a completed execution with new input. Creates a parent-child relationship.
POST /api/v1/executions/:executionId/rerun
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
input | string | Yes | New input/instructions for the rerun |
Example
curl -X POST "https://clanker.net/api/v1/executions/exec_abc123/rerun" \
-H "Content-Type: application/json" \
-H "x-api-key: ck_live_xxxxx" \
-d '{ "input": "Make it more concise and add a troubleshooting section" }'
Response
{
"executionId": "exec_xyz789",
"parentExecutionId": "exec_abc123",
"skillName": "README Generator",
"status": "started",
"message": "Rerun started. Open the live stream (step 2) for real-time updates."
}
Cancel Execution
POST /api/v1/executions/:executionId/cancel
Response
{
"success": true,
"message": "Execution cancelled",
"processKilled": true,
"actualStatus": "idle"
}
Control Plane Stream
Receive cache-invalidation signals and execution lifecycle notifications for a user.
GET /api/v1/events/subscribe-url
Returns a short-lived signed URL:
{ "url": "wss://workspace.clanker.net/internal/channel/<userId>/subscribe?ts=…&sig=…", "expiresInMs": 300000 }
Open it as a WebSocket to receive events as bare JSON objects. The channel is your own — it is derived from the credential you authenticate with and cannot be selected via a query parameter. The signature is valid for five minutes and is verified only at the upgrade, so fetch a fresh URL per reconnect.
Event Types
| Event | Description |
|---|---|
cache-invalidate | Data changed, refresh queries |
execution-started | New execution began |
execution-progress | Execution update |
execution-completed | Execution finished |
execution-failed | Execution error |
Execution States
| State | Description |
|---|---|
idle | Created, waiting for user.message to start |
working | Actively running |
completed | Successfully finished |
failed | Error occurred |
Interface Field
When executing skills from external platforms, pass an interface field to identify the client type for analytics and provenance tracking.
| Value | Description |
|---|---|
api | Default for REST API clients |
mcp | MCP protocol clients |
mcp-vscode | VS Code MCP extension |
mcp-cursor | Cursor MCP extension |
whatsapp-bot | WhatsApp Business integration |
ci-bot | CI/CD pipeline integration |
| Custom | Any string identifying your integration |
Error Handling
If the SSE connection drops, reconnect with the lastSeq query parameter to resume without replaying already-rendered events:
// On reconnect, pass the last sequence number seen
const url = `${sseUrl}&lastSeq=${lastSeq}`;
Best Practices
- Open the stream before sending input — connect in step 2 before posting in step 3 to avoid missing early events
- Resume with
lastSeqon reconnect — pass the last seen sequence so the DO replays only newer events, avoiding re-rendering output already shown - Handle
session.status_idle— always close the stream on this event to avoid dangling connections - Set timeouts — executions can take up to 65 minutes