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

# Run an outbound dialer campaign live: console widgets, pause/abort, callbacks

> What you see on the live dialer campaign console while a run is in flight — the answered-call and abandon-rate telemetry, the pause/abort lifecycle, per-contact dispositions, callback honoring, and the API endpoints that power them for scripted monitoring.

# Run an outbound dialer campaign live

You launched a dialer campaign with [Launch an outbound dialer campaign](/guides/outbound-dialer-campaign). This guide is what you run on during the live phase — the `/voice/dialer/[id]` console you land on when you click into an active campaign, what each widget measures, when to pause or abort, and how callbacks and DNC sync stay honored while calls are in flight. All endpoints used below are on the [Dialer API](/api-reference/dialer) surface.

## 1. Not to be confused with a campaign journey — the dialer "create" wizard

Orbit has two outbound-campaign surfaces that use the word "campaign." Choosing the wrong one is the most common onboarding mistake.

* A **campaign journey** ([Build, simulate, and launch a campaign journey](/guides/campaign-journey-builder)) is a bulk pusher for message-style content — SMS, email, voice broadcasts, and multi-step journeys. It originates a fixed wave of sends, then advances per-event. There is no agent-pacing math and no per-contact dialing — it's an event-driven batch flow (`type: "journey"`).
* The **dialer campaign** (`/voice/dialer`) is a live, agent-ready pacing surface. It claims contacts one tick at a time, answers each leg with a person or a voice agent, and re-claims on disposition. Its wizard collects pacing, abandon-ceiling, caller-ID, and script fields because those knobs exist only on a live pacing engine.

Open `/voice/dialer` in the dashboard; the **New campaign** button opens the dialer CreateCampaignDialog. A journey lives under `/campaigns`; the two are never cross-linked. If you intended a bulk message-style send, cancel here — the dialer wizard's pacing knobs (`pacing_ratio`, `max_abandon_rate`) only make sense for a live pacing engine.

## 2. Live-run console layout — what each widget meters

The campaign detail page at `/voice/dialer/[id]` (reached from the dialer list or created from the dialog) is a wallboard for the run. The page polls live stats every 10 seconds while the campaign is `active` or `paused`; polling stops when the campaign is `completed` or `aborted`.

The header carries the campaign name, its status badge (draft / active / paused / completed / aborted), and the mode badge (Preview / Progressive / Predictive / Agentless). The action buttons below them change with lifecycle — see "Pause / Abort" below.

### Stats strip — per-contact status

The stats strip reads the campaign's contact-status breakdown as of the last stats fetch (visible right after `GET /dialer/campaigns/:id/stats`). The tiles that matter:

