---
title: "Queues and resources"
description: "POST /api/v1/configure and the /api/v1/resources/* listings: every enforced option, its default, and what reads it."
---

> Documentation Index
> Fetch the complete documentation index at: https://queenmq.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Queues and resources

A queue does not have to be created before it is used: the first push to a queue
name creates the queue, one partition, and a `queen.queues` configuration row
with column defaults. It also derives `namespace` and `task` from a dotted queue
name (`billing.invoice` becomes namespace `billing`, task `invoice`), so a
push-only queue is still visible to namespace and task discovery pops.
`POST /api/v1/configure` exists to set options that differ from the defaults, and to
pre-create a queue so a consumer can attach before any producer runs.

A **pop can create the row too**: a queue-scoped pop on a name that does not
exist creates the `queen.queues` row with column defaults, because a consumer
group may legitimately subscribe before the first push and its durable
subscription record hangs off the queue's id. The practical consequence is that
a consumer-side typo now creates an empty queue rather than nothing.

Two routes mutate queue state: `POST /api/v1/configure` (read-write) and
`DELETE /api/v1/resources/queues/:queue` (**admin**). Everything else on this page
is a read at the `read-only` access level.

## POST /api/v1/configure

`queue` is the only required field. Options may be nested under `options` or sent
at the top level; the broker normalises them into one bag before calling
`queen.configure_queue_v1`. Top-level `namespace` and `task` are folded into the
options when they are non-empty strings.

```json
{
  "queue": "orders",
  "namespace": "billing",
  "task": "invoice",
  "options": {
"leaseTime": 120,
"retryLimit": 5,
"dedupWindowSeconds": 600
  }
}
```

An empty string is a valid queue name: `queen.configure_queue_v1` creates a row
named `''`, and the JavaScript client's own load test relies on it. A missing or
non-string `queue` is a `400`.

### Configure is a full replace

Every call rewrites the whole configuration row. Keys you omit are **reset to
their defaults**, not left as they were.

> **Danger**
>
> `/configure` is not a patch. A queue configured with `retentionEnabled: true`
> and then reconfigured with only `leaseTime` loses retention. The same applies to
> `dedupWindowSeconds`, which falls back to **3600**. Omitting it on a queue that
> had dedup disabled silently re-enables a one-hour dedup window. Always send the
> complete option set you want the queue to have.

The reset is structural: `configure_queue_v1` parses each option with
`COALESCE(p_options->>'key', <default>)` and writes every column on
`ON CONFLICT DO UPDATE`, so an absent key becomes the default value rather than
being skipped.

### Enforced options

These are the options this engine reads. Each row names what enforces it, so you
can predict the behaviour change from setting it.

| Option | Default | What enforces it |
| --- | --- | --- |
| `namespace` | `""` | Stored on `queen.queues`; selects queues for the discovery pop and for `namespace` filters on the listings and analytics routes. |
| `task` | `""` | Same as `namespace`, on the `task` axis. |
| `leaseTime` | `300` (seconds) | Written to `queen.queues.lease_time`; the pop path uses `COALESCE(request leaseSeconds, queue lease_time, 60)` for the lease it takes. |
| `retryLimit` | `3` | The per-(partition, group) retry budget charged by an explicit `failed` ack (`005_log_ack.sql`). Below the limit the batch redelivers; at the limit the head frame is dead-lettered or dropped. |
| `deadLetterQueue` | `true` | With `dlqAfterMaxRetries`, decides whether an exhausted retry budget writes `queen.log_dlq`. |
| `dlqAfterMaxRetries` | `true` | See the note below: the two flags are OR-ed. |
| `delayedProcessing` | `0` (seconds) | Pop visibility: only segments at least this old are delivered. In-flight backlog is bounded by rate × delay. |
| `windowBuffer` | `0` (seconds) | Pop visibility: if the partition received a segment within this window, the pop delivers **nothing** from that partition. Trades latency for larger batches. |
| `minPopWaitTime` | `0` (ms, off) | How long a non-empty pop may hold an under-full batch before claiming it, so one commit carries more messages. Clamped by SQL to `[0, 60000]`. |
| `retentionEnabled` | `false` | Master switch for the two age-based retention rules. Without it, `retentionSeconds` and `completedRetentionSeconds` do nothing. |
| `retentionSeconds` | `0` | Rule 1 cutoff. Active only when `retentionEnabled` is true and the value is positive. Whole segments older than the cutoff are deleted. |
| `completedRetentionSeconds` | `0` | Rule 2 cutoff: segments every group has consumed past. Also widens the `queen.log_txns` purge window (`GREATEST(dedupWindowSeconds, completedRetentionSeconds, 900)`). |
| `maxWaitTimeSeconds` | `0` | **Deletes whole segments older than the cutoff for every consumer group, in-flight leases included.** Independent of `retentionEnabled`. See below. |
| `encryptionEnabled` | `false` | Stored on `queen.queues` and read at push time; payloads are stored as an encrypted envelope when a key is configured. |
| `dedupWindowSeconds` | `3600` (seconds) | Written to `queen.queues.dedup_window_seconds`. The push path probes `queen.log_txns` within this window before allocating an offset. `0` disables the probe entirely. |

