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

# Contact Lists

> Upload campaign contacts via CSV with consent fields and custom variables

The **Contacts** tab holds the campaign's target list. Upload a CSV, and every extra column you define becomes a `{{variable}}` your AI agent can use in its greeting, guidelines, and actions.

## CSV Format

Upload a `.csv` file by drag-and-drop or click-to-browse. A **Download template** button generates a correctly formatted starter file.

### Required columns

| Column                    | Format                                                                        | Purpose                                                              |
| ------------------------- | ----------------------------------------------------------------------------- | -------------------------------------------------------------------- |
| `phone`                   | E.164 with country code (e.g. `+15551234567`)                                 | The number to dial                                                   |
| `zipcode`                 | Postal code                                                                   | Resolves the contact's local timezone for legal calling-hours checks |
| `has_pewc`                | `true`/`false` (also accepts yes/no, y/n, 1/0)                                | Whether you hold Prior Express Written Consent for this contact      |
| `pewc_date`               | `YYYY-MM-DD`, `M/D/YYYY`, `MM/DD/YYYY`, or written month (e.g. `Apr 19 2026`) | When PEWC was captured — required whenever `has_pewc` is true        |
| `is_voice_call_opted_out` | `true`/`false`                                                                | If true, the contact is never dialed, regardless of PEWC             |

Any additional column becomes a **custom field**, provided you have defined it first (see below).

<Warning>
  Format the `phone` column as **Text** in Excel or Google Sheets before saving. The uploader detects and rejects the two classic spreadsheet corruptions — dashed numbers evaluated as arithmetic (`+1-206-555-0109` → `-869`) and long numbers truncated to scientific notation — with a row-level error explaining the fix.
</Warning>

### Phone number normalization

Numbers are validated and normalized to E.164 on upload. Accepted inputs include full E.164, digits-only international numbers (a `+` is added), 7–10 digit US national numbers, and dashed or spaced formats. Invalid numbers are reported per row and skipped — they never fail the whole upload.

## Limits

| Constraint                             | Limit                                                  |
| -------------------------------------- | ------------------------------------------------------ |
| Contacts per CSV upload                | **100,000** (split larger lists into multiple uploads) |
| Custom fields per campaign             | **50** keys                                            |
| Custom field data per contact          | **16 KB**                                              |
| Custom field value length at call time | **500 characters** (longer values are truncated)       |
| Row errors displayed                   | First **200** (all are counted)                        |

## Custom Fields → Agent Variables

Define custom fields in the **Custom contact fields** panel before uploading columns that use them:

1. Type a field name (e.g. `first_name`) and press Enter. Keys are normalized to lowercase snake\_case.
2. Include a matching column in your CSV — its values are stored per contact.
3. In the agent's flows and prompts, the field appears as a **Campaign** variable grouped under the campaign's name. `phone` and `zipcode` are always available as built-in campaign variables.

At call time, the dialed contact's values are injected into the agent session, so `{{first_name}}` in the greeting speaks the right name for every call.

<Warning>
  Removing a custom field breaks any flow node that references it as a Campaign variable. The Studio asks for confirmation before removing a field, and blocks removal while its variable is still referenced.
</Warning>

## Upload Behavior

Uploads are parsed in your browser, validated row by row, and sent in batches — keep the tab open until the progress bar completes. The results screen then breaks down exactly what happened:

* **Contacts added** — new rows inserted.
* **Contacts updated** — rows whose phone number already existed in the campaign. Re-uploading a contact **updates** its zipcode, consent fields, and custom fields rather than being skipped, so you can refresh compliance data by re-importing.
* **Duplicates (file)** — the same phone appeared more than once in the file; the last row wins.
* **Invalid** — rows that failed validation, listed with row number, phone, and reason.

<Note>
  Contacts with `is_voice_call_opted_out = true` are registered on your internal Do-Not-Call list. If your account has no SAN configured yet, the upload still succeeds and shows a warning — those contacts are registered automatically once you save a SAN. See [Compliance & DNC](/campaigns/compliance-and-dnc).
</Note>

## Managing the List

The contacts table shows Phone, Zipcode, the three consent columns (with explanatory tooltips), and up to six custom columns, paginated 50 per page.

* **Add more** — upload another CSV; existing contacts are updated, new ones added.
* **Clear all** — remove every contact (inline confirmation required). Only available while the campaign is editable.

## Best Practices

* **One source of truth.** Export from your CRM right before launch so consent flags are current — re-importing refreshes existing contacts.
* **Keep custom fields lean.** Only include columns the agent actually needs; every field is injected into the conversation context.
* **Test with real data shapes.** Place a [test call](/campaigns/campaign-setup#test-calls) with sample custom-field values to confirm variables resolve as expected.
* **Mind consent dates.** `pewc_date` is required whenever `has_pewc` is true — rows missing it are rejected at upload and blocked from dialing.

## Where to go next

<Columns cols={2}>
  <Card title="Scheduling" icon="calendar" href="/campaigns/scheduling">
    Configure the calling windows before launch.
  </Card>

  <Card title="Compliance & DNC" icon="shield-check" href="/campaigns/compliance-and-dnc">
    How consent fields and DNC scrubbing gate each dial.
  </Card>
</Columns>
