HTTP API
The application API is rooted at /api/v2. A running Server's
/openapi.json is the authoritative machine-readable schema for request and
response bodies. This page records the behavioral contract that an independent
Client or Worker must preserve.
curl http://127.0.0.1:8000/health
curl http://127.0.0.1:8000/openapi.json > openapi.json
FastAPI's interactive Swagger and ReDoc pages are intentionally disabled.
Common contract
/healthand/openapi.jsonare unauthenticated. Every/api/v2endpoint uses the one Server-wide Bearer token when authentication is configured.- Request schemas reject unknown fields. Response schemas may gain optional fields within v2; existing fields, types, defaults, meanings, and Task states do not change without a new API prefix.
- Request bodies and complete stored Task data are each limited to 1 MiB. Large artifacts do not belong in Task JSON.
- Public timestamps are UTC RFC 3339 strings. Task states are exactly
pending,running,succeeded,failed, andcancelled. - Successful
204responses have no body. Clients must not invent receipt objects for them.
When enabled, authenticate application requests with:
Authorization: Bearer <token>
Discovery
| Method and path | Success |
|---|---|
GET /health |
200 with {"status":"ok","api_version":"2","database":"ok"} after a real database check. Database failure returns 503 with status and database set to error. |
GET /openapi.json |
200 with the generated OpenAPI schema. |
Queue endpoints
| Method and path | Input | Success and constraints |
|---|---|---|
PUT /api/v2/queues/{queue} |
No body | 201 when created, 200 when already present; returns {"name": ...}. |
GET /api/v2/queues |
None | 200 with the complete unpaginated Queue array. |
DELETE /api/v2/queues/{queue}?cascade=false |
Boolean query parameter | 204. A non-empty Queue requires cascade=true; any running Task still blocks deletion. Successful cascade atomically deletes the Queue and all its Tasks. |
Queue names are explicit path components and are not authentication identities.
Task resource endpoints
| Method and path | Input | Success and constraints |
|---|---|---|
PUT /api/v2/queues/{queue}/tasks/{task_id} |
Task creation object | 201 on creation; 200 for an identical replay at the same ID; 409 task_id_conflict for a different definition. |
GET /api/v2/queues/{queue}/tasks/{task_id} |
None | 200 with one Task; 404 task_not_found when absent. |
GET /api/v2/queues/{queue}/tasks |
Selection and pagination query parameters | 200 with {"items":[...],"next_cursor":...}. Returns one page only. |
GET /api/v2/queues/{queue}/tasks/count |
Selection query parameters | 200 with {"count":N} for the complete selection. |
PATCH /api/v2/queues/{queue}/tasks/{task_id} |
Non-empty Task update object | 200 with the Task. Running Tasks reject updates. Object/list fields are complete replacements. |
PATCH /api/v2/queues/{queue}/tasks |
{"filter":...,"changes":...} |
200 with {"matched":N,"updated":M}. The filter is required; the update is atomic across matching non-running Tasks. |
POST /api/v2/queues/{queue}/tasks/{task_id}/cancel |
No body | 200 with the cancelled Task. Accepts pending/running and is idempotent for cancelled. |
POST /api/v2/queues/{queue}/tasks/{task_id}/requeue |
No body | 200 with the pending Task. Accepts pending/failed/cancelled, resets attempt and last error. |
DELETE /api/v2/queues/{queue}/tasks/{task_id} |
None | 204; idempotent when absent. Running Tasks reject deletion. |
Creation body
Every field is optional because the Server expands the same defaults as the Python API:
{
"name": null,
"args": {},
"metadata": {},
"priority": 0,
"max_attempts": 3,
"routes": ["default"]
}
The Task ID is the resource identity and must match
t_[A-Za-z0-9_-]{12}. Replaying the same normalized definition returns the
Task's current representation. JSON object-key order, route input order, and an
omitted default versus the same explicit default do not change that identity.
List and count queries
Task listing accepts:
status, name, filter, order_by, descending, limit, cursor
Count accepts status, name, and filter. Selectors are combined with AND.
limit is 1 to 1000 and defaults to 100. A non-null next_cursor must be reused
with the same Queue, selectors, filter, order field, and direction. The cursor is
opaque. See Query language for filter syntax.
Update body
A Task update contains at least one of:
{
"name": "new name",
"args": {},
"metadata": {},
"priority": 10,
"max_attempts": 5,
"routes": ["robotwin"],
"result": {}
}
Every supplied object/list is a complete replacement. Identity, status, attempt, last error, run ownership, and timestamps are Server-owned. Bulk update counts only rows that match and remain non-running; a concurrent claim either sees all new values or excludes that Task. Validation failure for one matched non-running Task rolls back the complete batch.
Worker protocol
These endpoints let an independent executor implement the same lease/fencing
contract as the bundled Workers. They are not public convenience methods on the
Python Client.
| Method and path | Body | Success |
|---|---|---|
POST /api/v2/queues/{queue}/tasks/claim |
{"route":...,"run_id":...} |
200 with Task, run_id, and lease_expires_at; 204 when no compatible pending Task exists. |
POST /api/v2/queues/{queue}/tasks/{task_id}/heartbeat |
{"run_id":...} |
200 with the renewed lease_expires_at. |
POST /api/v2/queues/{queue}/tasks/{task_id}/complete |
{"run_id":...,"result":{...}} |
204; completes the matching active run as succeeded. |
POST /api/v2/queues/{queue}/tasks/{task_id}/fail |
{"run_id":...,"error":{"type":...,"message":...,"traceback":...}} |
204; charges the failure and retries or fails according to the Task budget. |
POST /api/v2/queues/{queue}/tasks/{task_id}/unclaim |
{"run_id":...} |
204; returns the matching run to pending without an error payload. |
run_id matches r_[A-Za-z0-9_-]{12} and is a private per-claim ownership
token, not a Run resource. Heartbeat and terminal actions require the currently
active ID. A stale process cannot mutate a newer claim.
Claim matches the supplied route exactly and case-sensitively. Among compatible pending Tasks, higher priority is claimed first, followed by stable pending order. A healthy run renews its five-minute lease with heartbeat; the lease is not an execution timeout.
Terminal actions are effect-idempotent for the most recently finalized run and
same action. A contradictory action or stale run conflicts. The Server exposes
no /runs collection or execution-history API.
Task representation
Ordinary get/list/action responses contain exactly these required v2 fields:
id, queue, status, name, args, metadata, priority, attempt, max_attempts,
routes, result, last_error, last_route, created_at, updated_at, started_at,
finished_at
Active run_id and lease expiry appear only in Worker protocol responses, never
in the ordinary Task resource.
Errors
Every documented application error uses one envelope:
{
"error": {
"code": "stale_run",
"message": "This run is no longer active.",
"details": {}
}
}
Branch on error.code, not message. The stable status classes are:
| HTTP status | Meaning and representative codes |
|---|---|
401 |
Missing or incorrect token: unauthorized. |
404 |
Missing Queue or Task: queue_not_found, task_not_found. |
409 |
State or ownership conflict: task_id_conflict, task_running, stale_run, run_finalized, lifecycle/update conflicts. |
413 |
Body exceeds 1 MiB: request_too_large. |
422 |
Invalid request, Task, filter, update, identifier, or complete stored data: for example invalid_task, invalid_filter, invalid_update, task_data_too_large. |
503 |
Retryable Server-side operational failure such as database_busy; still uses the same envelope. |
Malformed FastAPI/Pydantic native errors are converted to this envelope; Server tracebacks and database details are never part of the public response.
Client retry boundary
Safe reads and representation-idempotent Task creation can be retried with the same inputs. Lifecycle, update, and delete calls should not be retried blindly after an uncertain response because another actor may have changed the resource. Inspect current state before deciding.
Workers use a separate reliability policy: they keep heartbeat active while
retrying one terminal report, and run_id fencing prevents a stale retry from
overwriting a newer execution.