zoxaAI
Homepage
API ReferenceCampaigns

Campaign insights

GET /api/v1/campaign/{id}/insights — funnel, outcome breakdown, retry effectiveness, cost & duration, timeline, and pacing in one request.

GET /api/v1/campaign/{campaign_id}/insights

Deep campaign analytics in a single round trip. Everything is computed live from the ledger (contact-level funnel + per-attempt outcomes) and the campaign's call records (cost, duration, timeline) with indexed GROUP BYs — there are no cached aggregates, so the numbers are always current. One request returns seven sections: funnel, outcomes, retries, cost, duration, timeline, and pacing.

Cost and duration

cost and duration are aggregated over the campaign's completed calls.

Response

{
  "funnel":   { "contacts": 500, "dialed": 480, "connected": 310, "completed": 268 },
  "outcomes": {
    "by_outcome":  { "completed": 268, "voicemail": 42, "no_answer": 90, "busy": 55, "failed": 25 },
    "pending":         12,
    "retry_scheduled": 8,
    "dialing":         3,
    "in_flight":       2,
    "dispatch_failed": 4
  },
  "retries": {
    "success_by_attempt": { "1": 220, "2": 40, "3": 8 },
    "conversion_by_reason": {
      "no_answer": { "total": 90, "completed": 34, "rate": 0.377 },
      "busy":      { "total": 55, "completed": 14, "rate": 0.254 }
    },
    "pending": [
      {
        "queued_run_id": 9021,
        "phone_number":  "+14155552698",
        "retry_reason":  "busy",
        "scheduled_for": "2026-07-19T15:05:00Z"
      }
    ]
  },
  "cost": {
    "billed_calls":      310,
    "total_usd":         12.74,
    "avg_usd":           0.0411,
    "per_connected_usd": 0.0411
  },
  "duration": {
    "total_seconds": 18620.0,
    "avg_seconds":   60.06,
    "distribution": [
      { "bucket": "0-15s", "count": 40 },
      { "bucket": "15-30s", "count": 61 },
      { "bucket": "30-60s", "count": 92 },
      { "bucket": "1-2m", "count": 78 },
      { "bucket": "2-5m", "count": 34 },
      { "bucket": "5m+", "count": 5 }
    ]
  },
  "timeline": {
    "bucket": "minute",
    "buckets": [
      { "bucket_start": "2026-07-19T14:00:00Z", "dialed": 22, "connected": 14 },
      { "bucket_start": "2026-07-19T14:01:00Z", "dialed": 25, "connected": 17 }
    ]
  },
  "pacing": { "rate_limit_per_second": 5, "max_concurrency": 5, "in_flight": 2 }
}

Sections

funnel

Contact-level funnel (each stage counts contacts, not attempts, so a contact retried three times counts once).

FieldMeaning
contactsTotal contacts in the campaign.
dialedContacts with at least one dispatched attempt.
connectedContacts that reached the callee at least once (completed or voicemail).
completedContacts with a completed call (ran to its natural end).

outcomes

by_outcome is a per-attempt count of terminal call outcomes (keys are only those that occurred). The remaining fields are live, non-terminal attempt counts:

FieldMeaning
pendingQueued, not yet dialed (no scheduled_for).
retry_scheduledQueued for a future retry.
dialingClaimed by a batch, dialing now.
in_flightDispatched; awaiting a terminal outcome.
dispatch_failedAttempt failed at dispatch (never reached the carrier — provider rejected, pool exhausted, etc.).

retries

Retry effectiveness:

FieldMeaning
success_by_attemptCompleted calls keyed by attempt number ("1" = first try, "2" = first retry, …).
conversion_by_reasonFor each retry reason, how many retried attempts reached a terminal outcome (total), how many completed, and the rate (completed / total, or null when total is 0).
pendingUp to 50 upcoming (future-scheduled) retries: queued_run_id, phone_number, retry_reason, scheduled_for. Future-only — already-fired retries are not listed here.

cost (agent campaigns only, else null)

Aggregated over completed calls (status = 'completed'):

FieldMeaning
billed_callsNumber of completed (billed) calls.
total_usdTotal cost across those calls.
avg_usdAverage cost per billed call (null if none).
per_connected_usdtotal_usd divided by the funnel's connected-contact count (null if none connected).

duration

FieldMeaning
total_secondsSum of call durations.
avg_secondsAverage call duration (null if none).
distributionHistogram over fixed buckets: 0-15s, 15-30s, 30-60s, 1-2m, 2-5m, 5m+. Always all six buckets, count 0 where empty.

timeline

Calls over time. bucket auto-scales to the campaign's wall-clock span: minute (under 3 hours), hour (under 3 days), or day (longer). Each entry carries bucket_start, dialed, and connected. A campaign that never started returns { "bucket": null, "buckets": [] } (an empty bucket list).

pacing

FieldMeaning
rate_limit_per_secondThe campaign's dial-rate cap.
max_concurrencyConfigured max concurrent calls (null if unset).
in_flightCalls currently dispatched and awaiting an outcome (same value as outcomes.in_flight).

Empty campaigns are safe

A campaign with no contacts (or one that hasn't started) returns zeros, empty lists, and nulls — never an error. There is no ETA field; the dashboard derives ETA client-side from the timeline and funnel.

Examples

curl https://dashboard.zoxa.ai/api/v1/campaign/7/insights \
  -H "X-API-Key: zsk_..."

Errors

StatusdetailWhen
404"Campaign not found"Id not in your org.

On this page