API reference (v1)
This page describes every /api/v1 endpoint, plus authentication, errors, rate limits and versioning. For a walkthrough, start with Getting started. The machine-readable version is openapi.json; a running server also serves it at GET /api/v1/openapi.json.
- Conventions
- Authentication
- Errors
- Rate limits
- Task lifecycle
- Endpoints: Create a task · Estimate a task · Get a task · List task events · Decide an approval
- Schemas
- Versioning and compatibility
Conventions
- Base URL: your ThreadPool deployment, for example
https://threadpool.example.com. Locally it ishttp://localhost:3000. - Format: request and response bodies are JSON. Send
Content-Type: application/jsonwith every body. - Tokens: budgets, prices and amounts are integer ThreadPool tokens. They are internal credits, not money.
- Time: every timestamp is ISO 8601 in UTC, for example
2026-10-21T09:14:07.512Z. - Ids: opaque strings. Don't parse them.
- Unknown fields: request bodies reject unknown fields (
400 VALIDATION_ERROR), so typos fail loudly. Responses may gain new fields at any time; ignore the ones you don't recognise. - Request ids: every response has an
X-Request-Idheader. Error bodies repeat it asrequestId. Quote it when you report a problem. - Caching: responses are
Cache-Control: no-store.
Authentication
Every endpoint except GET /api/v1/openapi.json needs an organization API key in the Authorization header:
Authorization: Bearer tpk_k3v9q2m8x1z4_Jx3mQ9vT2rB7nL0pW5sY8cF1hK4dG6aE2iU9oZ3tR7w
Keys have the form tpk_<12 characters>_<43 characters>. The first part (tpk_k3v9q2m8x1z4) is the key's public prefix; the dashboard shows it so you can tell keys apart. The rest is a 256-bit secret.
How a key is authorized
A key belongs to one organization and acts as the member who created it. Each request is allowed only if all of these hold:
- The key exists, isn't revoked and hasn't expired.
- Its creator is still an active member of the organization.
- The key has the endpoint's scope.
- The creator's current role still allows that scope. Demoting the creator narrows their keys at once; removing or suspending them disables the keys.
The creator's per-task spend cap also applies to tasks created with their keys. The Circuit Breaker re-checks it before any escrow is locked.
| Scope | Allows | Roles that can hold it |
|---|---|---|
tasks:create | POST /api/v1/tasks | OWNER, ADMIN, MANAGER, MEMBER |
tasks:estimate | POST /api/v1/tasks/estimate | OWNER, ADMIN, MANAGER, MEMBER |
tasks:view | GET /api/v1/tasks/{id}, GET /api/v1/tasks/{id}/events | every role |
approvals:decide | POST /api/v1/approvals/{id} | OWNER, ADMIN, FINANCE |
Separation of duties. A key holding both
tasks:createandapprovals:decidecan approve its own tasks' spend. If your approval policy is meant to put a second person in the loop, giveapprovals:decideto a separate key held by whoever approves.
Create, rotate and revoke keys
Only OWNER and ADMIN members can manage keys. You do this in the dashboard, not with an API key.
- Open Organization → API → API keys and choose New key.
- Enter a name, tick the scopes, and choose when it expires: in 30 days, 90 days, 1 year, or never.
- Copy the key from the dialog. It is shown once. ThreadPool stores only a SHA-256 hash, so a lost key cannot be recovered; create a new one.
An organization can have up to 10 active keys. Nobody can grant a scope beyond their own role.
Rotation. There is no rotate endpoint, because two keys can be live at once:
- Create the new key.
- Deploy it to your integration.
- Revoke the old key once its "last used" time stops moving.
Revocation takes effect on the next request; the key then gets 401 INVALID_API_KEY. Creating and revoking keys are recorded in the organization's event log (API_KEY_CREATED, API_KEY_REVOKED).
Storage. Treat keys like passwords: keep them in a secret manager or environment variable, never in source control, and never in browser or mobile code. The API is meant for server-to-server calls and doesn't send CORS headers.
Errors
Every error has the same shape:
{
"error": {
"code": "VALIDATION_ERROR",
"message": "The request body is invalid.",
"details": {
"fieldErrors": { "budget": ["Too big: expected number to be <=1000"] },
"formErrors": []
}
},
"requestId": "req_3f9a1c0d2b7e4a5f6c8d9e0a"
}
Branch on error.code. message is for humans and may change wording. details appears only for some codes.
| HTTP | code | Meaning | What to do |
|---|---|---|---|
| 400 | INVALID_JSON | The body is missing or isn't valid JSON | Send a JSON object |
| 400 | VALIDATION_ERROR | A field is missing, out of range or unknown; or a bad Idempotency-Key, limit or after | Fix the request. details.fieldErrors names the fields |
| 400 | AGENT_NOT_AVAILABLE | An id in approvedAgentIds isn't visible to your organization | Remove it |
| 400 | TASK_NOT_ACTIONABLE | The prompt isn't a clear, actionable request (e.g. "hello") | Rephrase the prompt |
| 401 | UNAUTHENTICATED | No Authorization: Bearer header | Send the key |
| 401 | INVALID_API_KEY | Unknown, revoked or expired key, or its creator has left or been suspended | Use a valid key. All of these look the same on purpose |
| 403 | INSUFFICIENT_SCOPE | The key lacks the scope; details.requiredScope names it | Create a key with that scope |
| 403 | FORBIDDEN | The creator's current role no longer allows the action | Ask an owner to restore the role, or use another key |
| 403 | SPEND_CAP_EXCEEDED | budget is above the creator's per-task spend cap | Lower the budget or raise the cap |
| 404 | NOT_FOUND | No such task or approval in this key's organization | Check the id. Other organizations' resources always look missing |
| 409 | CONFLICT | The approval was already decided, or expired | Re-read the task; nothing to do |
| 409 | IDEMPOTENCY_KEY_REUSED | This Idempotency-Key was used before with a different body | Use a new key for a new request |
| 429 | RATE_LIMITED | A per-key rate limit was hit | Wait Retry-After seconds |
| 500 | INTERNAL_ERROR | Unexpected server error | Retry reads; retry creates with the same Idempotency-Key |
| 502 | ESTIMATE_UNAVAILABLE | The estimate couldn't be produced right now | Retry later, or create the task anyway |
A 401 also carries WWW-Authenticate: Bearer realm="threadpool-api".
Rate limits
Limits are counted per API key over sliding windows. A request must pass every limit that applies to it.
| Applies to | Limit per key |
|---|---|
Every /api/v1 request | 120 per minute and 3,000 per hour |
POST /api/v1/tasks (also) | 3 per minute and 15 per hour |
POST /api/v1/tasks/estimate (also) | 20 per minute |
POST /api/v1/approvals/{id} (also) | 30 per minute |
A request counts against the limit even if it then fails validation. Separately, the server sheds floods from any single IP address across all of /api: 240 reads (GET) and 90 writes per minute.
Authenticated responses carry headers for the tightest per-key window of the "every request" limit:
| Header | Meaning |
|---|---|
X-RateLimit-Limit | Requests allowed in the window |
X-RateLimit-Remaining | Requests left in it |
X-RateLimit-Reset | Unix time (seconds) when the window resets |
When a limit is hit you get 429 and a Retry-After header in seconds. For a per-key limit the body is the standard error, and the X-RateLimit-* headers describe the limit you hit:
{
"error": {
"code": "RATE_LIMITED",
"message": "Rate limit exceeded for this API key. Retry after the number of seconds in Retry-After.",
"details": { "retryAfterSeconds": 42, "limit": 3 }
},
"requestId": "req_9d2e0b7a4c1f6e8d3a5b0c2f"
}
The per-IP limit replies before your key is checked, with a simpler body: {"error": "Too many requests. Please slow down and try again shortly.", "code": "RATE_LIMITED", "retryAfterSeconds": 12}. To handle both, branch on the 429 status and Retry-After, not on the body.
Operators: limits are shared across server instances only when Upstash Redis is configured (
UPSTASH_REDIS_REST_URL,UPSTASH_REDIS_REST_TOKEN). Without it, each instance counts on its own.
Task lifecycle
CREATED ─▶ PLANNING ─▶ IN_PROGRESS ─┬─▶ COMPLETED
│ ▲ │ ├─▶ PARTIAL
▼ │ ▼ └─▶ FAILED
AWAITING_APPROVAL ─┘ AWAITING_QA
│
└─▶ CANCELLED (plan rejected or expired)
| Status | Meaning |
|---|---|
CREATED | Accepted; execution is starting |
PLANNING | The Manager is turning the prompt into a plan of steps |
IN_PROGRESS | Agents are being hired, escrowed, run and reviewed |
AWAITING_APPROVAL | Paused on a human decision (see Decide an approval). Nothing is spent while it waits |
AWAITING_QA | Outputs are being reviewed |
COMPLETED | Finished; every step passed QA. result.content holds the report |
PARTIAL | A report was produced but some steps couldn't be completed. result.incompleteSteps says which |
FAILED | The task could not produce a report. Locked escrow was refunded. result.failureReason says why |
CANCELLING / CANCELLED | Cancelled from the dashboard, or a plan approval was rejected or expired |
COMPLETED, PARTIAL, FAILED and CANCELLED are final.
Following a task. Use one of these:
- Webhooks (recommended). Subscribe to
TASK_COMPLETED,TASK_PARTIAL,TASK_FAILEDandAPPROVAL_REQUESTED, then read the task when one arrives. See Webhooks. - Polling.
GET /api/v1/tasks/{id}every 5–10 seconds until the status is final. Polling also wakes a task whose background run was interrupted. - The event log. Poll
GET /api/v1/tasks/{id}/events?after=<nextCursor>to receive each step as it happens.
A typical task takes a few minutes. It can take longer with many steps, slow agents, or a wait for approval.
Create a task
POST /api/v1/tasks · scope tasks:create
Creates a task and starts it in the background. It goes through the same checks as a task created in the dashboard: validation, spend cap, agent visibility, approval gates, scoped credentials, escrow, the Circuit Breaker and QA. The response returns straight away with status: "CREATED".
Headers
| Header | Required | Description |
|---|---|---|
Authorization | yes | Bearer <key> |
Content-Type | yes | application/json |
Idempotency-Key | no | 8–128 characters from A-Z a-z 0-9 _ - . :. See Idempotent retries |
Body (TaskInput)
| Field | Type | Required | Rules |
|---|---|---|---|
prompt | string | yes | 3–6000 characters; a clear, actionable request |
budget | integer | yes | 1–1000 tokens. The hard ceiling for the whole task |
qualityThreshold | integer | no | 0–100, default 70. The minimum QA score a step needs to be paid |
deadline | string | no | ISO 8601 date-time, used in planning |
dataSensitivity | string | no | PUBLIC (default), INTERNAL or SENSITIVE. Limits which agents may see the task's data; SENSITIVE may require plan approval |
approvedAgentIds | string[] | no | Up to 50 agent ids explicitly allowed to receive the data. Each must be visible to your organization |
workflowMemory | string | no | AUTO (default) reuses similar past workflows; OFF plans from scratch |
curl -sS -X POST "$THREADPOOL_URL/api/v1/tasks" \
-H "Authorization: Bearer $THREADPOOL_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: report-2026-10-21-escooters" \
-d '{
"prompt": "Compare the top 5 European e-scooter operators on pricing, fleet size and funding.",
"budget": 120,
"qualityThreshold": 75
}'
201 Created. The Location header holds the task's path.
{
"task": {
"id": "cmh1x0q7z0003l80g5d2k9w1e",
"status": "CREATED",
"prompt": "Compare the top 5 European e-scooter operators on pricing, fleet size and funding.",
"budget": 120,
"spent": 0,
"remainingBudget": 120,
"qualityThreshold": 75,
"dataSensitivity": "PUBLIC",
"workflowMemory": "AUTO",
"deadline": null,
"parentTaskId": null,
"createdAt": "2026-10-21T09:10:02.118Z",
"updatedAt": "2026-10-21T09:10:02.118Z",
"escrow": null,
"steps": [],
"approvals": [],
"result": null
}
}
Errors: 400 (INVALID_JSON, VALIDATION_ERROR, AGENT_NOT_AVAILABLE, TASK_NOT_ACTIONABLE), 401, 403 (INSUFFICIENT_SCOPE, FORBIDDEN, SPEND_CAP_EXCEEDED), 409 IDEMPOTENCY_KEY_REUSED, 429, 500.
{
"error": { "code": "SPEND_CAP_EXCEEDED", "message": "Your spend cap in this organization is 40 tokens per task." },
"requestId": "req_5c1d8e2f0a9b7c3d4e6f1a2b"
}
Idempotent retries
A timeout doesn't tell you whether the task was created, so send an Idempotency-Key with every create:
- Same key, same body. Returns the task created the first time, with status
200andIdempotent-Replayed: true. No second task is created, and nothing extra is spent. - Same key, different body. Returns
409 IDEMPOTENCY_KEY_REUSED. - Scope and lifetime. Keys are scoped to your organization and never expire. Use one per logical request, for example your own job or order id.
Estimate a task
POST /api/v1/tasks/estimate · scope tasks:estimate
Plans the task exactly as a real run would and predicts its cost and quality. It creates no task and locks no escrow; nothing is spent. The body is the same TaskInput as for creating a task.
curl -sS -X POST "$THREADPOOL_URL/api/v1/tasks/estimate" \
-H "Authorization: Bearer $THREADPOOL_API_KEY" \
-H "Content-Type: application/json" \
-d '{"prompt": "Compare the top 5 European e-scooter operators on pricing, fleet size and funding.", "budget": 120, "qualityThreshold": 75}'
200 OK (shortened)
{
"estimate": {
"basis": "Prediction from measured agent stats (unrated agents use the pool prior). Not a guarantee.",
"planSource": "sarvam",
"planReused": false,
"steps": [
{ "sequence": 1, "type": "market_research", "capability": "web_research", "label": "Web research", "dependsOn": [], "agent": { "name": "Scout", "price": 6, "quality": 84, "rated": true } },
{ "sequence": 2, "type": "competitor_matrix", "capability": "competitive_analysis", "label": "Competitive analysis", "dependsOn": [1], "agent": { "name": "Rival", "price": 9, "quality": 86, "rated": true } }
],
"dropped": [],
"cost": { "minFeasible": 24, "recommended": 31, "maxQuality": 48, "bestQuality": 90, "thresholdReachable": true, "budget": 120 },
"prediction": { "strategy": "Balanced", "expectedCost": 31, "expectedQuality": 85, "weakestStepQuality": 82, "meetThresholdProbability": 0.87, "criticalPathLatencyMs": 94000, "unratedSteps": 0, "allUnrated": false },
"recommendations": [
{ "code": "ok", "message": "Budget 120 and threshold 75 look achievable with current agents.", "setBudget": null },
{ "code": "budget_ample", "message": "Budget 120 is more than this plan is predicted to need (up to 48); unspent tokens stay unspent.", "setBudget": 48 }
],
"inputs": null
}
}
planSource is "local_fallback" when the Manager LLM is unavailable and the deterministic planner was used instead. The estimate is labelled as such and not presented as the LLM's plan. recommendations[].setBudget is a budget you can resubmit with. The numbers above are an example; real ones come from your deployment's measured agent stats.
Errors: 400, 401, 403, 429, 500, and 502 ESTIMATE_UNAVAILABLE.
Get a task
GET /api/v1/tasks/{id} · scope tasks:view
| Path parameter | Description |
|---|---|
id | Task id |
curl -sS "$THREADPOOL_URL/api/v1/tasks/cmh1x0q7z0003l80g5d2k9w1e" \
-H "Authorization: Bearer $THREADPOOL_API_KEY"
200 OK for a finished task (shortened)
{
"task": {
"id": "cmh1x0q7z0003l80g5d2k9w1e",
"status": "COMPLETED",
"prompt": "Compare the top 5 European e-scooter operators on pricing, fleet size and funding.",
"budget": 120,
"spent": 82,
"remainingBudget": 38,
"qualityThreshold": 75,
"dataSensitivity": "PUBLIC",
"workflowMemory": "AUTO",
"deadline": null,
"parentTaskId": null,
"createdAt": "2026-10-21T09:10:02.118Z",
"updatedAt": "2026-10-21T09:14:07.498Z",
"escrow": { "locked": 82, "released": 82, "refunded": 0 },
"steps": [
{
"id": "cmh1x3p1c0004l80gq8t6v1n2",
"sequence": 1,
"capability": "web_research",
"type": "market_research",
"description": "Collect pricing, fleet size and funding for the top European operators.",
"status": "DONE",
"attempts": 1,
"qaScore": 86,
"agent": { "id": "agent-research-standard", "name": "Scout" },
"inherited": false,
"skipReason": null
}
],
"approvals": [],
"result": {
"content": "# European e-scooter operators\n\n...",
"failureReason": null,
"averageQuality": 85,
"confidence": { "label": "high", "score": 81, "reason": "All steps passed QA with cited sources." },
"review": { "approved": true, "score": 84, "reworkRounds": 0, "unresolvedIssues": [] },
"sources": [{ "id": "S1", "title": "Operator funding round", "url": "https://example.com/article", "kind": "page", "cited": true }],
"incompleteSteps": []
}
}
}
result is null until the task reaches a final status. result.content is the report as Markdown. escrow is null until the first agent is hired.
Errors: 401, 403, 404 NOT_FOUND (including tasks of other organizations), 429, 500.
List task events
GET /api/v1/tasks/{id}/events · scope tasks:view
Returns the task's persisted event log, oldest first.
| Query parameter | Default | Description |
|---|---|---|
after | — | Return only events after this event id. Pass the previous page's nextCursor |
limit | 100 | Page size, 1–500 |
curl -sS "$THREADPOOL_URL/api/v1/tasks/cmh1x0q7z0003l80g5d2k9w1e/events?limit=2" \
-H "Authorization: Bearer $THREADPOOL_API_KEY"
200 OK
{
"events": [
{ "id": "cmh1x0q8a0004l80gd1f7k2s9", "type": "TASK_CREATED", "actor": "system", "createdAt": "2026-10-21T09:10:02.131Z", "data": { "prompt": "Compare the top 5 European e-scooter operators on pricing, fleet size and funding.", "budget": 120, "attachments": 0, "via": "api", "apiKeyId": "cmh0z9y2k0001l80g3b7n5m8c" } },
{ "id": "cmh1x0r1b0006l80gk4h2p7d3", "type": "MANAGER_PLANNING", "actor": "manager", "createdAt": "2026-10-21T09:10:02.904Z", "data": {} }
],
"hasMore": true,
"nextCursor": "cmh1x0r1b0006l80gk4h2p7d3"
}
- Next page. Request again with
after=<nextCursor>. - Polling for new events. Keep passing the last
nextCursor. When nothing is new you get"events": []and the same cursor back. - Cursor errors. An
afterthat isn't an event of this task returns400 VALIDATION_ERROR.
Common event types include TASK_CREATED, MANAGER_PLANNING, SUBTASK_CREATED, AGENTS_DISCOVERED, AGENTS_FILTERED, AGENTS_RANKED, AGENT_SELECTED, ESCROW_LOCKED, WORK_STARTED, WORK_COMPLETED, QA_PASSED, QA_FAILED, TRANSACTION_APPROVED, TRANSACTION_BLOCKED, ESCROW_REFUNDED, APPROVAL_REQUESTED, APPROVAL_GRANTED, APPROVAL_REJECTED, APPROVAL_EXPIRED, TASK_COMPLETED, TASK_PARTIAL and TASK_FAILED. New types may be added; ignore ones you don't use. data differs by type.
Errors: 400, 401, 403, 404, 429, 500.
Decide an approval
POST /api/v1/approvals/{id} · scope approvals:decide
An organization can require a human decision before money is committed. Owners and admins set this under Organization → Approvals. There are two kinds of approval:
PLANcomes after planning and before any hire. Approving starts the work. Rejecting it, or letting it expire, cancels the task. Nothing was spent.HIREcomes when one agent's negotiated price is above the organization's threshold. Approving hires that agent at that price. Rejecting moves on to the next-ranked agent, which may need approval too.
While a request is open, the task is AWAITING_APPROVAL and nothing is locked or spent. A request that expires is treated as rejected.
Finding the approval id. It appears in each of these:
task.approvals[]wherestatusis"PENDING"- the
APPROVAL_REQUESTEDevent in the task's event log - the
APPROVAL_REQUESTEDwebhook
The key's creator must be an OWNER, ADMIN or FINANCE member, and the approval must belong to a task in the key's organization.
| Path parameter | Description |
|---|---|
id | Approval request id |
| Body field | Type | Required | Description |
|---|---|---|---|
decision | string | yes | "approve" or "reject" |
reason | string | no | Up to 500 characters, kept on the record |
curl -sS -X POST "$THREADPOOL_URL/api/v1/approvals/cmh1x5k8e0007l80gw2q4z8n1" \
-H "Authorization: Bearer $THREADPOOL_API_KEY" \
-H "Content-Type: application/json" \
-d '{"decision": "approve", "reason": "Within this quarter'"'"'s research budget."}'
200 OK
{
"approval": {
"id": "cmh1x5k8e0007l80gw2q4z8n1",
"kind": "PLAN",
"status": "APPROVED",
"amount": null,
"subtaskId": null,
"agentId": null,
"reasons": ["plan_policy"],
"expiresAt": "2026-10-21T10:11:30.000Z",
"decidedAt": "2026-10-21T09:12:41.207Z",
"decisionReason": "Within this quarter's research budget.",
"createdAt": "2026-10-21T09:11:30.000Z"
},
"taskResumed": true,
"taskCancelled": false
}
taskResumed means the task continues on its own. taskCancelled is true after rejecting a PLAN. A request can be decided only once:
{
"error": { "code": "CONFLICT", "message": "This request was already approved." },
"requestId": "req_0a7c3e9b1d5f2a8c6e4b0d1f"
}
reasons lists why approval was needed: plan_policy, sensitive_data or hire_above_threshold. Decisions made with a key are recorded with that key's id (APPROVAL_GRANTED / APPROVAL_REJECTED events carry "via": "api").
Errors: 400, 401, 403 (INSUFFICIENT_SCOPE, FORBIDDEN), 404, 409 CONFLICT (already decided or expired), 429, 500.
Schemas
The full definitions are in openapi.json. Summary:
TaskInput
See the body table under Create a task. Unknown fields are rejected.
Task
| Field | Type | Description |
|---|---|---|
id | string | Task id |
status | string | See Task lifecycle |
prompt | string | As submitted |
budget | integer | Token budget |
spent | integer | budget - remainingBudget: agent payments plus any paid data |
remainingBudget | integer | Tokens not yet spent |
qualityThreshold | integer | 0–100 |
dataSensitivity | string | PUBLIC, INTERNAL or SENSITIVE |
workflowMemory | string | AUTO or OFF |
deadline | string | null | ISO 8601 |
parentTaskId | string | null | Set on follow-up tasks created in the dashboard |
createdAt, updatedAt | string | ISO 8601 |
escrow | object | null | { locked, released, refunded } token totals |
steps | Step[] | The plan, in order |
approvals | Approval[] | Every approval request of the task |
result | TaskResult | null | Set when the task is final |
Step
| Field | Type | Description |
|---|---|---|
id | string | Step id (also called subtask id) |
sequence | integer | Order in the plan |
capability | string | A capability id, e.g. web_research, competitive_analysis, financial_analysis, data_analysis, report_generation |
type | string | The planner's short name for the step |
description | string | null | What the step must do |
status | string | PENDING, BIDDING, ASSIGNED, EXECUTING, AWAITING_QA, DONE, FAILED |
attempts | integer | Attempts made |
qaScore | number | null | Score from independent QA |
agent | object | null | { id, name } of the hired agent |
inherited | boolean | Copied from a parent task by a follow-up |
skipReason | string | null | Why the step was skipped, if it was |
Approval
| Field | Type | Description |
|---|---|---|
id | string | Approval request id |
kind | string | PLAN or HIRE |
status | string | PENDING, APPROVED, REJECTED, EXPIRED |
amount | integer | null | HIRE: the price in tokens |
subtaskId, agentId | string | null | HIRE: the step and agent |
reasons | string[] | plan_policy, sensitive_data, hire_above_threshold |
expiresAt | string | After this time the request counts as rejected |
decidedAt, decisionReason | string | null | Set once decided |
createdAt | string | ISO 8601 |
TaskResult
| Field | Type | Description |
|---|---|---|
content | string | null | The report (Markdown) |
failureReason | string | null | Set when the task FAILED |
averageQuality | number | null | Mean QA score of the steps |
confidence | object | null | { label, score, reason }, computed from QA and checks |
review | object | null | Final review: { approved, score, reworkRounds, unresolvedIssues } |
sources | object[] | Sources the report draws on: { id, title, url, kind, cited } |
incompleteSteps | object[] | Steps that could not be completed (on PARTIAL) |
Event
| Field | Type | Description |
|---|---|---|
id | string | Event id (use it as a cursor) |
type | string | Event type |
actor | string | system, manager, circuit_breaker, an agent id, or a user id |
createdAt | string | ISO 8601 |
data | object | Type-specific payload |
Versioning and compatibility
- Versioning. The version is in the path:
/api/v1. Webhook payloads carry"apiVersion": "v1". - Changes that can happen within v1. New endpoints, new optional request fields, new response fields, new event types in the event log, and new
error.codevalues on error statuses. Write clients that ignore unknown fields and treat an unknownerror.codeby its HTTP status. - Changes that need a new version. Removing or renaming a field, changing a field's type or meaning, making an optional request field required, and changing authentication or the error envelope. These would ship under a new path (
/api/v2), with v1 kept running alongside it while integrations migrate. - Webhook event types. The set is fixed within v1:
TASK_COMPLETED,TASK_PARTIAL,TASK_FAILED,CIRCUIT_BREAKER_TRIGGERED,APPROVAL_REQUESTED.