Agent Tasks API
The AI agent runs as a queue of AgentTask rows. Tasks are produced by assignment, @mention, workflow trigger, or manual creation, and are consumed by one of two execution paths:
- In-process worker. Runs inside the main app. Polls the queue every five seconds, claims tasks directly via the database, calls the Anthropic Messages API, and executes tools in process.
- External agent bridge. A separate Claude Code CLI process. Claims tasks over HTTP using this API, streams progress, and reports completion.
Both paths compete for the same queue. Whoever successfully transitions a PENDING row to CLAIMED owns the task. Every claim carries an autonomy level that gates which tools the agent is allowed to invoke for the duration of the task.
Authentication
Two auth modes apply, depending on the endpoint.
Admin routes (list, get, create, cancel, retry) use the standard authenticated session with CSRF on mutations and require ADMIN or OWNER role on the active workspace.
Bridge routes (claim, progress, complete) use an MCP API key with the cp_live_ prefix:
POST /api/agent-tasks/:taskId/claim
Authorization: Bearer cp_live_REDACTED...
MCP keys are scoped to a workspace and carry a stored autonomy level (0 through 3). The claim response echoes the effective autonomy level so the bridge can enforce it locally. Every tool call the bridge makes back into Opbox over MCP is re-validated against this level server-side. Keys default to autonomy level 0 (read-only) at creation.
Bridge routes are rate-limited per API key: 60 requests per minute for claim and complete, 120 per minute for progress.
List and Get Tasks
| Method | Endpoint | Auth | Description |
|---|---|---|---|
GET | /api/agent-tasks | Session (ADMIN / OWNER) | List tasks for the active workspace. |
POST | /api/agent-tasks | Session (ADMIN / OWNER) + CSRF | Create a manual task. |
GET | /api/agent-tasks/:taskId | Session (ADMIN / OWNER) | Full task details including result, error, timings. |
PATCH | /api/agent-tasks/:taskId | Session (ADMIN / OWNER) + CSRF | Cancel a task (only while PENDING, CLAIMED, or PROCESSING). |
POST | /api/agent-tasks/:taskId/retry | Session (ADMIN / OWNER) + CSRF | Create a new task copying the original prompt. Only FAILED and TIMED_OUT tasks can be retried. |
Query parameters on GET /api/agent-tasks:
| Parameter | Type | Description |
|---|---|---|
status | string | Filter by task status (see below). |
limit | number | Page size. Default 50, max 100. |
offset | number | Offset for pagination. |
Response:
{
"tasks": [
{
"id": "cln_task_abc",
"title": "Summarise matter progress",
"status": "DONE",
"priority": 1,
"sourceType": "MANUAL",
"sourceMatterId": null,
"errorMessage": null,
"createdAt": "2026-04-16T08:12:00.000Z",
"startedAt": "2026-04-16T08:12:02.000Z",
"completedAt": "2026-04-16T08:12:45.000Z",
"createdBy": { "id": "cln_usr_u1", "name": "Will Lilley", "email": "will@example.com" },
"agentUser": { "id": "cln_usr_agent", "name": "Workspace Agent" }
}
],
"total": 142,
"limit": 50,
"offset": 0
}
Create a manual task
POST /api/agent-tasks
x-csrf-token: <token>
{
"title": "Draft engagement letter for Acme",
"prompt": "Look up the Acme Holdings matter, extract engagement terms from the pipeline field map, and draft a short engagement letter using the standard template.",
"priority": 2
}
Response 201 Created:
{
"id": "cln_task_xyz",
"title": "Draft engagement letter for Acme",
"status": "PENDING",
"priority": 2,
"sourceType": "MANUAL",
"createdAt": "2026-04-16T09:30:00.000Z"
}
Prompts are scanned for prompt-injection patterns at creation time. Detected injections reject the task with 400 Bad Request. The workspace must have the AI Agent addon installed (which provisions the agent user) or creation fails with 400.
Claim, Update, Complete
These routes are the contract between Opbox and the external bridge. The in-process worker bypasses them entirely and transitions state via the database.
| Method | Endpoint | Description |
|---|---|---|
POST | /api/agent-tasks/:taskId/claim | Atomic claim. Transitions PENDING to CLAIMED, mints a one-time claimToken, returns the task payload. |
PATCH | /api/agent-tasks/:taskId/progress | Update progressText. First call promotes CLAIMED to PROCESSING and stamps startedAt. Requires claimToken. |
PATCH | /api/agent-tasks/:taskId/complete | Terminal transition. Mark DONE or FAILED. Requires claimToken. Runs post-completion side effects (matter comment, audit log). |
Claim
POST /api/agent-tasks/:taskId/claim
Authorization: Bearer cp_live_REDACTED...
Response 200 OK:
{
"claimToken": "8e15bb6e-3a22-4fd1-9a8b-1f63c5f8e0a4",
"task": {
"id": "cln_task_xyz",
"title": "Draft engagement letter for Acme",
"prompt": "Look up the Acme Holdings matter...",
"priority": 2,
"sourceType": "MANUAL",
"sourceMatterId": null,
"sourceStepId": null,
"depth": 0,
"autonomyLevel": 2,
"workspaceName": "Acme Corporate Services"
}
}
- The claim is atomic: only a task that is still
PENDINGand belongs to the authenticated workspace succeeds. Anything else returns409 Conflict. claimTokenis a random UUID. Only its SHA-256 hash is persisted. The bridge must carry the raw token on all subsequent calls for this task.- Tasks auto-time-out ten minutes after claim if no
completecall arrives.
Progress
PATCH /api/agent-tasks/:taskId/progress
Authorization: Bearer cp_live_REDACTED...
{
"claimToken": "8e15bb6e-3a22-4fd1-9a8b-1f63c5f8e0a4",
"progressText": "Reading matter record cln_matter_acme123..."
}
The first progress call flips status CLAIMED -> PROCESSING and stamps startedAt. Subsequent calls only update progressText so the UI can stream live output.
Complete
PATCH /api/agent-tasks/:taskId/complete
Authorization: Bearer cp_live_REDACTED...
{
"claimToken": "8e15bb6e-3a22-4fd1-9a8b-1f63c5f8e0a4",
"status": "done",
"result": "Drafted engagement letter and posted to the matter as a comment.",
"costUsd": 0.047,
"durationMs": 38200,
"toolCallCount": 12
}
Response 200 OK:
{
"success": true,
"taskId": "cln_task_xyz",
"status": "DONE"
}
statusis"done"or"failed".errorMessageis required whenstatusis"failed".- On
DONE, post-completion side effects run fire-and-forget: matter comment (if the task was matter-sourced) and audit log entry.
Task Statuses
| Status | Meaning |
|---|---|
PENDING | Queued. Eligible for claim. |
CLAIMED | A worker or bridge has claimed the task. claimToken issued. |
PROCESSING | First progress update received. startedAt stamped. |
DONE | Completed successfully. result populated. |
FAILED | Execution raised an error. errorMessage populated. |
CANCELLED | Cancelled by an admin before completion. |
TIMED_OUT | No complete call within ten minutes of claim. |
Example: bridge walkthrough
# 1. List PENDING tasks
curl "https://opbox.app/api/agent-tasks?status=PENDING&limit=10" \
-H "Authorization: Bearer cp_live_$KEY"
# 2. Claim one
curl -X POST https://opbox.app/api/agent-tasks/cln_task_xyz/claim \
-H "Authorization: Bearer cp_live_$KEY"
# -> { "claimToken": "<uuid>", "task": { "prompt": "...", "autonomyLevel": 2, ... } }
# 3. Stream progress as work happens
curl -X PATCH https://opbox.app/api/agent-tasks/cln_task_xyz/progress \
-H "Authorization: Bearer cp_live_$KEY" \
-H "Content-Type: application/json" \
-d '{"claimToken":"<uuid>","progressText":"Pulling matter context..."}'
# 4. Complete
curl -X PATCH https://opbox.app/api/agent-tasks/cln_task_xyz/complete \
-H "Authorization: Bearer cp_live_$KEY" \
-H "Content-Type: application/json" \
-d '{
"claimToken":"<uuid>",
"status":"done",
"result":"Posted a summary comment on the matter.",
"costUsd":0.031,
"durationMs":22400,
"toolCallCount":7
}'
Rate Limits and Autonomy
Every MCP call the agent makes back into Opbox is re-checked against the API key's stored autonomy level. The level set on the key is the ceiling; individual tasks cannot escalate it at runtime.
| Level | Access | Typical uses |
|---|---|---|
0 | Read-only. All write and destructive tools blocked. | Safe summarisation, reporting, ad-hoc queries. |
1 | Low-risk writes (comments, notifications, draft updates). | Triage, routing, tagging. |
2 | Full data writes scoped to the workspace. | Matter work, record updates, linking. |
3 | Destructive operations (delete, cancel, bulk update). | Reserved for explicit admin authorisation. |
Additional guard rails:
- Task depth cap. A task spawned by another task increments
depth. A hard ceiling of 5 prevents runaway self-spawning. - Prompt-injection scanning. Applied at task creation and on every tool result re-entering the model context. Detected injections are sanitised rather than silently dropped.
- Bridge rate limits. 60/min for claim and complete, 120/min for progress - both per API key. Excess returns
429 Too Many Requests. - Per-tool tier limits. Applied by the MCP layer on top of the per-key bridge limits above.
- Claim timeout. A claimed task that receives no terminal complete call within ten minutes is automatically marked
TIMED_OUTand becomes retryable.
Status Codes
| Status | Meaning |
|---|---|
200 OK | Successful read, progress update, or completion. |
201 Created | Manual task or retry created. |
400 Bad Request | Validation failure, prompt injection detected, or retry attempted on a non-terminal task. |
401 Unauthorized | Missing or invalid MCP key, or missing session on an admin route. |
403 Forbidden | Role check failed on an admin route. |
404 Not Found | Task does not exist or belongs to another workspace. |
409 Conflict | Atomic claim lost (task already claimed, wrong status, or invalid claimToken). |
429 Too Many Requests | Bridge per-key rate limit exceeded. |
500 Internal Server Error | Unexpected error. Side effects on completion are fire-and-forget and will not produce a 500 on the bridge call. |