* **Total / Pending / Dialing / Connected / Failed / DNC blocked / Timezone blocked / Exhausted / Callback scheduled / Wrong number / Archived** — per-contact status buckets, with a **Total** tile showing a `+` suffix when the scan is bounded (`contacts_count_capped=true`), so the count is a floor, not a runaway exact total.
* **Voicemail (24h) / No answer (24h)** — short-window outcome counters, read from \`last\_24h.

The donut at the bottom of the contact-progress card is the sum of `dialing + connected + failed + dnc + exhausted` against `Total` — the "dialed" share. `Pending` and `DNC / TCPA blocked` are rendered beneath it with their own counts.

### Call outcomes — last 24 hours

The "Call outcomes" table aggregates `dialer_call_attempts` by outcome over the trailing 24 hours: connected, no\_answer, busy, voicemail, AMD detected, and abandoned, with the average handle time (`avg_handle_time_seconds`) as a footer when set. An empty table reads "No calls yet."

### Post-call AI sentiment + per-attempt score

Two read-outs sit below the outcomes table — `GET /dialer/campaigns/:id/sentiment` (four-way sentiment tally; positive / neutral / negative / pending) and `GET /dialer/campaigns/:id/attempts/:attemptId/outcome-scoring` (single-attempt: sentiment, 0–100 composite score, warm/engaged/neutral/cold/voicemail label, next best action, agent disposition). `analyzed:false` is the normal zero state — the outcome-intelligence analyzer is tenant opt-in and async.

### Caller-ID coverage (local presence)

`GET /dialer/campaigns/:id/callerId_coverage` aggregates the lead phone numbers against the campaign's caller-ID pool and the `caller_id_strategy` (`npa_match` or fixed) and reports the under-covered destination NPAs (area codes) the pool should add to improve local-presence answer rates. This is a read-only aggregate; no calls originate from it.

## 3. Pause, resume, and abort — the lifecycle during a run

The action bar shows the lifecycle controls appropriate to the current status. Every transition goes through `PATCH /dialer/campaigns/:id` (`dialer:write` scope).

* **Activate** — visible only on drafts with ≥1 contact. Sends `{"status":"active"}`. The back-end flips `draft → active`; the pacing scheduler starts claiming.
* **Pause** (active) — `{"status":"paused"}`. Pacing stops claiming new contacts; in-flight legs complete.
* **Resume** (paused) — `{"status":"active"}` when correcting a trip.
* **Abort** — `{"status":"aborted"}`. The abort is **terminal**: after it the campaign cannot be reactivated under FCC Telemarketing Sales Rule (TSR), 47 C.F.R. § 64.1200(a)(7), and the page replaces its action buttons with a banner and a "Create new campaign" link. Abort on drafts is a safe discard (no calls in flight).
* **Delete campaign** (overflow menu) — a soft delete. Pending contacts archive; connected/failed/DNC terminal rows stay for TCPA-dispute defense. If any contact is still in flight (`409 DIALER_CAMPAIGN_IN_FLIGHT`) the dialog shows the count and asks you to pause first.
* **Dispatch batch** (voice-agent campaigns only) — fires one batch through a bound voice agent when `dispatch_mode='voice_agent'`; see "Voice agent vs human dispatch" below.

A 409 `CAMPAIGN_ABORTED` from a re-activate attempt means you aborted earlier; create a new campaign instead — the status is terminal by design. The mutated status is validated by the back-end and the UI renders an inline error on the detail page so the click doesn't silently land.

## 4. Dispositions and wrap-up codes during the live run

Every answered-or-completed attempt must land a disposition — otherwise the contact stays `pending` and the return-eligibility guard reclaims it. Agents post to `POST /dialer/campaigns/:id/dispositions` (scope `dialer:write`). The canonical route matrix on [dialer dispositions](/voice/dialer-dispositions) maps the string to a contact status:

| Disposition string                                                             | Contact status               | Side effects                                                  |
| ------------------------------------------------------------------------------ | ---------------------------- | ------------------------------------------------------------- |
| `callback_later`, `callback`                                                   | `callback_scheduled`         | Requires `callback_scheduled_at` (ISO-8601, ≤ 90 days ahead). |
| `sale`, `no_sale`, `voicemail`, `not_interested`, `interested`, `wrong_number` | `connected` / `wrong_number` | —                                                             |
| `dnc`, `do_not_call`                                                           | `dnc`                        | Inserts the phone onto the tenant DNC list.                   |
| `busy`, `no_answer`, `failed`, `fax_machine_detected`, `operator_intercept`    | `failed`                     | —                                                             |
| anything else (freeform)                                                       | `failed`                     | Deliberate: unmatched strings never inflate the connect rate. |

`z.string()` on the disposition caps input at 100 characters. For an inbound queue that routes a human agent through wrap-up, see [wrap-up codes](/voice/wrap-up-codes).

## 5. Callback honoring and DNC sync

A callback disposition is not just a note — `callback_scheduled_at` writes `next_attempt_at` on the contact, and the pacing claim re-picks the contact at that ISO moment (clamped by pacing). The **Callback scheduled** tile on the stats strip counts these rows so you can see pending callbacks before they fire.

A `dnc` / `do_not_call` disposition inserts the phone onto the tenant do-not-call list (`dnc_list`) with source `dialer_disposition`. Every future pacing tick across every campaign blocks at the DNC gate — first-write wins, so a phone already suppressed by SMS STOP or carrier feed cannot be overwritten on conflict. The audit log fires `dialer.dnc_added` alongside `dialer.disposition_submitted`.

## 6. Live-console API hooks — script the same views

Everything the console shows is a standard `GET /api/v1/dialer/...` read with scope `dialer:read`; writes require `dialer:write`. All responses are wrapped in the standard envelope (`{ data, meta, request }` — errors return `{ error: { code, message, details? } }`).

### Live batch metering

```bash theme={null}
export ORBIT_API_KEY="dv_live_sk_your_key_here"
export CAMPAIGN_ID="campaign_123"

# Live stats row (same row the detail page polls every 10s)
curl -s "https://api.orbit.devotel.io/api/v1/dialer/campaigns/$CAMPAIGN_ID/stats" \
  -H "X-API-Key: $ORBIT_API_KEY" | jq
