# Queue

> Send, receive, settle and inspect jobs; topics and subscriptions.

Source: https://useprimitives.com/docs/api/queue

These routes apply to a primitive of type `queue`. A queue primitive holds any number of named queues, created on first send, and the topic subscriptions of those queues. See [Queue](/docs/primitives/queue) for the concepts. Calling one of them on a primitive of another type answers `400 wrong_type`.

| Method | Path | Description |
| --- | --- | --- |
| `GET` | `/v1/primitives/{id}/queues` | Per-queue counts |
| `POST` | `/v1/primitives/{id}/queues/{queue}/messages` | Send a message |
| `GET` | `/v1/primitives/{id}/queues/{queue}/messages` | List jobs |
| `GET` | `/v1/primitives/{id}/queues/{queue}/messages/{job}` | Get a job |
| `POST` | `/v1/primitives/{id}/queues/{queue}/receive` | Receive (lease) jobs |
| `POST` | `/v1/primitives/{id}/queues/{queue}/messages/{job}/ack` | Settle a job |
| `POST` | `/v1/primitives/{id}/queues/{queue}/messages/{job}/heartbeat` | Extend a lease |
| `POST` | `/v1/primitives/{id}/queues/{queue}/messages/{job}/requeue` | Move a dead job back to ready |
| `POST` | `/v1/primitives/{id}/queues/{queue}/purge` | Delete finished jobs |
| `POST` | `/v1/primitives/{id}/topics/{topic}/subscriptions` | Subscribe a queue to a topic |
| `DELETE` | `/v1/primitives/{id}/topics/{topic}/subscriptions/{queue}` | Unsubscribe a queue |
| `POST` | `/v1/primitives/{id}/topics/{topic}/publish` | Publish to a topic |

`{id}` is the queue primitive's id. `{queue}` and `{topic}` follow the naming rules for group and primitive names; an invalid one answers `400 invalid_request`. Durations such as `delay` and `lease` take strings like `"30s"` or `"5m"`, or an integer number of milliseconds; see [Durations](/docs/api#durations).

Every route answers `423 paused` while the primitive is paused. Timestamps are milliseconds since the Unix epoch. See [Errors](/docs/api/errors) for the full list of codes.

## List queues

`GET /v1/primitives/{id}/queues`

Returns job counts by status for every queue in the primitive, sorted by queue name. A queue appears once it has at least one job. `oldest_ready_at` is the `created_at` of the oldest ready job, or `null`.

Key tool: `queue_receive`.

```bash
curl https://api.useprimitives.com/v1/primitives/01jbz3k8m5q2w7x9v4n6t0r1ce/queues \
  -H "Authorization: Bearer $PRIMITIVES_API_KEY"
```

```json
{
  "data": [
    {
      "queue": "reviews",
      "ready": 3,
      "leased": 1,
      "done": 42,
      "dead": 2,
      "oldest_ready_at": 1760486400000
    }
  ]
}
```

## Send a message

`POST /v1/primitives/{id}/queues/{queue}/messages`

Adds a job to the queue, creating the queue if it doesn't exist. The new job is `ready`, and can be received once its `run_at` has passed.

With a `dedupe_key`, a send to the same queue with the same key within 24 hours returns the original job, whatever its status, with `deduplicated: true` and status `200`. No new job is created.