Values are stored as given. The broker does not validate ranges, apart from the
SQL clamp on `minPopWaitTime` and a `max(0)` floor on `dedupWindowSeconds`.

> **Note**
>
> The effective `leaseTime` depends on how the queue came to exist. A queue created
> implicitly by a push or a pop gets the `queen.queues` column default, **60 seconds**.
> A queue that has been through `/configure` at least once gets the option's default,
> **300 seconds** (`/configure` writes an explicit 300 when `leaseTime` is omitted).
> Both are overridable per request with `?leaseSeconds=`. Because there is a single
> `lease_time` column, the value the queue detail endpoint displays is the value the
> pop path leases with. Before 2026-07-31 the display could say 300 on an
> implicitly created queue whose pops actually leased at 60. Every other option has
> the same default either way, because the `queen.queues` column defaults and the
> `/configure` defaults agree.

#### Disabling the dead-letter queue takes both flags

`005_log_ack.sql` computes the DLQ switch as
`COALESCE(dead_letter_queue, true) OR COALESCE(dlq_after_max_retries, true)`.
Setting only one of them to `false` leaves dead-lettering on. Send both as
`false` to turn it off, at which point an exhausted retry budget drops the
poison frame instead of quarantining it.

#### maxWaitTimeSeconds deletes messages

`maxWaitTimeSeconds` is not a delivery deadline and it does not dead-letter.
`queen.log_evict_max_wait_step_v1` delegates to the same delete-and-advance path
retention uses, with the cutoff taken from `queen.queues.max_wait_time_seconds`:
whole segments older than the cutoff are removed for every consumer group,
including groups holding a live lease on them. The SQL calls this data loss by
design. A cursor left below the new log start resumes at the next existing
offset; the pop scan tolerates the gap. Use it only where dropping stale work is
the intent.

### Response

`200` with the stored-procedure JSON passed through verbatim:

```json
{
  "configured": true,
  "queueId": "0198...",
  "partitionId": null,
  "queue": "orders",
  "namespace": "billing",
  "task": "invoice",
  "storage": "segments",
  "options": { "leaseTime": 120, "retryLimit": 5, "dedupWindowSeconds": 600 }
}
```

`storage` is a hard-coded literal. There is no engine-selector column behind it
(there is only one engine); the key is kept so the response shape does not change.

`partitionId` is **always `null`**. Configuring a queue does not create a partition:
partitions are created by the first push that names one. The key used to carry the id of
a `Default` partition the engine never used, and it was removed on 2026-07-30 along with
the rows-engine table that held it. The key itself is kept so the response shape does not
change.

The `options` object echoes the resolved configuration: the values actually
stored, after defaulting, `dedupWindowSeconds` included. Clients assert on
`configured === true` and round-trip the option keys, so the body is never
reshaped by the broker.

