API
Two audiences on two hostnames. The producer and worker endpoints are
served on each queue's own domain, routed by Host header, and gated by that queue's
access policy: the Bearer token of a listed
user, or open when the operation is public. The
management endpoints live under /api/ and take the single master token.
Every response is JSON. A denied or unknown request returns a plain 404, so a probe can't
tell what exists.
Producer endpoints
| Method & path | Access | Body | Response |
|---|---|---|---|
POST /<queue> | post | the item, as JSON | 200 { ok, id, status }
or 400 { ok:false, errors:[…] } |
GET /<queue>/items/<id> | fetch | none | 200 { ok, id, status, postedBy, postedAt, updatedAt, finishedAt, attempts, progress, result, error }
or 404 { ok:false, error:"no such item" } |
Post an item
POST /resize
Authorization: Bearer USER_TOKEN
Content-Type: application/json
{ "url": "https://ex.com/a.png", "format": "jpg", "width": 800 }
→ 200
{ "ok": true, "id": "9f2c1a0b7d4e6f83", "status": "pending" }
Read its status
GET /resize/items/9f2c1a0b7d4e6f83
Authorization: Bearer USER_TOKEN
→ 200
{
"ok": true,
"id": "9f2c1a0b7d4e6f83",
"status": "processing",
"postedBy": "alice",
"postedAt": 1751500000000,
"updatedAt": 1751500012000,
"finishedAt": null,
"attempts": 1,
"progress": { "percent": 40 },
"result": null,
"error": null
}
The first fetch that returns a terminal result stamps the item's fetched clock, which
starts retention when the queue's retainFrom is "fetched".
Worker endpoints
| Method & path | Access | Body | Response |
|---|---|---|---|
POST /<queue>/claim | work | { worker?, criteria? } | { ok, id, item, attempts, lease }
or { ok:true, empty:true } |
POST /<queue>/items/<id>/progress | work | the progress record | { ok, accepted, status } |
POST /<queue>/items/<id>/result | work | the result record | { ok, accepted, status } |
Claim one item
POST /resize/claim
Authorization: Bearer USER_TOKEN
Content-Type: application/json
{ "worker": "box-1", "criteria": { "format": "jpg", "width": { "lte": 2000 } } }
→ 200
{
"ok": true,
"id": "9f2c1a0b7d4e6f83",
"item": { "url": "https://ex.com/a.png", "format": "jpg", "width": 800 },
"attempts": 1,
"lease": { "worker": "box-1", "until": 1751500072000 }
}
// when nothing matches:
{ "ok": true, "empty": true }
The claim hands back the oldest matching pending item (FIFO by postedAt), leased for the
queue's leaseSeconds. It claims exactly one item; to work more, claim again.
Progress and result
POST /resize/items/9f2c1a0b7d4e6f83/progress { "percent": 40 }
→ { "ok": true, "accepted": true, "status": "processing" }
POST /resize/items/9f2c1a0b7d4e6f83/result { "output": "aGVsbG8=" }
→ { "ok": true, "accepted": true, "status": "finished" }
accepted is false when the item was already terminal (another worker
finished it, or the queue timed it out), in which case your submission is discarded. A result is
accepted whenever the item is not yet terminal, so a late result from a re-queued worker still counts.
See the lifecycle.
Criteria operators
A criteria object maps an item field to a matcher: a bare value (equality) or one operator
object. An item matches only if every criterion passes; a missing field or unknown operator fails
closed.
| Matcher | Matches when the field is |
|---|---|
v (bare) | strictly equal to v |
{ "eq": v } | equal to v |
{ "ne": v } | not equal to v |
{ "lt": v } | less than v |
{ "lte": v } | less than or equal to v |
{ "gt": v } | greater than v |
{ "gte": v } | greater than or equal to v |
{ "in": [ … ] } | one of the listed values |
Management endpoints
Served under /api/v2 on the server itself (behind Caddy), authenticated with the
master token as Authorization: Bearer <master>. Used by the
local package. The manifest and def endpoints cover both queue
definitions (taskhoster-queues/) and user definitions (taskhoster-users/). With
a valid token the HTTP status carries the outcome; without one, every route returns an identical
404 wall. The Sync internals page documents the
full contract.
| Method & path | Purpose | Response |
|---|---|---|
GET /api/v2/manifest | Content hashes of every stored queue and user definition. | 200 { "<host>/taskhoster-queues|taskhoster-users/<name>.json": "<sha256>", … } |
PUT /api/v2/def?path=<host>/taskhoster-queues|taskhoster-users/<name>.json | Store a queue or user definition (validated by folder; body is the JSON). | 200 { ok:true },
400 { error }, or 500 |
DELETE /api/v2/def?path=… | Remove a queue or user definition (idempotent). | 200 { deleted:bool } or 400 |
GET /api/version | API version, for skew checks (unversioned path). | 200 { api: 2 } |
A PUT is validated by its folder: a taskhoster-queues/ path is parsed as a queue
definition, a taskhoster-users/ path as a user definition (whose
taskhoster-name must match its filename). An unrecognised path returns 400; a
request on a different API version's path returns 426.
GET /api/v2/manifest
Authorization: Bearer MASTER_TOKEN
→ 200
{ "task.example.com/taskhoster-queues/resize.json": "e3b0c44298fc1c14…",
"task.example.com/taskhoster-queues/thumbnail.json": "9f86d081884c7d65…",
"task.example.com/taskhoster-users/alice.json": "2c26b46b68ffc68f…" }
/api/v2/logs, /stats, /tasks) behind the log browsers; see
Tools internals. The public producer and worker responses above
carry open CORS (access-control-allow-origin: *, a preflight OPTIONS returning
204), so browser producers and monitors work cross-origin, subject to each queue's policy.