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

# Troubleshooting: queue SLA forecast callback gate

> Diagnose a 503 + slaCallbackBlocked refusal on queue entry — tell a forecast breach from a head-of-line trip with no agents staffed, pick the suggest-vs-block posture, and rewire the callback dispatch path.

# Troubleshooting: queue SLA forecast callback gate

A queue opted into the [SLA forecast callback gate](/voice/queue-sla-forecast-callback)
can refuse a new inbound caller at the entry check: the forecast projects the
wait the caller is about to accrue (or finds the head-of-line caller already
past the target) and, on a queue set to `block`, diverts the call into the
press-1 virtual-callback consent flow instead of accepting FIFO depth. This
page owns the decision matrix for that refusal — when the gate should
suggest, when it should block, and what to fix when a caller routes into the
press-1 flow unexpectedly. For the depth-cap refusal instead, work the
[queue capacity gates](/troubleshooting/queue-capacity-gates) page; the two
refusals look alike at the call-control layer but have different gates.

## Recognize the signal

The clearest signal is a `503` on `POST /api/v1/voice/queues/{id}/enqueue`
(or on the live-call transfer-to-queue path) whose decision body carries
`slaCallbackBlocked: true` — not a `QUEUE_DEPTH_CAP_REACHED` code:

```json theme={null}
{
  "slaCallbackBlocked": true,
  "slaThresholdSecs": 30,
  "projectedWaitSecs": 87,
  "overflowAction": "callback",
  "queueId": "queue_supp",
  "callControlId": "cc_…"
}
```

* `slaCallbackBlocked` — always `true` on this shape; the SLA gate fired,
  not the depth cap.
* `slaThresholdSecs` — the queue's `targetServiceLevelSeconds` the forecast
  projected against.
* `projectedWaitSecs` — the projected wait the caller was spared; unbounded
  when no agents are staffed.
* `overflowAction` — `callback`, the path the gate drove the call into.

If the response instead carries `error.code: "QUEUE_DEPTH_CAP_REACHED"`,
this page is the wrong gate — work the
[queue capacity gates](/troubleshooting/queue-capacity-gates) page.

## Cause table

| Signal                                                              | Cause                                                                                                                                                                                                                          | Fix                                                                                                                                                                                  |
| ------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `503` + `slaCallbackBlocked: true`, `projectedWaitSecs` in the body | Forecast breached: `waiting × recent average handle time ÷ available agents` crossed the queue's `targetServiceLevelSeconds`, so the gate refused entry on the position math                                                   | Confirm the queue's SLA target is the one you intended, then either raise staffing so projected wait falls under the target, or accept the diversion as the gate working as designed |
| `503` + `slaCallbackBlocked: true` while waiting depth reads 0 or 1 | Head-of-line tripped: the oldest existing waiter already crossed the target, so the queue counts as already breached even when the position math looks fine; a 0-waiting queue can trip on a head whose wait is still accruing | Read `GET /api/v1/voice/queues/{id}/stats` — if the longest wait exceeds `slaThresholdSecs`, the head-of-line check is the gate; fix staffing, not the forecast                      |
| Gate fires on every entry, even with idle agents                    | `targetServiceLevelSeconds` is set too low for the queue's real handle time, or the queue was opted into `block` before callback dispatch was wired                                                                            | Re-pick the posture (below); a queue on `block` with no callback path wired refuses every breaching entry instead of just flagging supervisors                                       |
| Gate never fires, but you expected diversions                       | `targetServiceLevelSeconds` is unset on the queue — a queue with no SLA target never forecasts, and the gate stays fully out of the way (the default fail-open posture)                                                        | Set `targetServiceLevelSeconds` (5–300, default 20) on the queue config; the gate is opt-in through the target                                                                       |
| Caller diverted, supervisor expected only a cue                     | Queue is on `block` when you meant `suggest`                                                                                                                                                                                   | Set the posture back to `suggest` — the forecast still computes, the caller joins normally, and only the supervisor flag fires                                                       |

## Fixes

### 1. Pick the posture per queue: suggest vs block

The `slaCallbackPolicy` field on the queue config is the decision:

* **Omitted (default)** — fail-open; entry always succeeds. Use this on
  queues where you have not wired callback dispatch yet.
* **`suggest`** — the forecast computes, the caller joins the queue
  normally, and the supervisor surface flags "press 1 for a callback
  advised." Use this while you validate callback dispatch, or on queues
  where a hard refusal is worse than real hold.
* **`block`** — a breaching forecast denies the entry and routes the call
  into the press-1 virtual-callback consent flow with the caller's position
  saved.

```bash theme={null}
curl -X PATCH "https://api.orbit.devotel.io/api/v1/voice/queues/queue_supp" \
  -H "X-API-Key: dv_live_sk_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{ "slaCallbackPolicy": "suggest" }'
```

### 2. Confirm the queue's SLA target