`queen.configure_queue_v1` owns every configuration write: one row,
`queen.queues`, carries the whole configuration including `lease_time` and
`dedup_window_seconds`. After the stored procedure returns, the handler does one
more thing: it drops this queue's cached lease time (locally, and on mesh peers
via a `QUEUE_CONFIG_SET` frame), so a `leaseTime` change is visible on the next
pop. If the stored procedure returns a body carrying an `error` key, the handler
short-circuits before that invalidation.

Failures are `500` with `{"error":"configure failed: ..."}`.

## GET /api/v1/resources/queues

Lists the tenant's queues.

```json
{
  "queues": [
{
  "id": "0198...",
  "name": "orders",
  "namespace": "billing",
  "task": "invoice",
  "createdAt": "2026-07-30T09:12:44.011Z",
  "partitions": 8,
  "retainedBytes": 194883,
  "segments": { "segments": 41, "messages": 12904 },
  "messages": { "total": 12904, "pending": 310, "processing": 24 }
}
  ]
}
```

Two different clocks feed one object, and the difference matters when you read
these numbers:

- `partitions`, `segments` and `messages.total` are computed live per request
  from `queen.log_partitions` / `queen.log_segments` (`log_queue_stats_all_v1`).
  `messages.total` is the count of **retained** frames, not the backlog.
- `messages.pending` and `messages.processing` come from `queen.stats`, which a
  background reconciler refreshes every `STATS_INTERVAL_MS` (default `10000` ms)
  on whichever replica wins advisory lock `737002`. They are the same watermark
  values the overview sums, so the list agrees with the rest of the dashboard
  rather than with a fresher count.

> **Caution**
>
> `namespace` and `task` query parameters are accepted and **ignored**. The handler
> takes no query extractor at all: it always serves the full list for the tenant.
> Filter client-side, or use `GET /api/v1/status/queues`, which does apply
> `queue`, `namespace` and `task`. See [Status and metrics](/reference/http/status-metrics).

## GET /api/v1/resources/queues/:queue

One queue in detail: identity, per-partition stats, and totals.

```json
{
  "id": "0198...",
  "name": "orders",
  "namespace": "billing",
  "task": "invoice",
  "createdAt": "2026-07-30T09:12:44.011Z",
  "partitions": [
{
  "id": "0198...",
  "name": "Default",
  "createdAt": "2026-07-30T09:12:44.031Z",
  "stats": {
    "total": 1600, "pending": 40, "processing": 8,
    "completed": 1552, "failed": null, "deadLetter": 0
  },
  "oldestMessage": "2026-07-30T09:12:45.100Z",
  "newestMessage": "2026-07-30T10:44:02.900Z"
}
  ],
  "totals": { "total": 12904, "pending": 310, "processing": 24, "completed": 12570, "failed": null, "deadLetter": 0 },
  "retainedBytes": 194883,
  "segments": { "segments": 41, "messages": 12904 }
}
```

Notes that follow from the log engine: `pending` is measured against the **worst**
(lowest) committed cursor across the queue's consumer groups, so a single lagging
group holds the number up; `failed` is always `null` because there is no
per-message failure state; and `retainedBytes` is a `queen.stats` value, refreshed
on the stats cadence. A queue with no `queen.queues` row answers `404` with
`{"error":"Queue not found"}`.

## DELETE /api/v1/resources/queues/:queue

Access level **admin**. One call to `queen.delete_queue_v1`
(`013_analytics`) owns the whole delete. It removes, in order, the queue's
`queen.log_txns` and `queen.log_dlq` rows (neither carries a foreign key by
design, because the purge path must never pay FK-trigger cost, so they are
deleted explicitly while the partitions still resolve), then the single `queen.queues`
row. That one delete cascades to everything else: `queen.log_partitions` and,
under it, `log_segments` and `log_consumers`, plus the consumer watermarks,
queue-scoped consumer-group metadata, lag metrics and `queen.stats` rows.
Before the queue-identity merge this was two non-atomic steps (a stored
procedure plus a broker-side teardown of the engine tables); it is now one
transaction.

