Skip to content

AI Runs ​

AI features are predefined capabilities (e.g., summarization, analysis, coaching). Each invocation creates a run that tracks execution through steps.

Contract update (2026-04): The POST request body was corrected to match the live API. The input object, agentId, and workspaceId fields are not accepted. Use the messages array instead.

Run Orchestration Walkthrough ​

  1. List the catalog (GET /ai/features) to discover available feature keys.
  2. Create a run (POST /ai/features/:featureKey/runs) with a messages array containing the user prompt.
  3. Watch the run status via GET /ai/runs/:runId or GET /ai/runs/:runId/steps, and stream incremental events through GET /ai/runs/:runId/stream.
  4. Cancel (POST /ai/runs/:runId/cancel) or retry (POST /ai/runs/:runId/retry) when a run stalls, then capture the final output field once the run completes.
javascript
const orchestrateFeature = async () => {
  const featureRes = await fetch(`${BASE_URL}/ai/features`, {
    headers: { Authorization: `Bearer ${TOKEN}` },
  });
  const feature = (await featureRes.json()).data.find(
    (f) => f.key === "chain-coach"
  );

  const runRes = await fetch(
    `${BASE_URL}/ai/features/${feature.key}/runs`,
    {
      method: "POST",
      headers: {
        Authorization: `Bearer ${TOKEN}`,
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        messages: [
          { role: "user", content: "Help me maintain my daily vocabulary completion history" }
        ],
      }),
    }
  );
  const run = (await runRes.json()).data;
  const stream = new EventSource(
    `${BASE_URL}/ai/runs/${run.id}/stream`,
    { headers: { Authorization: `Bearer ${TOKEN}` } }
  );
  stream.onmessage = (event) => console.log(JSON.parse(event.data));
};

Endpoints ​

MethodPathDescriptionAuthRate Limit
GET/ai/featuresList AI feature catalogJWT + Entitlement60/min
POST/ai/features/:featureKey/runsCreate a run for a featureJWT + Entitlement20/min
GET/ai/runsList runs (filterable)JWT + Entitlement60/min
GET/ai/runs/:runIdGet a runJWT + Entitlement60/min
GET/ai/runs/:runId/stepsList steps of a runJWT + Entitlement60/min
GET/ai/runs/:runId/streamSSE stream for a runJWT + Entitlement20/min
POST/ai/runs/:runId/cancelCancel a running runJWT + Entitlement30/min
POST/ai/runs/:runId/retryRetry a failed runJWT + Entitlement10/min
POST/ai/runs/:runId/resumeResume a recoverable interrupted runJWT + Entitlement—

GET /ai/features ​

List all available AI features in the catalog.

Authentication: JWT Bearer token + active AI entitlement required. Rate limit: 60/min

Request ​

No path or query parameters. No request body.

Response ​

Response Example ​
json
{
  "data": [
    {
      "key": "chain-coach",
      "name": "Chain Coach",
      "description": "AI-powered coaching for maintaining chain run history",
      "category": "productivity",
      "inputSchema": {
        "chainId": "string",
        "context": "string"
      }
    },
    {
      "key": "bit-decomposer",
      "name": "Bit Decomposer",
      "description": "Break down complex bits into smaller actionable items",
      "category": "productivity",
      "inputSchema": {
        "bitId": "string"
      }
    }
  ]
}
Response Fields ​
FieldTypeDescription
keystringFeature identifier
namestringDisplay name
descriptionstringFeature description
categorystringFeature category
inputSchemaobjectExpected input fields

Code Examples ​

javascript
const res = await fetch(`${BASE_URL}/ai/features`, {
  headers: { Authorization: `Bearer ${TOKEN}` },
});
const { data } = await res.json();
python
import requests

res = requests.get(
    f"{BASE_URL}/ai/features",
    headers={"Authorization": f"Bearer {TOKEN}"},
)
data = res.json()["data"]

POST /ai/runs/:runId/resume ​

Resume a run that stopped in a recoverable state. The request has no body.

A successful request returns 200 OK with the run id, its new status, and a replayed boolean. The API returns 403 when the run is outside the caller's permitted scope and 409 conversation_run_not_resumable when the run is not in a recoverable state.


