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

FieldTypeRequiredDescription
skill_slugstringYesSlug of the installed skill to run
inputstringYesInput text for the skill (must be non-empty)
sourcestringNoSource identifier (default: api)
interfacestringNoClient 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

TypeDirectionPayloadDescription
session.status_runningserver → client{}Execution started running
session.status_idleserver → client{ stop_reason: { type } }Execution ended (see stop reasons below)
agent.messageserver → client{ content: [{ type, text }] }Text output from the agent
agent.tool_useserver → client{ name, tool, label }Tool call in progress
agent.thinkingserver → client{}Extended thinking block (internal)
agent.infoserver → clientvariesInformational status message
execution.resultserver → client{ summary, cost, tokenUsage }Final result summary
span.model_request_endserver → client{ usage: { input_tokens, output_tokens } }Token usage for one model call
user.messageclient → server{ content: [...] }Echoed back when client sends input
user.interruptclient → server{}Echoed back when client cancels

Stop reason types (in session.status_idle payload)

TypeDescription
end_turnAgent finished normally
interruptUser cancelled the execution
limit_reachedExecution credit limit hit
errorExecution 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

ParameterTypeDefaultDescription
limitnumber10Max results
offsetnumber0Pagination offset
channelsstringComma-separated source filter (e.g. api,mcp)
interfacesstringComma-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

FieldTypeRequiredDescription
inputstringYesNew 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

EventDescription
cache-invalidateData changed, refresh queries
execution-startedNew execution began
execution-progressExecution update
execution-completedExecution finished
execution-failedExecution error

Execution States

StateDescription
idleCreated, waiting for user.message to start
workingActively running
completedSuccessfully finished
failedError occurred

Interface Field

When executing skills from external platforms, pass an interface field to identify the client type for analytics and provenance tracking.

ValueDescription
apiDefault for REST API clients
mcpMCP protocol clients
mcp-vscodeVS Code MCP extension
mcp-cursorCursor MCP extension
whatsapp-botWhatsApp Business integration
ci-botCI/CD pipeline integration
CustomAny 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

  1. Open the stream before sending input — connect in step 2 before posting in step 3 to avoid missing early events
  2. Resume with lastSeq on reconnect — pass the last seen sequence so the DO replays only newer events, avoiding re-rendering output already shown
  3. Handle session.status_idle — always close the stream on this event to avoid dangling connections
  4. Set timeouts — executions can take up to 65 minutes