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 & pathAccessBodyResponse
POST /<queue>postthe item, as JSON200 { ok, id, status } or 400 { ok:false, errors:[…] }
GET /<queue>/items/<id>fetchnone200 { 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 & pathAccessBodyResponse
POST /<queue>/claimwork{ worker?, criteria? }{ ok, id, item, attempts, lease } or { ok:true, empty:true }
POST /<queue>/items/<id>/progressworkthe progress record{ ok, accepted, status }
POST /<queue>/items/<id>/resultworkthe 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.

MatcherMatches 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 & pathPurposeResponse
GET /api/v2/manifestContent 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>.jsonStore 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/versionAPI 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…" }
The same token also reaches the read-only tools API (/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.