POST /ai/features/:featureKey/runs ​

Create a new run for the specified AI feature.

Authentication: JWT Bearer token + active AI entitlement required. Rate limit: 20/min

Subscription required. Creating AI feature runs requires an active paid subscription. Credit balance alone is insufficient. Requests without a valid paid subscription receive 403 Forbidden with { "code": "subscription_inactive" }.

Request ​

FieldTypeRequiredConstraintsDescription
messagesarrayYes1–50 itemsConversation messages to send
messages[].rolestringYes"user" or "assistant"Role of the message author
messages[].contentstringYesNon-emptyMessage content
temperaturenumberNo0–2Sampling temperature
maxOutputTokensnumberNo1–200,000Maximum tokens in the response
idempotencyKeystringNoMax 256 charsPrevents duplicate runs on retry
modelIdstringNoUUIDOverride the default model

Response ​

Response Example ​
json
{
  "data": {
    "id": "cm5run001",
    "featureKey": "chain-coach",
    "status": "queued",
    "output": null,
    "startedAt": null,
    "completedAt": null,
    "createdAt": "2026-03-17T10:00:00.000Z"
  }
}
Response Fields ​
FieldTypeDescription
idstringRun ID
featureKeystringFeature that triggered the run
statusstringqueued, running, completed, failed, cancelled
outputobject | nullRun output (null while running)
startedAtstring | nullISO 8601
completedAtstring | nullISO 8601 or null
createdAtstringISO 8601

Code Examples ​