On a Starter workspace at its storage cap, a send answers `429 plan_limit`; see [Usage and limits](/docs/api/limits#usage-and-limits).

Key tool: `queue_send`.

**Body**

| Parameter | Type | Description |
| --- | --- | --- |
| `body` | any JSON | Required. The message. Any JSON value, including `null`. |
| `delay` | duration | Hold the job back this long before it can be received. Default `0`, max 365 days. |
| `dedupe_key` | string | 1–256 bytes. Deduplicates sends to this queue for 24 hours. |
| `priority` | integer | `0` (lowest) to `9` (highest). Higher priority is received first. Default `5`. |
| `max_attempts` | integer | Deliveries before the job is dead. `1`–`1000`, default `5`. |

```bash
curl -X POST https://api.useprimitives.com/v1/primitives/01jbz3k8m5q2w7x9v4n6t0r1ce/queues/reviews/messages \
  -H "Authorization: Bearer $PRIMITIVES_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"body": {"pr": 1234}, "delay": "5m", "dedupe_key": "pr-1234"}'
```

Returns `201` with the SendResult:

```json
{
  "id": "01jc0a4n7r2t8v5x3z9b6d1f0h",
  "queue": "reviews",
  "body": { "pr": 1234 },
  "status": "ready",
  "priority": 5,
  "attempts": 0,
  "max_attempts": 5,
  "run_at": 1760486700000,
  "lease_until": null,
  "dedupe_key": "pr-1234",
  "last_error": null,
  "created_at": 1760486400000,
  "updated_at": 1760486400000,
  "completed_at": null,
  "deduplicated": false
}
```

## List jobs

`GET /v1/primitives/{id}/queues/{queue}/messages`

Lists the queue's jobs in creation order, oldest first, optionally filtered by status. Paged; see [Pagination](/docs/api#pagination). An unknown `status` answers `400 invalid_request`. A queue with no jobs returns an empty page.

Key tool: `queue_receive`.

**Query parameters**

| Parameter | Type | Description |
| --- | --- | --- |
| `status` | string | `ready`, `leased`, `done` or `dead`. Default all. |
| `limit` | integer | Page size. Default `50`, max `200`. |
| `cursor` | string | The `next_cursor` of the previous page. |

```bash
curl "https://api.useprimitives.com/v1/primitives/01jbz3k8m5q2w7x9v4n6t0r1ce/queues/reviews/messages?status=dead&limit=50" \
  -H "Authorization: Bearer $PRIMITIVES_API_KEY"
```

```json
{
  "data": [
    {
      "id": "01jc0a4n7r2t8v5x3z9b6d1f0h",
      "queue": "reviews",
      "body": { "pr": 1234 },
      "status": "dead",
      "priority": 5,
      "attempts": 5,
      "max_attempts": 5,
      "run_at": 1760486762000,
      "lease_until": null,
      "dedupe_key": "pr-1234",
      "last_error": "GitHub API returned 502",
      "created_at": 1760486400000,
      "updated_at": 1760486790000,
      "completed_at": 1760486790000
    }
  ],
  "next_cursor": null
}
```

## Get a job

`GET /v1/primitives/{id}/queues/{queue}/messages/{job}`

Returns one job. The lease token is never part of it. A job that isn't in this queue answers `404 not_found`.

Key tool: `queue_receive`.

```bash
curl https://api.useprimitives.com/v1/primitives/01jbz3k8m5q2w7x9v4n6t0r1ce/queues/reviews/messages/01jc0a4n7r2t8v5x3z9b6d1f0h \
  -H "Authorization: Bearer $PRIMITIVES_API_KEY"
```

```json
{
  "id": "01jc0a4n7r2t8v5x3z9b6d1f0h",
  "queue": "reviews",
  "body": { "pr": 1234 },
  "status": "leased",
  "priority": 5,
  "attempts": 1,
  "max_attempts": 5,
  "run_at": 1760486700000,
  "lease_until": 1760487300000,
  "dedupe_key": "pr-1234",
  "last_error": null,
  "created_at": 1760486400000,
  "updated_at": 1760486700000,
  "completed_at": null
}
```

## Receive jobs

`POST /v1/primitives/{id}/queues/{queue}/receive`

Leases up to `max` ready jobs whose `run_at` has passed, highest priority first, then oldest `run_at` first. Each leased job is `leased` until `lease_until`, its `attempts` goes up by one, and it carries a `lease_token` that settling and heartbeats need. While a job is leased, no other receiver gets it.

Expired leases on the queue are reclaimed first: such a job goes back to `ready` with `last_error: "lease expired"`, or to `dead` if it has used all its attempts.

With `wait`, the request long-polls until at least one job is available or `wait` runs out. It returns `{ "data": [] }` when nothing arrived. The body is optional.

Key tool: `queue_receive`.

**Body**

| Parameter | Type | Description |
| --- | --- | --- |
| `lease` | duration | How long the jobs are yours. Default `30s`, max `12h`. |
| `wait` | duration | Long-poll time. Default `0`, max `20s`; more answers `400 invalid_request`. |
| `max` | integer | Jobs to lease at once, `1`–`10`. Default `1`. |

```bash
curl -X POST https://api.useprimitives.com/v1/primitives/01jbz3k8m5q2w7x9v4n6t0r1ce/queues/reviews/receive \
  -H "Authorization: Bearer $PRIMITIVES_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"lease": "10m", "wait": "20s", "max": 1}'
```

```json
{
  "data": [
    {
      "id": "01jc0a4n7r2t8v5x3z9b6d1f0h",
      "queue": "reviews",
      "body": { "pr": 1234 },
      "status": "leased",
      "priority": 5,
      "attempts": 1,
      "max_attempts": 5,
      "run_at": 1760486700000,
      "lease_until": 1760487300000,
      "dedupe_key": "pr-1234",
      "last_error": null,
      "created_at": 1760486400000,
      "updated_at": 1760486700000,
      "completed_at": null,
      "lease_token": "q7Xk2v9LmR4tWb8nYc3Hs6PdJf1Ze5Ga"
    }
  ]
}
```

## Settle a job

`POST /v1/primitives/{id}/queues/{queue}/messages/{job}/ack`

Settles a job you hold a live lease on, and returns the job.

| `action` | Result |
| --- | --- |
| `ack` | `done`. The default. |
| `retry` | Back to `ready`, receivable after `in`, or after the default backoff. |
| `fail` | Back to `ready` after the default backoff, or `dead` once `attempts` has reached `max_attempts`. |
| `dead` | `dead` right away. |

The default backoff is `min(2^attempts seconds, 1 hour)`. A given `error` is stored as the job's `last_error`; without one, the previous `last_error` is kept. `done` and `dead` jobs get a `completed_at`.

If the lease has expired, or the token belongs to another receiver, the call answers `410 lease_expired` and changes nothing: someone else may hold the job now. A job that isn't in this queue answers `404 not_found`.

Key tool: `queue_ack`.

**Body**

| Parameter | Type | Description |
| --- | --- | --- |
| `lease_token` | string | Required. The token from [Receive jobs](#receive-jobs). |
| `action` | string | `ack`, `retry`, `fail` or `dead`. Default `ack`. |
| `in` | duration | With `retry` only: delay before the job is receivable again. Max 365 days. Default the backoff. |
| `error` | string | With `retry`, `fail` or `dead`: why. Up to 16 KiB. |

```bash
curl -X POST https://api.useprimitives.com/v1/primitives/01jbz3k8m5q2w7x9v4n6t0r1ce/queues/reviews/messages/01jc0a4n7r2t8v5x3z9b6d1f0h/ack \
  -H "Authorization: Bearer $PRIMITIVES_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"lease_token": "q7Xk2v9LmR4tWb8nYc3Hs6PdJf1Ze5Ga"}'
```

```json
{
  "id": "01jc0a4n7r2t8v5x3z9b6d1f0h",
  "queue": "reviews",
  "body": { "pr": 1234 },
  "status": "done",
  "priority": 5,
  "attempts": 1,
  "max_attempts": 5,
  "run_at": 1760486700000,
  "lease_until": null,
  "dedupe_key": "pr-1234",
  "last_error": null,
  "created_at": 1760486400000,
  "updated_at": 1760486940000,
  "completed_at": 1760486940000
}
```

To retry in an hour instead:

```bash
curl -X POST https://api.useprimitives.com/v1/primitives/01jbz3k8m5q2w7x9v4n6t0r1ce/queues/reviews/messages/01jc0a4n7r2t8v5x3z9b6d1f0h/ack \
  -H "Authorization: Bearer $PRIMITIVES_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"lease_token": "q7Xk2v9LmR4tWb8nYc3Hs6PdJf1Ze5Ga", "action": "retry", "in": "1h", "error": "rate limited"}'
```

## Extend a lease

`POST /v1/primitives/{id}/queues/{queue}/messages/{job}/heartbeat`

Sets the lease to expire `extend` from now, and returns the job with its new `lease_until`. Send heartbeats while a job takes longer than its lease. An expired lease, or a token that belongs to another receiver, answers `410 lease_expired`.

Key tool: `queue_ack`.

**Body**

| Parameter | Type | Description |
| --- | --- | --- |
| `lease_token` | string | Required. The token from [Receive jobs](#receive-jobs). |
| `extend` | duration | New lease length from now. Default `30s`, max `12h`. |

```bash
curl -X POST https://api.useprimitives.com/v1/primitives/01jbz3k8m5q2w7x9v4n6t0r1ce/queues/reviews/messages/01jc0a4n7r2t8v5x3z9b6d1f0h/heartbeat \
  -H "Authorization: Bearer $PRIMITIVES_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"lease_token": "q7Xk2v9LmR4tWb8nYc3Hs6PdJf1Ze5Ga", "extend": "10m"}'
```

```json
{
  "id": "01jc0a4n7r2t8v5x3z9b6d1f0h",
  "queue": "reviews",
  "body": { "pr": 1234 },
  "status": "leased",
  "priority": 5,
  "attempts": 1,
  "max_attempts": 5,
  "run_at": 1760486700000,
  "lease_until": 1760487540000,
  "dedupe_key": "pr-1234",
  "last_error": null,
  "created_at": 1760486400000,
  "updated_at": 1760486940000,
  "completed_at": null
}
```

## Requeue a dead job

`POST /v1/primitives/{id}/queues/{queue}/messages/{job}/requeue`

Moves a `dead` job back to `ready`, receivable now, with `attempts` reset to `0` and `completed_at` cleared. Its `last_error` is kept. A job in any other status answers `409 conflict`. No body.

Key tool: `queue_ack`.

```bash
curl -X POST https://api.useprimitives.com/v1/primitives/01jbz3k8m5q2w7x9v4n6t0r1ce/queues/reviews/messages/01jc0a4n7r2t8v5x3z9b6d1f0h/requeue \
  -H "Authorization: Bearer $PRIMITIVES_API_KEY"
```

```json
{
  "id": "01jc0a4n7r2t8v5x3z9b6d1f0h",
  "queue": "reviews",
  "body": { "pr": 1234 },
  "status": "ready",
  "priority": 5,
  "attempts": 0,
  "max_attempts": 5,
  "run_at": 1760490000000,
  "lease_until": null,
  "dedupe_key": "pr-1234",
  "last_error": "GitHub API returned 502",
  "created_at": 1760486400000,
  "updated_at": 1760490000000,
  "completed_at": null
}
```

## Purge jobs

`POST /v1/primitives/{id}/queues/{queue}/purge`

Deletes the queue's `done` or `dead` jobs that finished at least `older_than` ago, and returns how many it deleted. Done and dead jobs are kept until you purge them. Any other `status` answers `400 invalid_request`.

Key tool: `queue_ack`.

**Body**

| Parameter | Type | Description |
| --- | --- | --- |
| `status` | string | Required. `done` or `dead`. |
| `older_than` | duration | Only jobs that finished at least this long ago. Default `0`: every job with that status. |

```bash
curl -X POST https://api.useprimitives.com/v1/primitives/01jbz3k8m5q2w7x9v4n6t0r1ce/queues/reviews/purge \
  -H "Authorization: Bearer $PRIMITIVES_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"status": "done", "older_than": "7d"}'
```

```json
{ "purged": 42 }
```

## Subscribe to a topic

`POST /v1/primitives/{id}/topics/{topic}/subscriptions`

Subscribes one of this primitive's queues to a topic. Messages published to the topic are then delivered to that queue. Subscribing is idempotent: an existing subscription is returned as is.

Key tool: `queue_receive`.

**Body**

| Parameter | Type | Description |
| --- | --- | --- |
| `queue` | string | Required. The queue, in this primitive, that receives the topic's messages. |

```bash
curl -X POST https://api.useprimitives.com/v1/primitives/01jbz3k8m5q2w7x9v4n6t0r1ce/topics/pr-opened/subscriptions \
  -H "Authorization: Bearer $PRIMITIVES_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"queue": "inbox"}'
```

Returns `201` with the Subscription:

```json
{
  "topic": "pr-opened",
  "queue": "inbox",
  "created_at": 1760486400000
}
```

## Unsubscribe from a topic

`DELETE /v1/primitives/{id}/topics/{topic}/subscriptions/{queue}`

Removes the queue's subscription. Jobs already delivered stay in the queue. A queue that isn't subscribed to the topic answers `404 not_found`.

Key tool: `queue_receive`.

```bash
curl -X DELETE https://api.useprimitives.com/v1/primitives/01jbz3k8m5q2w7x9v4n6t0r1ce/topics/pr-opened/subscriptions/inbox \
  -H "Authorization: Bearer $PRIMITIVES_API_KEY"
```

Returns `204` with no body.

## Publish to a topic

`POST /v1/primitives/{id}/topics/{topic}/publish`

Sends a message to every queue subscribed to the topic, in every queue primitive of your workspace, not only this one. Each subscribed queue gets a new job with default options and the body `{ "topic", "from", "body" }`, where `from` is the name of the publishing primitive's group. Paused queue primitives are skipped. Returns `delivered`, the number of jobs created; `0` when nothing is subscribed.

On a Starter workspace at its storage cap, a publish answers `429 plan_limit`; see [Usage and limits](/docs/api/limits#usage-and-limits).

Key tool: `queue_send`.

**Body**

| Parameter | Type | Description |
| --- | --- | --- |
| `body` | any JSON | Required. The message. |

```bash
curl -X POST https://api.useprimitives.com/v1/primitives/01jbz3k8m5q2w7x9v4n6t0r1ce/topics/pr-opened/publish \
  -H "Authorization: Bearer $PRIMITIVES_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"body": {"pr": 1234}}'
```

```json
{ "delivered": 2 }
```

A subscribed queue then holds a job whose `body` is:

```json
{
  "topic": "pr-opened",
  "from": "agent-42",
  "body": { "pr": 1234 }
}
```

## Shapes

| Shape | Fields |
| --- | --- |
| Job | `{ id, queue, body, status, priority, attempts, max_attempts, run_at, lease_until, dedupe_key, last_error, created_at, updated_at, completed_at }`. `status` is `ready`, `leased`, `done` or `dead`. `run_at` is when the job becomes receivable. `lease_until` is set only while leased. `completed_at` is set once `done` or `dead`. `dedupe_key`, `last_error`, `lease_until` and `completed_at` may be `null`. The lease token is never included |
| LeasedJob | Job plus `lease_token`, a string. Returned only by [Receive jobs](#receive-jobs) |
| SendResult | Job plus `deduplicated`, a boolean |
| QueueStats | `{ queue, ready, leased, done, dead, oldest_ready_at }`. Counts are numbers; `oldest_ready_at` is the `created_at` of the oldest ready job, or `null` |
| Subscription | `{ topic, queue, created_at }` |

Scoped API keys need the tool named in each section on their allowlist; see [Tool allowlists](/docs/api/authentication#tool-allowlists). For creating, snapshotting, forking and pausing the queue primitive itself, see [Primitives](/docs/api/primitives). See [Limits](/docs/api/limits) for how operations are counted.
