List contacts
GET /api/v1/campaign/{id}/contacts — the per-contact ledger, one row per contact with its full retry chain.
GET /api/v1/campaign/{campaign_id}/contactsReturn the campaign's contacts — one entry per person in the CSV, each rolled up from all of its dial attempts. When a contact is retried, every attempt lives in the same chain (root attempt + retry children), so this endpoint gives you the whole story: what happened on each try, the current rolled-up status, how many attempts were used, and when the next retry is due.
A contact is one root attempt (parent_queued_run_id IS NULL) plus its retry children, grouped by COALESCE(root_queued_run_id, id). The list paginates over contacts (roots), oldest-first (ascending id — roughly CSV order).
Query parameters
| Parameter | Type | Default | Notes |
|---|---|---|---|
page | int (≥ 1) | 1 | Page number. |
limit | int (1..200) | 50 | Contacts per page. |
status | string | — | Filter to contacts whose rolled-up status matches. One of the contact statuses. |
search | string (≤ 32) | — | Partial match on the contact's phone_number. |
How pagination interacts with the status filter
total counts all contacts matching search, ignoring status. The status filter is applied to the current page only, after rollup — so a filtered page may contain fewer than limit items, and total is not the count of matches for that status. Page through all contacts and filter client-side if you need an exact per-status count.
Contact status
The rolled-up status of a contact is the lifecycle state of its latest attempt (or completed if any attempt in the chain completed):
| Status | Meaning |
|---|---|
pending | Queued, not yet dialed. |
dialing | Claimed by a batch, dialing now. |
in_call | Dispatched; the call is in flight (no terminal outcome yet). |
retry_scheduled | A retry is queued for a future time. |
completed | The call completed (a human was reached and the call ran to its end). |
busy | Line was busy on the last attempt. |
no_answer | No answer on the last attempt. |
voicemail | Reached voicemail on the last attempt. |
failed | Terminal failure (or dispatch failed) with no successful connection. |
canceled | The call was canceled. |
Response
{
"items": [
{
"root_queued_run_id": 8801,
"phone_number": "+14155552671",
"context_variables": {
"phone_number": "+14155552671",
"first_name": "Sarah",
"account_id": "ACC-1234"
},
"status": "completed",
"latest_outcome": "completed",
"attempts_used": 2,
"next_retry_at": null,
"last_outcome_at": "2026-07-19T14:32:07Z",
"attempts": [
{
"queued_run_id": 8801,
"retry_count": 0,
"state": "processed",
"status": "no_answer",
"call_outcome": "no_answer",
"retry_reason": null,
"scheduled_for": null,
"outcome_at": "2026-07-19T14:10:41Z",
"processed_at": "2026-07-19T14:10:12Z"
},
{
"queued_run_id": 8845,
"retry_count": 1,
"state": "processed",
"status": "completed",
"call_outcome": "completed",
"retry_reason": "no_answer",
"scheduled_for": "2026-07-19T14:30:41Z",
"outcome_at": "2026-07-19T14:32:07Z",
"processed_at": "2026-07-19T14:30:55Z"
}
]
}
],
"total": 500,
"page": 1,
"limit": 50
}Contact fields
| Field | Type | Notes |
|---|---|---|
root_queued_run_id | int | Id of the chain's root attempt — a stable per-contact identifier. |
phone_number | string | null | The contact's number (from the CSV phone_number column). |
context_variables | object | The full CSV row for this contact (all columns, including template variables). |
status | string | Rolled-up contact status. |
latest_outcome | string | null | call_outcome of the latest attempt, or null if it has no terminal outcome yet. |
attempts_used | int | Number of dial attempts so far (max(retry_count) + 1). |
next_retry_at | string | null | When the next scheduled retry fires, if one is pending. |
last_outcome_at | string | null | When the latest attempt's outcome was recorded. |
attempts | array | Every attempt in the chain, oldest first (by retry_count). |
Attempt fields
| Field | Type | Notes |
|---|---|---|
queued_run_id | int | Id of this attempt row. |
retry_count | int | 0 for the root attempt; increments per retry. |
state | string | Ledger state: queued, processing, processed, failed. |
status | string | Per-attempt lifecycle status (same vocabulary as contact status). |
call_outcome | string | null | Terminal call outcome: completed, busy, no_answer, voicemail, failed, canceled; null until the call terminates. |
retry_reason | string | null | Why this retry was scheduled (busy / no_answer / voicemail). null on the root attempt. |
scheduled_for | string | null | For retries, when the attempt was scheduled to dial. |
outcome_at | string | null | When the terminal outcome landed. |
processed_at | string | null | When the attempt was dispatched. |
Examples
# First page of contacts
curl "https://dashboard.zoxa.ai/api/v1/campaign/7/contacts?page=1&limit=50" \
-H "X-API-Key: zsk_..."
# Only contacts currently waiting on a scheduled retry
curl "https://dashboard.zoxa.ai/api/v1/campaign/7/contacts?status=retry_scheduled" \
-H "X-API-Key: zsk_..."
# Find one contact by partial phone number
curl "https://dashboard.zoxa.ai/api/v1/campaign/7/contacts?search=5552671" \
-H "X-API-Key: zsk_..."Errors
| Status | detail | When |
|---|---|---|
404 | "Campaign not found" | Id not in your org. |
422 | array | status is not one of the contact statuses, limit > 200, or search exceeds 32 characters. |
Related
GET /campaign/{id}/insights— aggregate funnel, outcomes, retry effectivenessWS /campaign/{id}/events— live updates as contact outcomes land