javascript
const res = await fetch(`${BASE_URL}/ai/features/chain-coach/runs`, {
  method: "POST",
  headers: {
    Authorization: `Bearer ${TOKEN}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    messages: [
      {
        role: "user",
        content: "I have been struggling to maintain my daily vocabulary practice completion history",
      },
    ],
  }),
});
const { data } = await res.json();
python
import requests

res = requests.post(
    f"{BASE_URL}/ai/features/chain-coach/runs",
    headers={
        "Authorization": f"Bearer {TOKEN}",
        "Content-Type": "application/json",
    },
    json={
        "messages": [
            {
                "role": "user",
                "content": "I have been struggling to maintain my daily vocabulary practice completion history",
            }
        ]
    },
)
data = res.json()["data"]

GET /ai/runs ​

List all runs, with optional filters.

Authentication: JWT Bearer token + active AI entitlement required. Rate limit: 60/min

Request ​

ParameterTypeRequiredDescription
agentIdstringNoFilter by agent
workspaceIdstringNoFilter by workspace
featureKeystringNoFilter by feature
statusstringNoFilter by status: queued, running, completed, failed, cancelled
limitnumberNoItems per page
offsetnumberNoPagination offset

Response ​

Response Example ​
json
{
  "data": [
    {
      "id": "cm5run001",
      "featureKey": "chain-coach",
      "status": "completed",
      "startedAt": "2026-03-17T10:00:00.000Z",
      "completedAt": "2026-03-17T10:00:12.000Z"
    }
  ],
  "meta": {
    "total": 1,
    "limit": 10,
    "offset": 0
  }
}
Response Fields ​
FieldTypeDescription
idstringRun ID
featureKeystringFeature that triggered the run
statusstringqueued, running, completed, failed, cancelled
startedAtstringISO 8601
completedAtstring | nullISO 8601 or null

Code Examples ​

javascript
const params = new URLSearchParams({
  featureKey: "chain-coach",
  status: "completed",
  limit: "10",
});
const res = await fetch(`${BASE_URL}/ai/runs?${params}`, {
  headers: { Authorization: `Bearer ${TOKEN}` },
});
const { data, meta } = await res.json();
python
import requests

res = requests.get(
    f"{BASE_URL}/ai/runs",
    headers={"Authorization": f"Bearer {TOKEN}"},
    params={"featureKey": "chain-coach", "status": "completed", "limit": 10},
)
body = res.json()

GET /ai/runs/:runId ​

Get details of a single run.

Authentication: JWT Bearer token + active AI entitlement required. Rate limit: 60/min

Request ​

Use the id from Create a Run's response (data.id) as $RUN_ID.

Response ​

Response Example ​
json
{
  "data": {
    "id": "cm5run001",
    "featureKey": "chain-coach",
    "status": "completed",
    "output": null,
    "startedAt": "2026-03-17T10:00:00.000Z",
    "completedAt": "2026-03-17T10:00:12.000Z",
    "createdAt": "2026-03-17T10:00:00.000Z"
  }
}
Response Fields ​
FieldTypeDescription
idstringRun ID
featureKeystringFeature that triggered the run
statusstringqueued, running, completed, failed, cancelled
outputobject | nullRun output (null while running)
startedAtstring | nullISO 8601
completedAtstring | nullISO 8601 or null
createdAtstringISO 8601

Code Examples ​

javascript
const runId = process.env.RUN_ID; // id of the run to fetch

const res = await fetch(`${BASE_URL}/ai/runs/${runId}`, {
  headers: { Authorization: `Bearer ${TOKEN}` },
});
const { data } = await res.json();
python
import os
import requests

run_id = os.environ["RUN_ID"]  # id of the run to fetch

res = requests.get(
    f"{BASE_URL}/ai/runs/{run_id}",
    headers={"Authorization": f"Bearer {TOKEN}"},
)
data = res.json()["data"]

GET /ai/runs/:runId/steps ​

List all steps executed within a run.

Authentication: JWT Bearer token + active AI entitlement required. Rate limit: 60/min

Request ​

Use the id from Create a Run's response (data.id) as $RUN_ID.

Response ​

Response Example ​
json
{
  "data": [
    {
      "id": "cm5step01",
      "runId": "cm5run001",
      "type": "llm_call",
      "status": "completed",
      "input": { "prompt": "Analyze completion history data..." },
      "output": { "response": "Based on your data..." },
      "tokensUsed": 1250,
      "durationMs": 3400,
      "createdAt": "2026-03-17T10:00:01.000Z"
    }
  ]
}
Response Fields ​
FieldTypeDescription
idstringStep ID
runIdstringParent run ID
typestringStep type (e.g., llm_call)
statusstringStep status
inputobjectStep input
outputobjectStep output
tokensUsednumberTokens consumed
durationMsnumberExecution duration in ms
createdAtstringISO 8601

Code Examples ​

javascript
const runId = process.env.RUN_ID; // id of the run to list steps for

const res = await fetch(`${BASE_URL}/ai/runs/${runId}/steps`, {
  headers: { Authorization: `Bearer ${TOKEN}` },
});
const { data } = await res.json();
python
import os
import requests

run_id = os.environ["RUN_ID"]  # id of the run to list steps for

res = requests.get(
    f"{BASE_URL}/ai/runs/{run_id}/steps",
    headers={"Authorization": f"Bearer {TOKEN}"},
)
data = res.json()["data"]

GET /ai/runs/:runId/stream ​

Connect to a Server-Sent Events stream to receive real-time updates as a run executes.

Authentication: JWT Bearer token + active AI entitlement required. Rate limit: 20/min

Request ​

Use the id from Create a Run's response (data.id) as $RUN_ID.

Response ​

Response Example ​
event: run.started
data: {"eventId":1,"type":"run.started","runId":"cm5run001","timestamp":"...","payload":{}}

event: message.delta
data: {"eventId":2,"type":"message.delta","runId":"cm5run001","timestamp":"...","payload":{"delta":"Based on your completion history data, "}}

event: message.delta
data: {"eventId":3,"type":"message.delta","runId":"cm5run001","timestamp":"...","payload":{"delta":"I recommend starting with smaller "}}

event: message.completed
data: {"eventId":4,"type":"message.completed","runId":"cm5run001","timestamp":"...","payload":{"content":"Based on your completion history data, I recommend starting with smaller goals."}}

event: run.settlement.completed
data: {"eventId":5,"type":"run.settlement.completed","runId":"cm5run001","timestamp":"...","payload":{"status":"completed"}}
Response Fields ​
FieldTypeDescription
eventIdnumberMonotonically increasing event counter for this run
typestringSSE event type — see SSE Streaming reference for the full catalog
runIdstringRun UUID
timestampstringISO 8601 event timestamp
payloadobjectEvent-specific data (e.g. { delta } for message.delta)

Code Examples ​

javascript
// Browser — uses built-in EventSource (no auth header support)
// For Node.js, install: npm install eventsource
import EventSource from 'eventsource';

const runId = process.env.RUN_ID; // id of the run to stream

const es = new EventSource(
  `${BASE_URL}/ai/runs/${runId}/stream`,
  { headers: { Authorization: `Bearer ${TOKEN}` } }
);

es.addEventListener('message.delta', (e) => {
  const { payload } = JSON.parse(e.data);
  process.stdout.write(payload.delta);
});

es.addEventListener('run.settlement.completed', () => es.close());
es.addEventListener('run.failed', (e) => {
  console.error('Run failed:', JSON.parse(e.data));
  es.close();
});
python
import os
import sseclient
import requests
import json

run_id = os.environ["RUN_ID"]  # id of the run to stream

response = requests.get(
    f"{BASE_URL}/ai/runs/{run_id}/stream",
    headers={
        "Authorization": f"Bearer {TOKEN}",
        "Accept": "text/event-stream",
    },
    stream=True,
)
client = sseclient.SSEClient(response)
for event in client.events():
    if event.event == "message.delta":
        data = json.loads(event.data)
        print(data["payload"]["delta"], end="", flush=True)
    elif event.event == "run.settlement.completed":
        break

POST /ai/runs/:runId/cancel ​

Cancel a currently running run.

Authentication: JWT Bearer token + active AI entitlement required. Rate limit: 30/min

Request ​

Use the id from Create a Run's response (data.id) as $RUN_ID.

Response ​

Response Example ​
json
{
  "data": {
    "cancelled": true
  }
}
Response Fields ​
FieldTypeDescription
cancelledbooleantrue if the run was cancelled; false if you may not cancel the run or it has already finished

Who can cancel. Account owners and admins can cancel any run in the workspace the request acts in; other members can cancel only runs they started. A member who has been removed from the account or suspended cannot cancel any run there, including runs they started. A runId that does not exist, or a run in another account or workspace that you did not start, is refused with 403. Every other refusal returns { "cancelled": false } — the same answer as for a run that has already finished — so the response does not reveal which case applied. Cancellation is best-effort; tokens already streamed are not retracted and may still be billed.

Code Examples ​

javascript
const runId = process.env.RUN_ID; // id of the run to cancel

const res = await fetch(`${BASE_URL}/ai/runs/${runId}/cancel`, {
  method: "POST",
  headers: { Authorization: `Bearer ${TOKEN}` },
});
const { data } = await res.json();
python
import os
import requests

run_id = os.environ["RUN_ID"]  # id of the run to cancel

res = requests.post(
    f"{BASE_URL}/ai/runs/{run_id}/cancel",
    headers={"Authorization": f"Bearer {TOKEN}"},
)
data = res.json()["data"]

POST /ai/runs/:runId/retry ​

Retry a failed run, creating a new run from the same input.

Authentication: JWT Bearer token + active AI entitlement required. Rate limit: 10/min

Request ​

Use the id from Create a Run's response (data.id) as $RUN_ID.

Response ​

Response Example ​
json
{
  "data": {
    "id": "cm5run002",
    "featureKey": "chain-coach",
    "status": "queued",
    "retriedFromRunId": "cm5run001",
    "createdAt": "2026-03-17T10:05:00.000Z"
  }
}
Response Fields ​
FieldTypeDescription
idstringNew run ID
featureKeystringFeature that triggered the run
statusstringqueued, running, completed, failed, cancelled
retriedFromRunIdstringOriginal run ID that was retried
createdAtstringISO 8601

Code Examples ​

javascript
const runId = process.env.RUN_ID; // id of the failed run to retry

const res = await fetch(`${BASE_URL}/ai/runs/${runId}/retry`, {
  method: "POST",
  headers: { Authorization: `Bearer ${TOKEN}` },
});
const { data } = await res.json();
python
import os
import requests

run_id = os.environ["RUN_ID"]  # id of the failed run to retry

res = requests.post(
    f"{BASE_URL}/ai/runs/{run_id}/retry",
    headers={"Authorization": f"Bearer {TOKEN}"},
)
data = res.json()["data"]

Built with purpose.