The forecast projects against `targetServiceLevelSeconds` (5–300, default
20\) on the queue config. Read the queue back with
`GET /api/v1/voice/queues/{id}` and confirm the target is the bound you
intended: a target of 5 seconds on a queue whose handle time averages 4
minutes blocks nearly every entry; an unset target means the gate never
fires. Set the target once and every surface — wallboard, forecast, breach
policy — reads the same bound.

### 3. Check the press-1 consent prompt copy

On `block`, the diverted caller hears the press-1 virtual-callback consent
prompt: "press 1 to keep your place and get a callback." Confirm the prompt
copy your call-control layer plays on the diverted path is the consent copy
you intend — a diverted caller who hears the wrong prompt is told nothing
about keeping their position, and abandons instead of opting in. The prompt
wiring lives on the [voice queues guide](/guides/voice-queues).

### 4. Verify the callback dispatcher wiring

A blocked entry lands in the same virtual-callback dispatch path the
in-queue press-1 escape uses, with the dispatcher's built-in guards —
emergency-dial blocking and the tenant-owned TCPA calling window — applied
the same way. If you take `block` before the callback path is wired, every
breaching entry refuses callers into a flow that never dials back. Wire
dispatch first on the
[callback runbook](/guides/queue-sla-callback-runbook), then opt into
`block`.

## Decision checklist

Work the refusal in this order:

1. **Read the response shape.** `slaCallbackBlocked: true` means the SLA
   gate; a `QUEUE_DEPTH_CAP_REACHED` code means the depth gate — switch to
   the [queue capacity gates](/troubleshooting/queue-capacity-gates) page.
2. **Compare `projectedWaitSecs` to `slaThresholdSecs`.** If projected wait
   exceeds the threshold, the forecast math fired; check staffing against
   `GET /api/v1/voice/queues/{id}/stats`.
3. **Check head-of-line when depth reads near zero.** A longest-wait value
   over the threshold breaches the queue even at zero waiting — the gate
   reads the head's unserved wait, not the position math.
4. **Confirm the posture and the target.** `GET /api/v1/voice/queues/{id}`
   and read `slaCallbackPolicy` plus `targetServiceLevelSeconds` — an unset
   target or a `block` you never intended is the config bug, not a forecast
   bug.
5. **Change one thing.** Re-set the target, re-pick the posture, or fix
   staffing. A queue at its SLA target with two idle agents has a
   dispatcher problem the gate posture will never rescue.

## What not to do

* **Do not delete the queue.** The refusal is tenant-owned config —
  `slaCallbackPolicy`, `targetServiceLevelSeconds`, and callback dispatch
  wiring are all queue fields you set; deleting the queue destroys the
  membership and the SLA policy with it.
* **Do not rename the gate as a "fail-open default" without confirming the
  overflow policy.** Omitting `slaCallbackPolicy` restores fail-open, but
  the queue's `overflowAction` still decides what happens at max-wait —
  make sure the untargeted queue still overflows to voicemail or a sibling
  before you lift the gate.
* **Do not loop retries inside head-of-line when staffing is unset.** A
  queue with no agents staffed projects an unbounded wait; re-enqueue
  retries against that queue re-refuse at the gate every time. Add agents
  or re-route first.
* **Do not take `block` before callback dispatch is wired.** The gate then
  refuses breaching entries into a flow with no working callback path —
  the supervisor-cue posture (`suggest`) is the correct intermediate step.

## Escalate

If the config reads clean and entries still refuse through the SLA gate:

* capture the queue **id** and the full decision body
  (`slaCallbackBlocked`, `projectedWaitSecs`, `slaThresholdSecs`,
  `overflowAction`) from the refusal,
* include the **callControlId** from the body and the HTTP **request\_id**
  from the response envelope,
* attach a `GET /api/v1/voice/queues/{id}/stats` snapshot and the queue
  config from `GET /api/v1/voice/queues/{id}`.

Send those to support with the tenant's organization name — the operator
will compare the gate's verdict against the queue's live stats and the
dispatcher counters.

## See also

* [Inbound queue SLA forecast + virtual callback gate](/voice/queue-sla-forecast-callback)
  — the product page for the gate: the forecast inputs, the entry decision
  body, and the dispatch safety guards.
* [Queue capacity gates](/troubleshooting/queue-capacity-gates) — the
  sibling gate page for the depth-cap and overflow-chain refusals.
* [Queue SLA forecast-callback runbook](/guides/queue-sla-callback-runbook) —
  the guide that wires objectives, forecast, breach policy, and on-call
  response end to end.
* [Voice queues guide](/guides/voice-queues) — membership, priority, stats,
  and the prompt surface the press-1 consent copy rides on.
* [Per-queue SLA breach alerting and escalation policies](/voice/queue-sla-escalation-policies)
  — the objective ladder that pairs with the entry gate.
