Troubleshooting: dialer preview, power, and predictive modes not advancing
A dialer campaign that stops pulling contacts fails in one of a small number of ways — the claim returns nothing, the claim returns an error, the scheduler paces but no legs originate, or the campaign pauses itself. Every failure in this class is decidable from the mode the campaign runs in and the exact response the dialer API returns. This page maps symptom to cause to fix per mode. The full launch sequence, pacing knobs, and disposal matrix are on Launch an outbound dialer campaign. This page works the failures, not the setup.Mode decoder — who dials, and when
The Dialer API exposes two agent-facing endpoints. The other two modes progress without them.
Two consequences follow from this table that resolve most “stuck
campaign” reports before any deeper triage:
- Preview is the only mode where an agent’s poll drives the
campaign. If no agent polls
next-call, a preview campaign claims nothing and the list sits atpendingforever. Progressive and predictive campaigns keep dialing as long as the scheduler sees a ready agent and eligible contacts. - A
204fromnext-callis the correct answer on every non-preview mode, not an error. The pacing engine originates those calls server-side; the endpoint has nothing to hand an agent.
Symptom index
Find the response you actually get, then work the matching section.Claim returns 404 — the campaign isn’t active
GET /api/v1/dialer/next-call?campaign_id=<id> returns
404 { code: "NOT_FOUND", message: "Active campaign not found" } in
exactly one case: no row in dialer_campaigns matches that id with
status = 'active'. The three reasons that hit in practice:
- The campaign was never activated. A campaign created through
POST /api/v1/dialer/campaignsstarts indraftand dials nothing until youPATCHit toactive. This is also the answer to “preview claim returns 404 after the last contact” — a campaign whose final disposition left it with no remaining work does not auto-deactivate, but one you finished and thenPATCHed tocompletedorpauseddoes return 404 to the next poll. - The campaign auto-aborted. The abandonment ceiling flipped it to
aborted— that status is terminal, andnext-calltreats an aborted campaign the same as a missing one. See Abandonment abort. - Wrong id. A typo or a id from a different workspace. Confirm
with
GET /api/v1/dialer/campaigns/:id— if that returns the campaign with a non-activestatus, the status is the answer.
PATCH {"status": "active"} for a
paused or draft campaign; create a new campaign for an aborted one (the
aborted status cannot be re-activated).
Preview returns an empty claim
A200 with "contact": null and a resolved preview_decision_seconds
means the campaign is active and preview-mode — the eligibility filter
just found nothing. Work the filter’s four inputs in order:
- Is the list uploaded? A campaign with no
POST /campaigns/:id/listsupload claims nothing. The upload is a separate step from creating the campaign. - Are contacts still
pending?GET /api/v1/dialer/campaigns/:id/statsreturns the per-status breakdown. If everything isconnected,failed,dnc, orcallback_scheduledand nothing ispending, the campaign is done — the empty claim is the correct state. - Quiet hours. A contact whose recipient-local hour falls outside
the permitted window is deferred with
timezone_blockedrather than failed; it becomes eligible again when the window opens. A bulk list of US contacts polled at 11 PM ET legitimately claims nothing. The window mechanics and the reason codes the pre-dial gate emits are on Troubleshooting: resolve dialing-window blocked voice calls. - A stuck claim. A contact an agent claimed and never dialed or
dispositioned sits in
dialing. The campaign’s decision countdown (preview_decision_seconds, defaulting to the platform TTL) re-releases it automatically — a past-deadline claim re-enters the eligible pool on the next poll. Ifcontact: nullpersists across several minutes and the stats show rows stuck indialing, one slow-polling agent is holding them; either that agent dials or skips, or the countdown releases them.
Claim-not-held — the claim lapsed
POST /api/v1/dialer/dial returns
409 { code: "CLAIM_NOT_HELD", details: { current_status } } when the
contact_id in the body is not currently in dialing state. The claim
is single-owner and time-boxed; three things end it:
- The countdown expired. The response from
next-callcarriespreview_decision_secondsanddecision_expires_at— dial before that wall-clock moment or the scheduler re-releases the claim and another agent’s poll can take the same contact. - Another agent already took it. Two agents polling the same preview campaign never receive the same contact concurrently, but a skipped or lapsed claim is re-issued — the second agent’s claim invalidates the first.
- The contact was already dispositioned. A completed disposal moves the row to its terminal status; dialing it again is a contract violation the gate rejects.
GET /next-call and dial the newly returned contact_id. Do not retry
the same contact_id in a loop — it will keep 409ing until you claim a
fresh row. If your agent UI keeps a queue of pre-fetched preview cards,
treat 409 CLAIM_NOT_HELD as the signal to re-claim rather than
retrying the stale card.
Softswitch/unhealthy — the breaker tripped
A503 SOFTSWITCH_UNHEALTHY from POST /dial is the dialer protecting
your list. Before originating, the dialer reads the last minutes of
terminated call attempts from your workspace and trips if the share
that failed at the transport layer exceeds the threshold. Without the
gate, a wholesale-leg outage burns through the list in minutes with
zero connects — and each failed origination counts toward the FCC 3%
abandonment ceiling and your caller-id reputation.
On trip, the contact you claimed is reset back to pending, so nothing
is lost. The response details object carries error_rate,
total_attempts, failed_attempts, and the window — capture those.
The fix is to wait and re-poll: the gate re-evaluates on every dial,
and the moment real legs start succeeding again, dispatch resumes.
There is no tenant toggle to bypass the breaker, and disabling it is
never the right answer to a transport outage — retrying against a down
wholesale leg only multiplies the abandon-rate damage.
If the 503 persists beyond a few minutes on a campaign whose caller ID
and numbers are unchanged, escalate — the breaker is telling you the
outbound path for your workspace is down.
Predictive AMD storms
A predictive campaign that dials but where nearly every attempt ends inamd_detected within a second or two of answer is hitting one of these:
- The AMD application is mis-wired. Predictive campaigns resolve an
application SID in three tiers — the campaign’s own override, then the
platform AMD default when
amd_enabledis true, then the standard default. If the AMD-flagged campaign has no AMD application resolvable, origination fails closed rather than dialing blind; checkamd_enabledon the campaign and confirm with support that the workspace’s AMD application is provisioned. - The list is genuinely voicemail-heavy. Answering machines,
IVRs, and operator intercepts all classify as
amd_detected. A purchased or stale list will swim in them; that’s the list, not the AMD. - Pacing outruns agent supply. The scheduler over-dials by
pacing_ratioper ready agent. When answered calls find no ready agent, they abandon — and answered-then-abandoned calls drive the same abandonment metric the ceiling enforces. Droppacing_ratiotoward 1.0 and re-check the campaign’sready agentcount against actual staffed agents.
pacing_ratio,
max_abandon_rate, and amd_enabled on the campaign, plus the
workspace-level abandon ceiling.
Abandonment abort — terminal status
The rolling 30-day abandonment rate (abandon_rate_30d) feeds the
campaign’s abandon gate. When it trips max_abandon_rate (default
0.03, clamped at the FCC hard cap of 3%), the campaign flips to
status: 'aborted'. That status is terminal — you cannot PATCH an
aborted campaign back to active. The error codes page and the launch
guide both call this out; the operational answer is:
- Read the campaign’s
GET /campaigns/:id/stats— theabandon_rate_30mvalue shows whether this was a momentary spike or a sustained over-dial. - Fix the pacing first: lower
pacing_ratio, verify ready agents actually exist while the campaign runs. - Create a new campaign with the corrected pacing settings and re-upload the remaining list. The aborted campaign stays as the audit record.
Nothing advances at all
The campaign isactive, the list is uploaded, next-call returns
contact: null, and predictive/progressive campaigns dial nothing. Work
these in order:
- Is anyone polling / ready? Preview needs an agent polling
next-call; progressive and predictive need agents marked ready in the queue the campaign draws from. A predictive campaign with zero ready agents paces to zero. - Is every remaining contact deferred? US-only lists polled outside
the federal 8 AM–9 PM recipient-local window all sit in
timezone_blockedand claim nothing. Pre-flight the list with the quiet-hours preview endpoint before launch — one preview per distinct area code covers the list. The full window matrix is on Troubleshooting: resolve dialing-window blocked voice calls. - Is the campaign’s caller ID valid? A campaign created against a
caller-id E.164 that has since been released or replaced will fail
origination on every attempt — which then trips the softswitch
breaker above, even though the path is up. After any number/port
change on the numbers side, update the campaign’s
caller_id_e164(or its caller-id pool) before resuming. - Check the stats row (
GET /campaigns/:id/stats) — the per-status breakdown tells you whether contacts arepending(never gated),timezone_blocked(window),dialing(stuck claims), orcallback_scheduled(waiting on a future attempt moment).
When to escalate
Work the symptom section first. Escalate to support with all of the following — the more of the four, the faster the resolution:- Your tenant / organization ID — Settings → Organization, or
organizationIdfromGET /api/v1/me. - The campaign ID and its mode (
preview/progressive/predictive/agentless) and currentstatus. - The exact request and response — for a preview issue, the
GET /api/v1/dialer/next-call?campaign_id=<id>response body (or thePOST /dialbody withcampaign_idandcontact_id) and the full error, includingerror.codeand thedetailsobject. - The error code verbatim —
CLAIM_NOT_HELD,SOFTSWITCH_UNHEALTHY,WRONG_DIALER_MODE,NOT_FOUND, or the TCPA class from the window page. The code routes the escalation to the right layer.
See also
- Launch an outbound dialer campaign — modes, pacing knobs, the abandonment ceiling, and dispositions.
- Dialer API — the endpoint contracts this page references.
- Troubleshooting: resolve dialing-window blocked voice calls — the TCPA federal window, state overlays, and the quiet-hours preview endpoint.
- Dialer dispositions — the disposition → contact-status matrix; a contact stuck in an unexpected status usually traces here.
- Error codes — the canonical list of the codes above.