> ## 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.

# Hold and pause reason codes: tagging why agents hold a call or step away

> Record why a caller was put on hold and why an agent went unavailable — with a fixed platform hold-reason catalog and a tenant-extendable pause (aux) catalog that feeds the wallboard, WFM adherence, and after-call reporting.

# Hold and pause reason codes

A hold reason code says why an agent placed a caller on hold; a pause reason code (also called an aux code) says why an agent is away and not taking calls. Tagging either one turns an anonymous duration — *4 minutes on hold*, *22 minutes away* — into a supervisor-readable fact: *verifying information*, *consulting a colleague*, *lunch*, *training*.

Both catalogs cover the same reporting questions from different sides, so they share this page:

* **Hold reason codes** attach to a live call. The catalog is fixed by the platform; agents pick from it but you cannot add entries.
* **Pause reason codes** attach to an agent's availability state. The catalog ships platform defaults, and owners and admins can add their own codes to match their operation.

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

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

**Scope:** `voice:read` to list either catalog. Create, update, and delete on the pause catalog require the `owner` or `admin` role; the hold catalog is read-only for everyone.

***

## Hold reason codes: why the caller is on hold

Agents still place a caller on hold the same way they always have — from the softphone or with `POST /api/v1/voice/calls/{callId}/hold`. A hold reason code is the optional tag attached with that hold so the hold has context instead of being an unexplained gap.

The catalog is a **fixed platform list** — the reasons Five9, Genesys, and NICE each ship out of the box, consolidated into one set. It is not tenant-editable, and that is deliberate: hold reasons are short and occur per call, so every tenant reads the same seven options and supervisor roll-ups compare like with like.

| Code                | Label                   |
| ------------------- | ----------------------- |
| `verifying_info`    | Verifying information   |
| `checking_system`   | Checking system         |
| `consulting`        | Consulting a colleague  |
| `researching`       | Researching the request |
| `supervisor_review` | Awaiting supervisor     |
| `processing`        | Processing the request  |
| `other`             | Other                   |

Codes are lowercase slugs matching `^[a-z0-9_-]+$`. They are **stable across releases** — a code you tag today resolves to the same label in a year, so historical reporting never dangles. Because the catalog is code-defined in the platform itself, it exists for every tenant without any migration or setup.

## Attaching a hold reason

The softphone hold picker lists the catalog, and the agent taps it as they place the caller on hold:

```http theme={null}
POST /api/v1/voice/calls/call_01HZK…/hold
Content-Type: application/json

{
  "reason_code": "consulting"
}
```

The code is normalised (trimmed and lowercased), so `Consulting` and `consulting ` resolve to the same tag. An unknown slug is rejected with `422` — the error response includes the valid codes, so a typo never becomes a silent untagged hold, and your integration never has to guess at the catalog.

What happens to the tag:

* The reason is stored on the call record itself — `hold_reason` and its resolved `hold_reason_label` land in the call's metadata alongside the hold start timestamp. No catalog row to reference, no schema change to deploy.
* **Releasing the hold clears the tag.** When the agent resumes the call, the current hold's reason slate resets; the pattern is "what is the current hold for," not a standing flag.
* The reason is **optional**. `POST .../hold` with no body works exactly as before — long-standing integrations that never send a `reason_code` keep working unchanged.

