> ## Documentation Index
> Fetch the complete documentation index at: https://docs.anyreach.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Contacts

> Upload and manage the audience for a campaign.

Contacts are the audience a campaign dials. Each contact carries one or more phone numbers, arbitrary structured data, and optional per-contact overrides that change how its call is placed. You add contacts by uploading a CSV in the dashboard or by calling the contacts API directly.

## What a contact holds

| Field                                                                          | Type                 | Description                                                                                                                                                                                                                 |
| ------------------------------------------------------------------------------ | -------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `phone_numbers`                                                                | string array (E.164) | Ordered list of numbers to dial. Order is dial priority: the first number is tried first, then the next on retry.                                                                                                           |
| `data`                                                                         | JSON object          | Arbitrary key/value data. Any CSV column you do not map to a structured field lands here, and the whole object is passed as `custom_metadata` on the call so your agent and prompts can reference it.                       |
| `external_id`                                                                  | string               | Optional identifier from your own system. Used as the upsert key and is unique per campaign.                                                                                                                                |
| `timezone`                                                                     | string               | IANA timezone (for example `America/New_York`) used for call-window scheduling.                                                                                                                                             |
| `scheduled_at`                                                                 | datetime             | ISO 8601 time before which the contact is not dialed.                                                                                                                                                                       |
| `agent_id`, `agent_config`, `agent_number`, `agent_version`, `initial_context` | overrides            | Per-contact overrides of the campaign's agent assignment, configuration, caller number, pinned version, and opening context. See [Configurations](/campaigns/configurations) for the campaign-level defaults these replace. |

<Note>
  Structured fields (`phone_numbers`, `external_id`, `timezone`, `scheduled_at`, and the agent overrides) live as top-level columns. Everything else you send stays inside `data`. Do not duplicate a structured field inside `data`.
</Note>

## Upload a CSV

In the campaign, open the contacts uploader and drag a CSV onto the drop zone (or click to browse). The uploader parses the file in the browser, auto-detects columns, and lets you adjust the mapping before sending.

<Steps>
  <Step title="Drop the file">
    Drag a `.csv` file onto the upload area. The file must be 10MB or smaller, and at most the first 1000 rows are read.
  </Step>

  <Step title="Confirm the mapping">
    The uploader auto-detects phone columns by header name (for example `phone`, `phone_number`, `mobile`, `cell`, `tel`) and maps **Contact ID**, **External ID**, **Timezone**, and **Scheduled At** when it recognizes their headers. Adjust any mapping that is wrong.
  </Step>

  <Step title="Review and upload">
    Mapped timezones, datetimes, and contact IDs are validated inline. Unmapped columns are kept in `data`. Submit to send the batch.
  </Step>
</Steps>

The CSV uploader maps only the structured fields above plus phone columns. Per-contact `agent_config` and `initial_context` overrides are API-only — set them through the contacts endpoint.

<Warning>
  The uploader maps a column named `id` (or `contact_id`, `anyreach_id`) to the Anyreach **Contact ID**, which must be a UUID and updates an existing contact in place. To match against your own keys instead, map your column to **External ID**.
</Warning>

## Add contacts via the API

```bash theme={null}
curl -X POST https://api.anyreach.ai/campaign/campaigns/{campaign_id}/contacts \
  -H "Authorization: Bearer <token>" \
  -H "X-Anyreach-Org: <organization_id>" \
  -H "Content-Type: application/json" \
  -d '{
    "contacts": [
      {
        "external_id": "crm-4821",
        "phone_numbers": ["+14155550101", "+14155550102"],
        "timezone": "America/Los_Angeles",
        "scheduled_at": "2026-07-01T17:00:00Z",
        "initial_context": "Renewal due next week.",
        "data": { "first_name": "Dana", "plan": "pro" }
      }
    ]
  }'
```

`POST /campaign/campaigns/{campaign_id}/contacts` upserts the batch. For each contact it matches an existing row by `id` first, then by `external_id` within the campaign, and updates it; otherwise it inserts a new contact.

| Limit                                        | Value |
| -------------------------------------------- | ----- |
| Contacts per API request                     | 1000  |
| Contacts inline on campaign create or update | 5000  |
| CSV file size                                | 10MB  |
| CSV rows read                                | 1000  |

Sending contacts inline when you create or update a campaign accepts up to 5000 in a single payload; the standalone contacts endpoint accepts up to 1000 per call.

### Phone normalization

Numbers are normalized to E.164 using the campaign configuration's `default_country_code`, so you can upload local-format numbers as long as they belong to that country. A number that cannot be parsed into a valid E.164 number fails the whole request with `422`; the response lists the first invalid numbers.

<Info>
  Adding contacts to a campaign whose status is `closed` returns `400`. Reopen or recreate the campaign before uploading.
</Info>

## Contact statuses

As a campaign runs, each contact moves through these statuses:

| Status        | Meaning                                                   |
| ------------- | --------------------------------------------------------- |
| `pending`     | Eligible to be dialed and waiting in the queue.           |
| `locked`      | Reserved by a worker and about to be dialed.              |
| `in_progress` | A call attempt is currently active.                       |
| `completed`   | The contact reached a terminal successful outcome.        |
| `exhausted`   | All retry attempts were used without success.             |
| `error`       | Processing failed in a way that stopped further attempts. |

Track these and per-contact attempt outcomes from [Results and analytics](/campaigns/results-and-analytics).

## Related

<CardGroup cols={2}>
  <Card title="Configurations" icon="sliders" href="/campaigns/configurations">
    Set the agent, caller number, country code, retries, and call window a campaign applies by default.
  </Card>

  <Card title="Results and analytics" icon="chart-line" href="/campaigns/results-and-analytics">
    Review attempts, outcomes, and assessment results per contact.
  </Card>
</CardGroup>
