Skip to main content

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 at pending forever. Progressive and predictive campaigns keep dialing as long as the scheduler sees a ready agent and eligible contacts.
  • A 204 from next-call is 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:
  1. The campaign was never activated. A campaign created through POST /api/v1/dialer/campaigns starts in draft and dials nothing until you PATCH it to active. 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 then PATCHed to completed or paused does return 404 to the next poll.
  2. The campaign auto-aborted. The abandonment ceiling flipped it to aborted — that status is terminal, and next-call treats an aborted campaign the same as a missing one. See Abandonment abort.
  3. 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-active status, the status is the answer.
The fix is the status, every time. 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

A 200 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:
  1. Is the list uploaded? A campaign with no POST /campaigns/:id/lists upload claims nothing. The upload is a separate step from creating the campaign.
  2. Are contacts still pending? GET /api/v1/dialer/campaigns/:id/stats returns the per-status breakdown. If everything is connected, failed, dnc, or callback_scheduled and nothing is pending, the campaign is done — the empty claim is the correct state.
  3. Quiet hours. A contact whose recipient-local hour falls outside the permitted window is deferred with timezone_blocked rather 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.
  4. 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. If contact: null persists across several minutes and the stats show rows stuck in dialing, 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-call carries preview_decision_seconds and decision_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.
The fix is the one in the error message: re-fetch via 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

A 503 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 in amd_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_enabled is true, then the standard default. If the AMD-flagged campaign has no AMD application resolvable, origination fails closed rather than dialing blind; check amd_enabled on 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_ratio per ready agent. When answered calls find no ready agent, they abandon — and answered-then-abandoned calls drive the same abandonment metric the ceiling enforces. Drop pacing_ratio toward 1.0 and re-check the campaign’s ready agent count against actual staffed agents.
The corrective knobs are all campaign-level: 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:
  1. Read the campaign’s GET /campaigns/:id/stats — the abandon_rate_30m value shows whether this was a momentary spike or a sustained over-dial.
  2. Fix the pacing first: lower pacing_ratio, verify ready agents actually exist while the campaign runs.
  3. 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 is active, the list is uploaded, next-call returns contact: null, and predictive/progressive campaigns dial nothing. Work these in order:
  1. 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.
  2. Is every remaining contact deferred? US-only lists polled outside the federal 8 AM–9 PM recipient-local window all sit in timezone_blocked and 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.
  3. 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.
  4. Check the stats row (GET /campaigns/:id/stats) — the per-status breakdown tells you whether contacts are pending (never gated), timezone_blocked (window), dialing (stuck claims), or callback_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:
  1. Your tenant / organization ID — Settings → Organization, or organizationId from GET /api/v1/me.
  2. The campaign ID and its mode (preview / progressive / predictive / agentless) and current status.
  3. The exact request and response — for a preview issue, the GET /api/v1/dialer/next-call?campaign_id=<id> response body (or the POST /dial body with campaign_id and contact_id) and the full error, including error.code and the details object.
  4. The error code verbatimCLAIM_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.
For anything compliance-flavored — dialing-window blocks, DNC routing, recording-consent rejects — keep this page and escalate with the same four items; the platform will not loosen a federal hard block, but the gates that fail open are yours to reconfigure.

See also