The response is `200` with a JSON body, never `204`: the SDKs read
`res.deleted === true` and a bodyless response would make them see `null`.

```json
{ "deleted": true, "queue": "orders", "existed": true }
```

Deleting a queue that does not exist is also `200`, with the body made
self-consistent rather than misleading:

```json
{
  "deleted": false,
  "queue": "nope",
  "existed": false,
  "message": "Queue not found, nothing was deleted"
}
```

The status stays `200` because the SDKs use delete-before-create as a cleanup
idiom; a `404` would turn a no-op into a thrown error for them. Check `deleted`,
not the status code.

Message traces are **not** deleted. `queen.message_traces` had its foreign keys
dropped so it could hold traces for both engines, so a queue delete leaves its
traces behind. See [Traces](/reference/http/traces).

## Aggregate listings

Three sibling reads, all `read-only`, all passing their stored procedure's JSON
through unchanged.

| Route | Returns |
| --- | --- |
| `GET /api/v1/resources/overview` | Counts (`queues`, `partitions`, `namespaces`, `tasks`), a `messages` block (`total`, `pending`, `processing`, `completed`, `failed`, `deadLetter`), a `lag` block (time and offset, avg/median/min/max), a `throughput` block (`ingestedPerSecond`, `processedPerSecond` over the last 5 minutes ÷ 300), `timestamp` and `statsAge`. |
| `GET /api/v1/resources/namespaces` | `namespaces[]` with `namespace`, `queues`, `partitions`, `messages.total`, `messages.pending`. |
| `GET /api/v1/resources/tasks` | The same shape on the `task` axis. |

> **Caution**
>
> Before 2026-07-30 the `partitions` count on the namespaces and tasks routes was wrong, and
> usually **zero**: it counted rows in the retired rows-engine partition table, whose only
> writer was `/configure`. A queue created implicitly by a push contributed nothing to it.
> It now counts live log partitions and agrees with the per-queue `partitions` that
> `/api/v1/resources/queues` has always reported correctly, so expect these two numbers to
> go up on an existing dashboard.

`statsAge` is the age in seconds of the numbers you are reading, or `-1` when
nothing has been reconciled yet. Treat a large `statsAge` as "the reconciler is
not running", not as "the queue is idle".

> **Note**
>
> `GET /api/v1/resources/overview` has two bodies. For the **default tenant** (which
> is every request when `QUEEN_TENANCY_HEADER` is off), it blends cluster-wide
> lifetime counters from `queen.worker_metrics_summary` and counts
> `queen.queues` without a tenant predicate, so on a shared cell the default-tenant
> overview is cell-wide. For a specific tenant it is re-derived from that tenant's
> `queen.stats` queue rows and its own per-queue counters. The `namespaces` count
> also changes meaning between the branches: `queen.stats` namespace rows for the
> default tenant, `COUNT(DISTINCT namespace)` for a tenant.

## Access levels

Access levels are a role *set*, not a ladder. A `WriteOnly` token passes
`/api/v1/push` and is rejected on every route on this page, including the
listings.

| Route | Level |
| --- | --- |
| `POST /api/v1/configure` | read-write |
| `GET /api/v1/resources/queues` | read-only |
| `GET /api/v1/resources/queues/:queue` | read-only |
| `DELETE /api/v1/resources/queues/:queue` | admin |
| `GET /api/v1/resources/overview` | read-only |
| `GET /api/v1/resources/namespaces` | read-only |
| `GET /api/v1/resources/tasks` | read-only |

Every route here resolves a tenant from the request, so with
`QUEEN_TENANCY_HEADER` on they are scoped to the caller's tenant. The full route
table, generated from the router and the authorization function, is in the
[reference index](/reference).

Source: https://queenmq.com/reference/http/queues/index.mdx
