Producers

A producer is any app that has work it wants done. It POSTs a task to a queue's endpoint, gets back an id, and then either polls that id for status or subscribes to it on a socket and watches it move. The examples below assume a resize queue on task.example.com.

Post a task

POST /<queue> with the item as a JSON body. The server validates it against the queue's item schema; on success you get the new id and its starting status. This needs post access: the Bearer token of a listed user, unless the queue's taskhoster-post is public.

curl -fsS https://task.example.com/resize \
  -H 'authorization: Bearer USER_TOKEN' \
  -H 'content-type: application/json' \
  -d '{"url":"https://ex.com/a.png","format":"jpg","width":800}'
{ "ok": true, "id": "9f2c1a0b7d4e6f83", "status": "pending" }

If validation fails you get 400 with the reasons, and no item is created:

{ "ok": false, "errors": ["url is required", "format: must be one of: png, jpg"] }

The id is a 16-hex-character string. Keep it: it is how you read the task's status and how you subscribe to it.

Read a task's status

GET /<queue>/items/<id> returns the full status view. This needs fetch access: the Bearer token of a listed user.

curl -fsS https://task.example.com/resize/items/9f2c1a0b7d4e6f83 \
  -H 'authorization: Bearer USER_TOKEN'
{
  "ok": true,
  "id": "9f2c1a0b7d4e6f83",
  "status": "processing",
  "postedBy": "alice",
  "postedAt": 1751500000000,
  "updatedAt": 1751500012000,
  "finishedAt": null,
  "attempts": 1,
  "progress": { "percent": 40 },
  "result": null,
  "error": null
}
FieldMeaning
statusOne of pending, processing, finished or failed. See the lifecycle.
postedByThe name of the user who posted the item, or null if it was posted publicly (anonymously).
postedAt / updatedAt / finishedAtMillisecond timestamps. finishedAt is null until the item is terminal.
attemptsHow many times the item has been claimed; it rises each time a re-queued item is claimed again.
progressThe last progress record a worker posted, or null.
resultThe result record once status is finished, else null.
errorSet when status is failed: e.g. "timeout" from a queue timeout, or an in-band error your workers put in the result schema.
The first fetch that returns a terminal result stamps the item's fetched clock. When the queue's retainFrom is "fetched", that is when its retention countdown begins, so a result is kept until you have actually collected it.

Watch a task live

Polling is fine, but for anything interactive open the monitor WebSocket at GET /<queue>/monitor. Subscribe to one or more ids and the server pushes a status message immediately, then again whenever a subscribed item changes. It needs fetch access; because browsers can't set headers on a WebSocket, pass a user's token as ?token=.

const ws = new WebSocket("wss://task.example.com/resize/monitor?token=USER_TOKEN");

ws.onopen = () => {
  ws.send(JSON.stringify({ subscribe: ["9f2c1a0b7d4e6f83"] }));
};

ws.onmessage = (ev) => {
  const msg = JSON.parse(ev.data);
  // { type: "status", id, status, postedBy, progress, result, error, ... }
  if (msg.type === "status") {
    console.log(msg.id, msg.status, msg.progress);
    if (msg.status === "finished") {
      console.log("done:", msg.result);
      ws.send(JSON.stringify({ unsubscribe: [msg.id] }));
    }
  }
};

You can subscribe and unsubscribe ids at any time on the open socket; each subscribe replays the current status once, so you never miss the state an item is already in. One socket watches one queue: open one per queue you care about.

From application code

The HTTP surface is plain JSON, so any client works. In the browser or Node:

async function submit(task) {
  const res = await fetch("https://task.example.com/resize", {
    method: "POST",
    headers: {
      "authorization": "Bearer USER_TOKEN",
      "content-type": "application/json",
    },
    body: JSON.stringify(task),
  });
  const { ok, id, errors } = await res.json();
  if (!ok) throw new Error(errors.join("; "));
  return id;
}

const id = await submit({ url: "https://ex.com/a.png", format: "jpg", width: 800 });

Sending binary as a blob

If a task carries binary (an image to process, a file to transform), declare a blob field and send it as base64 text. It rides inside the ordinary JSON body; the item file on the server stays plain text.

const bytes = new Uint8Array(await file.arrayBuffer());
const b64 = btoa(String.fromCharCode(...bytes));
await submit({ url: "https://ex.com/ignored", format: "png", blob: b64 });
A blob's max is a length limit on the base64 text (default 8 MiB). Base64 is about a third larger than the raw bytes, so size the max accordingly.