Supervisors see the label on the live wallboard while the hold runs, and it carries into after-call reporting once the call ends — see [Reporting](#reporting) below.

***

## Pause (aux) reason codes: why the agent is away

Pause reason codes tag agent availability, not a call. When an agent flips from available to an away state — lunch, break, training — they pick a reason. Those reasons do two jobs: they give supervisors a readable wallboard, and they give WFM adherence something precise to measure scheduled time against.

Unlike hold reasons, this catalog is **tenant-extendable**. The platform ships defaults, and owners and admins add their own:

* **Defaults** — `lunch`, `bio`, `training`, `break`, `coaching`. These are always available to every tenant and cannot be shadowed by a tenant row.
* **Tenant additions** — domain-specific reasons like `team_standup`, `qa_review`, or a shift supervisor's `one_on_one`. Tenant codes carry a display label, an optional per-reason duration ceiling, and an active flag they can flip to retire a code without orphaning history.

Each tenant row carries `code` (the same `^[a-z0-9_-]+$` slug shape), `display_label`, an optional `max_seconds` ceiling, and `active`. A code created while inactive is staged but invisible to pickers until activated; deactivating is a soft-delete, and agents who used the retired code keep its label in historical records.

Agents pick a reason when they set their own away state; supervisors and admins can also set it for an agent from the agent-activity surface. The resolved reason shows up:

* On the **wallboard** — the away tile renders the label next to the away duration, so a glance tells "three on lunch, two in training" from "two missing agents."
* In the **agent activity feed** — every state change is recorded with the reason code, so a day-review can reconstruct when each agent went away and for what.
* In **WFM adherence** — the reason an agent set is read against their scheduled activity, so an unscheduled `meeting` reads differently from a scheduled `training` deviation. See [Workforce-management workflows](/guides/wfm-workflows) for how adherence consumes the feed.

Agents flipping status can only send a code from the union of platform defaults and the tenant's active additions — a response of `422` names the allowed set on an invalid attempt, so a rogue integration never produces a reason supervisors cannot report on.

Pause codes have a **per-reason duration ceiling** (`max_seconds`). When an agent stays in a reason longer than its ceiling, a supervisor alert fires — a reason-coded overrun, such as *lunch over 30 minutes*. Leave the ceiling blank to defer that to a manual supervisor nudge.

***

## Reporting: what the tags buy you

Hold and pause reasons share one purpose: they answer "how long, and was that time justified."

* **After-call reporting** — a call that spent 4 minutes on hold now reports *4 minutes — Consulting a colleague* instead of an unexplained block. Hold mix (how much of handle time is hold, broken down by reason) becomes a coaching input instead of an anecdote.
* **Supervisor hold-time alert** — when a caller sits on hold past the threshold (60 seconds by default), the wallboard emits a long-hold alert. The reason tag is alongside the duration, so the supervisor triaging "who needs a hand" sees *Awaiting supervisor* and acts on it instead of walking to the agent's desk to ask.
* **Pause reporting** — adherence exceptions, shrinkage, and break-overrun alerts all segment by reason. "Away 12% of staffed hours" is a very different problem when most of that 12% is approved training versus when it is a miscellany of uncoded absences.

Without the tag, none of this is askable — you only see the duration. With it, every duration carries its why.

***

## API examples

All examples use `https://api.orbit.devotel.io/api/v1/voice` with a session token or API key. Replace `call_01HZK…` and the pause-code id with real values from your tenant.

### List the hold-reason catalog

Read-only; any authenticated user can call it. Static — this endpoint never touches a tenant schema.

```bash theme={null}
curl "https://api.orbit.devotel.io/api/v1/voice/hold-reason-codes" \
  -H "X-API-Key: dv_live_sk_your_key_here"
```

```json theme={null}
{
  "data": {
    "items": [
      { "code": "verifying_info", "display_label": "Verifying information" },
      { "code": "checking_system", "display_label": "Checking system" },
      { "code": "consulting", "display_label": "Consulting a colleague" },
      { "code": "researching", "display_label": "Researching the request" },
      { "code": "supervisor_review", "display_label": "Awaiting supervisor" },
      { "code": "processing", "display_label": "Processing the request" },
      { "code": "other", "display_label": "Other" }
    ]
  }
}
```

### Hold a call with a reason, then release it

```bash theme={null}
# Place the caller on hold, tagged with a reason
curl -X POST "https://api.orbit.devotel.io/api/v1/voice/calls/call_01HZK…/hold" \
  -H "X-API-Key: dv_live_sk_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{ "reason_code": "consulting" }'

# Resume the call — the reason tag clears
curl -X POST "https://api.orbit.devotel.io/api/v1/voice/calls/call_01HZK…/unhold" \
  -H "X-API-Key: dv_live_sk_your_key_here"
```

An invalid code returns `422` with the full valid catalog in `error.details.validCodes`, so a picker can re-render from the response without a second fetch.

### Read hold history for a call

The tagged holds live on the call record. Fetch the call to see each hold segment with its reason:

```bash theme={null}
curl "https://api.orbit.devotel.io/api/v1/voice/calls/call_01HZK…" \
  -H "X-API-Key: dv_live_sk_your_key_here"
```

The response includes the current `hold_reason` and `hold_reason_label` in the call's metadata while the call is held, and the hold duration stamps (`hold_started_at`, unhold timestamps) that after-call reporting aggregates.

### List the pause-reason catalog (defaults + tenant codes)

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

Active rows only by default. Add `?include_inactive=true` to render the archived set in an admin editor.

### Add a tenant pause reason (owner or admin)

```bash theme={null}
curl -X POST "https://api.orbit.devotel.io/api/v1/voice/agents/pause-reason-codes" \
  -H "X-API-Key: dv_live_sk_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "code": "team_standup",
    "display_label": "Team standup",
    "max_seconds": 900
  }'
```

A duplicate active slug returns `409`; retiring and recreating is the way to replace a label. Code slugs are case-normalised at the boundary — send them lowercase.

### Update or retire a tenant pause reason (owner or admin)

```bash theme={null}
# Change the label or the ceiling — code slug is immutable
curl -X PATCH "https://api.orbit.devotel.io/api/v1/voice/agents/pause-reason-codes/prc_abc123" \
  -H "X-API-Key: dv_live_sk_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{ "display_label": "Daily standup", "max_seconds": 1200 }'

# Soft-delete — historical uses keep the label
curl -X DELETE "https://api.orbit.devotel.io/api/v1/voice/agents/pause-reason-codes/prc_abc123" \
  -H "X-API-Key: dv_live_sk_your_key_here"
```

Reactivate with `PATCH { "active": true }`.

### Set an agent's away state with a reason

Agents (and admins acting for an agent) post the status change with a reason from the union of defaults and active tenant codes:

```bash theme={null}
curl -X POST "https://api.orbit.devotel.io/api/v1/voice/agents/agent_12/status" \
  -H "X-API-Key: dv_live_sk_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{ "status": "away", "reason_code": "team_standup" }'
```

`422` names the allowed set if the code is not found, so the picker always stays aligned with the catalog it rendered from.

***

## Edge notes

* **Reasons are optional everywhere.** A hold posted with no body, or a status flip with no `reason_code`, behaves exactly as before the catalogs existed — no breaking change for integrations built before this.
* **Hold catalog slugs are stable across releases.** The seven platform codes never change name; report queries keyed on `code` are safe to persist.
* **Tenant additions only apply to pause codes.** There is no tenant extension point for hold reasons — that is a deliberate parity choice, not an oversight, since a per-call picker should never need domain training.
* **Pause code slugs are immutable once created.** Retire (soft-delete) and recreate to rename; a hard rename would orphan the audit and adherence records that carry the slug.
* **Codes normalise.** `Consulting`, `consulting`, and `consulting` are the same tag; keep your integration storing the canonical lowercase slug.
* **The catalogs are tenant-scoped for pause, global for hold.** One tenant's added pause reasons are invisible to others; the hold catalog is identical for everyone.

## Related references

* [Workforce-management workflows](/guides/wfm-workflows) — how adherence consumes pause reasons for exceptions and scoring
* [Wallboard](/guides/wallboard) — where supervisors see live hold and away durations with their reason labels
* [Wrap-up codes](/voice/wrap-up-codes) — the end-of-call disposition catalog, a separate reporting axis from hold and away reasons
* [Voice API reference](/api-reference/voice) — full endpoint inventory
