> ## Documentation Index
> Fetch the complete documentation index at: https://docs.orbit.devotel.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Set up and run voice queues

> Create an ACD queue, staff it with skilled agents, manage caller priority, watch queue health, flip emergency mode, and wire supervisor alert rules — end to end.

A voice queue is an ACD (Automatic Call Distribution) target: inbound callers wait in FIFO order, the platform dispatches each caller to an eligible available agent, and everything about the queue — depth, wait times, SLA, occupancy, dispositions — is observable over the API. This guide walks the full operator loop: create the queue, enroll members, control priority, verify staffing, watch health, run an emergency cutover, record wrap-up codes, and wire alerting.

Inbound routing to a queue is usually the [flow's `queue` node](/guides/build-ivr-flow) or a DID's routing config; this page picks up where that traversal ends.

**Base path:** `/api/v1/voice`

**Authentication:** Clerk session (`Authorization: Bearer <token>`) or API key (`X-API-Key`).

**Scope:** `voice` write for management; `voice` read for monitoring.

***

## 1. Create the queue

`POST /api/v1/voice/queues` creates the queue and returns its id (an opaque `queue_*` string every subsequent call uses). Every queue setting is tenant-owned: you choose the SLA target, the overflow behavior, hold-music and announcements, the skills a caller must match, and the routing strategy.

```bash cURL theme={null}
curl -X POST "https://api.orbit.devotel.io/api/v1/voice/queues" \
  -H "X-API-Key: dv_live_sk_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Support",
    "maxWaitSeconds": 300,
    "targetServiceLevelSeconds": 60,
    "skills": ["english", "billing"],
    "overflowAction": "voicemail",
    "routingStrategy": "longest-idle",
    "welcomePromptUrl": "https://media.example.com/support-welcome.mp3",
    "announcePosition": true,
    "announceWaitTime": true
  }'
```

Key fields you set at creation (all optional except `name`; defaults noted):

* **`skills`** — the skill tags the queue requires. A call's `requiredSkills` (set at enqueue) only dispatches to members whose own skills cover them; leave empty to accept any member.
* **`overflowAction`** — what happens when a caller exceeds `maxWaitSeconds` (default 300s, bounds 30–3600). Choices: `voicemail` (default), `childQueue` (fails over to a designated queue), or `hangup`.
* **`targetServiceLevelSeconds`** — SLA numerator for the live dashboard's Service Level metric (default 20s, bounds 5–300).
* **`announcePosition` / `announceWaitTime`** — play queue position or estimated wait time to waiting callers (both default `true`; `announceIntervalSeconds` controls the spacing, default 30s, bounds 15–180).
* **`routingStrategy`** — member selection preference at dispatch time (default `longest-idle`).
* **`welcomePromptUrl` / `periodicPromptUrl` / `holdMusicUrl`** — the queue's own playback prompts. A malformed URL returns `422` before persistence.
* **`rnaTimeoutSeconds`** — ring-no-answer timeout before the platform re-dispatches to the next member (default 30s, bounds 5–120).
* **Callback tune-ups** — `callbackMaxAttempts` and `callbackRetryIntervals` drive the abandoned-callback rescue schedule when `abandonedCallbackRescueEnabled` is on.
* **Dispatch tune-ups** — `maxHoldSeconds`, `skillPriorities`, `minSkillLevel`, and `maxOccupancyPct` are optional dispatch-level edits.

`PUT /api/v1/voice/queues/{id}` accepts the same body as a partial update — omitted fields keep their stored value. `DELETE /api/v1/voice/queues/{id}` removes the queue; its waiting callers are terminated and its active calls route through the overflow fallback.

## 2. Enroll members

Agents become eligible the moment they appear on the queue's member list. Membership carries the per-member dispatch order, the member's own skill tags (intersected with the queue's requirements), optional proficiency levels, and the initial state.

```bash cURL theme={null}
curl -X POST "https://api.orbit.devotel.io/api/v1/voice/queues/queue_supp/members" \
  -H "X-API-Key: dv_live_sk_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "agentUserId": "user_01HJ...",
    "priority": 100,
    "skills": ["english", "billing"],
    "skillLevels": { "english": 3, "billing": 2 },
    "state": "offline"
  }'
```

* **`agentUserId`** — the agent's user id; either the Clerk-managed id or a platform-minted `agent_<hex>` id works.
* **`priority`** — dispatch preference within the queue; lower wins (a VIP responder tier picks 1–10, a fallback tier picks 200+). Defaults to 100.
* **`skills`** — the member's skill list. An **empty array means the member is eligible for every call on the queue** regardless of a call's `requiredSkills`; a non-empty list intersects with the queue's `skills` and the call's `requiredSkills` at dispatch. Use it when a bilingual agent should only take Spanish calls or a senior agent should only take escalations.
* **`skillLevels`** — proficiency per skill (1–5), intersected with the queue's `minSkillLevel` and composite weighting at dispatch.
* **`state`** — initial state while you wire the roster; the agent later flips themselves `available` via `POST /api/v1/voice/agents/{id}/status`. The queue-safe default is `offline`, so you can enroll the whole roster before anyone logs in.

Loop this call for each agent. To adjust membership later use `PATCH /api/v1/voice/queues/{id}/members/{memberId}` with any subset of `priority`, `skills`, `state`, `skillLevels`; `DELETE` removes the membership. List the roster with `GET /api/v1/voice/queues/{id}/members` (keyset-paginated, `?limit=50&cursor=...`).

<Warning>
  An agent whose skills do not satisfy the queue's requirements (or who is not a member at all) is never dispatched on a call for that queue, even while the agent reports `available` for other queues. Enroll every agent before you announce staffing in the supervision surface — the available-agents count below ignores non-members.
</Warning>

## 3. Priority queueing

Enqueue a caller when your routing logic parks the call on the queue (e.g. IVR `queue` node outbound, or a manual dispatch). Lower `priority` numbers win; a caller bumped by a supervisor lands at priority 0.

```bash cURL theme={null}
curl -X POST "https://api.orbit.devotel.io/api/v1/voice/queues/queue_supp/enqueue" \
  -H "X-API-Key: dv_live_sk_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "callControlId": "call_01HZ...",
    "callerNumber": "+14155550212",
    "priority": 10,
    "requiredSkills": ["billing"]
  }'
```

* **`callControlId`** — the voice leg's platform call id. The queue's FIFO, max-wait, and overflow semantics anchor on this id; once a call is enqueued, every subsequent queue endpoint keys on it.
* **`priority`** — initial ranking; smaller numbers pass earlier (0–99, default 10). Use supervisor-set business rules: a callback reservation comes in with an explicit priority, a VIP bump pushes priority to 0, a partners-tier queue assigns 5.
* **`requiredSkills`** — the caller's own skill tags (e.g. a speech intent resolved upstream, or the contact's account attributes) that intersect with member eligibility.

When a caller needs to jump the line, the supervisor bumps the call:

```bash cURL theme={null}
curl -X POST "https://api.orbit.devotel.io/api/v1/voice/queues/queue_supp/calls/call_01HZ.../bump" \
  -H "X-API-Key: dv_live_sk_your_key_here"
```

That endpoint is admin-only — only a supervisor role re-queues a caller. Individual waiting entries are also adjustable with `PATCH /api/v1/voice/queues/{id}/entries/{entryId}` using a `priority` from 0–99 (0 is the queue head; the original wait clock is preserved, so a bump is a re-score, not a re-join).

## 4. Check membership before staffing

Before you trust the floor, the platform gives a tenant-wide head count of agents whose queue-membership plus skills make them dispatchable:

```bash cURL theme={null}
curl "https://api.orbit.devotel.io/api/v1/voice/queues/available-agents" \
  -H "X-API-Key: dv_live_sk_your_key_here"
```

**200 OK** body:

```json theme={null}
{ "data": { "availableAgents": 12 }, "meta": { ... } }
```

This is a **DISTINCT count**, not the sum of each queue's `stats.agentsAvailable`. An agent who is a member of two queues (and whose skills satisfy both) is counted once — summing the per-queue figures double-counts bilingual agents and skews the staffing decision. Use `available-agents` in any capacity planner.

## 5. Watch queue health

Loop every supervisor aggregation in as often as the board refreshes.

**Live snapshot:** `GET /api/v1/voice/queues/{id}/live` is an SSE stream, not a plain snapshot — the endpoint keeps the connection open and publishes, on each tick, the named metrics plus any alert rules that fire. If you only need one tick, poll the aggregate routes below; `live` is the continuous feed that powers the in-app wallboard.

```bash cURL theme={null}
curl "https://api.orbit.devotel.io/api/v1/voice/queues/queue_supp/stats" \
  -H "X-API-Key: dv_live_sk_your_key_here"
```

This is the headline-numbers call: queue config plus current depth, waiting, longest wait, SLA, agents total/available/busy, occupancy, and hangup counters. Poll it per queue for a plain dashboard; do not chain it as your aggregation — every metric below ties a different window or distribution.

**Windowed analytics:** `GET /api/v1/voice/queues/{id}/analytics?from=...&to=...&interval=30m|1h|1d` (30-minute default; optional `fcr_window_hours` tunes the First-Call-Resolution heuristic past the default-24h definition) returns per-interval `sla_pct`, `aht_seconds`, `asa_seconds`, `abandon_pct`, `occupancy_pct`, plus a `fcr_pct` (heuristic resolved against call logs) with the echoed window so you can label the column "FCR-24h". ASA here is answered-queue-wait only; abandoned calls are excluded.

**Hour-of-week heatmap:** `GET /api/v1/voice/queues/{id}/heatmap` normalises into a 7×24 grid (missing cells are zero) so a wallboard can show when the queue's SLA band saturates each week.

**Queue comparison:** `GET /api/v1/voice/queues/comparison` ranks queues side by side.

**Hangup distribution:** `GET /api/v1/voice/queues/{id}/hangup-breakdown` answers which overflow disposition (voicemail, child queue, hangup) takes the terminal volume.

**Per-member leaderboard:** `GET /api/v1/voice/queues/{id}/agent-leaderboard` ranks members on a chosen metric across the same supervisor visibility rules as the platform-wide [wallboard](/guides/wallboard).

**Skill-by-window channeling:** `GET /api/v1/voice/queues/{id}/skill-breakdown` collapses the `[from,to]` window into one row per skill (no interval bucketing) for a per-skill staffing check.

## 6. Emergency mode

Flip the queue straight to an off-queue destination when the floor takes an outage — fire drill, all-offline, an upstream carrier cutover. Arming requires a destination (E.164); the controller refuses an `active: true` arm with no landing, and disarm leaves the queue's normal overflow behavior intact.

```bash cURL theme={null}
curl -X POST "https://api.orbit.devotel.io/api/v1/voice/queues/queue_supp/emergency" \
  -H "X-API-Key: dv_live_sk_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{ "active": true, "destination": "+14155558210" }'
```

The inbound routing bridge checks the queue's `emergencyActive` flag on every ring, so a flip takes effect on the next call. Disarm by POST'ing `active: false` (destination optional, `destination: null` explicitly clears). Every `queue.emergency_toggled` event lands in the audit log so post-incident review correlates the cutover to a named supervisor.

<Warning>
  Emergency override is a zero-player safety rail: it bypasses the queue's `overflowAction` (which sends an overload caller *after `maxWaitSeconds`* to voicemail or a fallback queue; it cannot rescue callers while the outage happens). Set the destination the moment a supervisor arms the override — the API demands it when you flip to `active: true`, not when the phone rings.
</Warning>

## 7. Wrap-up and dispositions

Per-queue wrap-up codes let a finished agent record the outcome. The catalog is administered on `GET/POST/PATCH/DELETE /api/v1/voice/queues/{queueId}/dispositions`, with two guards built into disposition recording — only the assigned agent records the outcome, and free-text outcomes are rejected. A finished conversation posts its disposition with `POST /api/v1/voice/queues/{queueId}/calls/{callId}/disposition`:

```json theme={null}
{ "dispositionCode": "billing_adjustment", "note": "..." }
```

The endpoint is idempotent on `(queueId, callId)`. Setting `requireDisposition` on a queue blocks the `busy → available` flip while the agent's most recent finished call carries no recorded disposition, and the queue's AI suggestion endpoint `GET /api/v1/voice/queues/{queueId}/calls/{callId}/disposition/suggestion` pre-fills a hypothesis to speed up the manual pick. The full lifecycle — enforcement gate, AI suggestion, outlier-flag analytics — lives on the [wrap-up codes reference](/voice/wrap-up-codes).

## 8. Wire queue alert rules

A supervisor defines thresholds the in-app live feed and the outbound webhook/email surfaces fire on. Alert rules bind to a queue, pick a metric, operator, and threshold, and become real when enabled.

```bash cURL theme={null}
curl -X POST "https://api.orbit.devotel.io/api/v1/voice/queues/queue_supp/alert-rules" \
  -H "X-API-Key: dv_live_sk_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Queue too deep",
    "metric": "queue_depth",
    "op": "gte",
    "threshold": 25,
    "action": "webhook",
    "enabled": true,
    "cooldown_seconds": 300,
    "delivery_config": { "webhook_event_type": "queue.alert_rule_fired" }
  }'
```

* **`metric`** choices: `queue_depth`, `asa_seconds`, `abandon_rate`, `sla_pct`, `aht_seconds`, `occupancy_pct`, `agents_available`. The canonical per-queue SLA breach alert is `sla_pct` with `op = lt` and a threshold like `0.8`.
* **`action`** — `sse` for an in-dashboard banner (default), `webhook` to fan out a tenant webhook event of your choosing (the `queue.alert_rule_fired` slug is the default; PagerDuty/Opsgenie/custom may subscribe), or `email` to notify org admins. `delivery_config` tunes the override slug or the subject prefix for email.
* **`cooldown_seconds`** (0–86400; 300 default) suppresses re-fire; a rule that just fired is silenced across every delivery surface until its cooldown elapses. `0` restores legacy fire-every-tick.
* Update with `PATCH /api/v1/voice/queues/{id}/alert-rules/{ruleId}` — the patch needs at least one of `name`, `metric`, `op`, `threshold`, `action`, `delivery_config`, `enabled`, `cooldown_seconds`; `DELETE` on a rule flips every dispatch surface off. List active rules with `GET /api/v1/voice/queues/{id}/alert-rules`.

A rule fires with the same audit parity the supervisor's own edit emits — the queue's SSE event, webhook fan-out payload, and email subject all carry the metric, operator, and threshold so post-incident review is not a chase through Slack.

## 9. Define the per-queue SLA breach policy

Alert rules cover ad-hoc thresholds; the SLA breach policy is the queue's persistent service-level objective — the "80% of calls answered within 30 seconds over a 15-minute window" contract a queue breaches. You set it once per queue; every breach against it is logged and routed to the alert surfaces you choose.

```bash cURL theme={null}
curl -X PUT "https://api.orbit.devotel.io/api/v1/voice/queues/queue_supp/sla-breach/policy" \
  -H "X-API-Key: dv_live_sk_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "enabled": true,
    "targetPercentage": 80,
    "thresholdSeconds": 30,
    "evaluationWindowMinutes": 15,
    "breachAction": "reroute_to_overflow_queue",
    "breachCooldownSeconds": 300,
    "escalationSteps": [
      { "stepIndex": 1, "delayMinutesAfterBreach": 0,  "actionType": "notify",      "actionConfig": {} },
      { "stepIndex": 2, "delayMinutesAfterBreach": 15, "actionType": "slack_alert", "actionConfig": {} }
    ]
  }'
```

* **`targetPercentage`** (1–100) is the service-level percent the queue must hold; **`thresholdSeconds`** is the answer-within bound a call must beat to count, and **`evaluationWindowMinutes`** is the rolling window the service level is measured over. The queue's reported service level compares against the window's offered-minus-short-abandon denominator — the same convention the analytics endpoint reports.
* **`breachAction`** is the one-shot response when no escalation ladder is set: `open_ticket` (notify org admins in-app and by email), `page_supervisor` (publish the `queue.alert_rule_fired` tenant webhook — the payload a PagerDuty or Opsgenie subscription pages on), `reroute_to_overflow_queue` (both), `enqueue_callback` (webhook, so downstream automation queues a callback), or `none` (log the breach, alert nobody). Read the current policy with `GET`; remove it with `DELETE` — a queue with no policy logs nothing and alerts nothing.
* **`escalationSteps`** is the per-queue ladder. When any step is set, the ladder replaces the one-shot `breachAction`: step 1 notifies the duty supervisors immediately on breach, step 2 pages the escalation channel if nobody has fixed the queue 15 minutes later. Steps without a value for `delayMinutesAfterBreach` of `0` are scheduled responses — they fire when a downstream executor reads the logged breach, not in the scan request itself. Each step's `actionType` uses the same notify/reassign/slack/webhook vocabulary as inbox SLA policies.
* **`breachCooldownSeconds`** (0–86400) dedupes repeated breach alerts: a queue scanned every few seconds still pages at most once per cooldown window (floored at 60 seconds).

**Evaluate and log a breach** — `POST /api/v1/voice/queues/{id}/sla-breach/scan` takes a window sample (`offeredCalls`, `answeredWithinSla`, optional `shortAbandons`, `windowStart`), compares the observed service level against the queue's stored policy, and on a breach whose cooldown is clear: appends the breach event, fires the routing the policy selected, and returns the verdict (`breached`, `alert_fired`, `observed_service_level`). A queue with no policy answers `policy_configured: false` and does nothing.

**Audit the breach history** — `GET /api/v1/voice/queues/{id}/sla-breach/events` returns the queue's newest-first breach log (up to 200 entries), each stamped with the observed service level, the policy in force, and the surfaces that actually fired. The supervisor wallboard's breach-log tab reads this same endpoint.

## 10. Troubleshooting

* **Queue is full of callers, but `agentsAvailable` reads zero:** those agents aren't members of this queue, or their member `skills` don't match the queue's requirements. Run `GET /api/v1/voice/queues/{id}/members` to inspect the roster; an agent who exists on paper but has no member record is an empty pool until you enroll them with a POST.
* **A queue is stuck (longest wait explodes, ASA overflows, agents never pick):** confirm the caller's `requiredSkills` intersects at least one member's `skills`. A bad `requiredSkills` value dispatches nowhere and waits forever — flip it at enqueue or enrol a broader-skilled member.
* **Emergencies left on:** a queue that still bypasses its agents after you disarm keeps a destination on file. Run `GET /api/v1/voice/queues/{id}` and inspect `emergencyActive` plus `emergencyDestinationNumber` before blaming the audit — the controller accepts `active: false` with any supplied destination.
* **The `available-agents` figure says N but queues each show a higher `agentsAvailable`:** `available-agents` counts DISTINCT members once; the per-queue stats multiply agents whose skills satisfy more than one queue. The sum always over-reads; never tier-right on the per-queue number.

## See also

* [Build and route an IVR flow](/guides/build-ivr-flow) — the `queue` node that feeds this ACD surface
* [Wallboard](/guides/wallboard) — the TV-mounted display that renders this queue's live tiles
* [Wrap-up codes](/voice/wrap-up-codes) — the per-queue dispositions and enforcement gate
* [Voice queues API reference](/api-reference/endpoints/voice) — parameter and response detail for every queue endpoint