```

The response carries the same fields the console renders; the full schema is on [Dialer API](/api-reference/dialer):

```json theme={null}
{
  "data": {
    "id": "campaign_123",
    "name": "Sales outbound — August",
    "mode": "predictive",
    "status": "active",
    "pacing_ratio": 1.2,
    "abandon_rate_30d": 0.014,
    "max_abandon_rate": 0.02,
    "contacts_pending": 42,
    "contacts_dialing": 3,
    "contacts_connected": 18,
    "contacts_failed": 7,
    "contacts_dnc": 2,
    "contacts_timezone_blocked": 1,
    "contacts_exhausted": 4,
    "contacts_archived": 0,
    "contacts_callback_scheduled": 1,
    "contacts_wrong_number": 1,
    "contacts_total": 58,
    "contacts_count_capped": false,
    "last_24h": {
      "calls_connected": 18,
      "calls_no_answer": 7,
      "calls_busy": 2,
      "calls_voicemail": 3,
      "calls_amd_detected": 1,
      "calls_abandoned": 0,
      "avg_handle_time_seconds": 93,
      "total_attempts": 31
    },
    "last_30m": {
      "abandoned_count": 0,
      "connected_count": 5,
      "abandon_rate_30m": 0
    }
  }
}
```

Polar a dashboard of these — the same widget the console polls. `last_30m.abandon_rate_30m` is the rolling 30-minute abandonment rate the pacing engine enforces against `max_abandon_rate`; it trips to `aborted` on the live side.

### Lifecycle via PATCH

```bash theme={null}
curl -s -X PATCH "https://api.orbit.devotel.io/api/v1/dialer/campaigns/$CAMPAIGN_ID" \
  -H "X-API-Key: $ORBIT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"status": "paused"}' | jq
```

Accepts `draft|active|paused|aborted` transitions; only `aborted → *` is rejected with `409 CAMPAIGN_ABORTED`.

### Post a disposition from a script

```bash theme={null}
curl -s -X POST "https://api.orbit.devotel.io/api/v1/dialer/campaigns/$CAMPAIGN_ID/dispositions" \
  -H "X-API-Key: $ORBIT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "attempt_id": "dialerAttempt_9f2c",
    "disposition": "callback",
    "callback_scheduled_at": "2026-09-25T14:00:00Z",
    "notes": "customer wanted weekly check-in"
  }' | jq
```

The response echoes the routed status, and DNC callbacks get `callback_scheduled_at` stamped onto the contact's `next_attempt_at`.

### Voice agent vs human dispatch

A campaign routes answered legs to either a human agent (`dispatch_mode='human'`, the default) or a bound AI voice agent (`dispatch_mode='voice_agent'`). The batch-dispatch endpoint only exists for the latter; for human-dispatch campaigns the console surfaces a `409 WRONG_DISPATCH_MODE` and hides the Dispatch-batch button.

```bash theme={null}
# Fire one batch of AI-voice calls; max_legs clamped to the campaign's
# concurrency_limit (default 10, clamped 1–200). 202 = all legs dispatched,
# 207 = partial, 204 = no eligible contacts this tick.
curl -s -X POST "https://api.orbit.devotel.io/api/v1/dialer/campaigns/$CAMPAIGN_ID/batch-dispatch" \
  -H "X-API-Key: $ORBIT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"max_legs": 5}' | jq
```

A `503 SOFTSWITCH_UNHEALTHY` trip resets claimed rows back to `pending` before any leg is attempted, and a per-batch TCPA audit row lands in `public.audit_logs` as `dialer.batch_dispatch`.

The Preview-mode agent workspace (`Open agent view` on the detail page) polls `GET /dialer/next-call` for the next pending contact and directly fires `POST /dialer/dial`; progressive and predictive campaigns auto-dial server-side and return 204 from next-call.

## See also

* [Launch an outbound dialer campaign](/guides/outbound-dialer-campaign) — setup and the compliance gates
* [Dialer dispositions](/voice/dialer-dispositions) — the full string → status matrix
* [Wrap-up codes](/voice/wrap-up-codes) — per-queue code catalogs for inbound queues
* [Recording consent](/compliance/recording-consent) — required before `recording_enabled: true` goes live
* [Dialer pacing model](/concepts/dialer-pacing-model) — how the scheduler paces claims
