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

# Import or migrate contacts: pick the right surface, in the right order

> Decide between the CSV import wizard, async import jobs, CDP profile ingest, and the platform-migration connectors — then run your first contact import end to end: shape the file, dry-run, import, map consent, dedupe, and verify the reachable audience.

# Import or migrate contacts

Getting contacts into Orbit is the first real task of onboarding, and four different surfaces can do it. This guide is the decision walkthrough: it tells you which surface to pick for which source, then walks the first-run workflow once — file shape, preview, import, consent mapping, dedupe, verify — so the links it hands off to are ordered stops, not a pile of options.

## 1. Pick the surface: decision table

Four surfaces move contact-shaped data into Orbit. Answer two questions — where does the data live, and what exactly does a row carry? — and the table resolves to one answer.

| Source is…                                                            | What a row carries                          | Use                                                                            | Wrong-shaped picks                                                       |
| --------------------------------------------------------------------- | ------------------------------------------- | ------------------------------------------------------------------------------ | ------------------------------------------------------------------------ |
| A file you shaped (CRM export, spreadsheet)                           | contact fields (phone / email / attributes) | **CSV import wizard** — **Audience → Import**                                  | A migration connector cannot read your file                              |
| The same file, automated via API                                      | contact fields                              | **Async import jobs** — `POST /contacts/imports` + `GET /contacts/import-jobs` | `POST /contacts/bulk` caps at 10,000 rows                                |
| An event/profile feed (Segment-style SDK, warehouse, CDP batch files) | `userId` / traits / event properties        | **CDP profile ingest** — see step 6                                            | A plain contact import writes one address per row with no identity graph |
| Twilio, Telnyx, Klaviyo, MessageBird, or Front                        | whatever the connector exposes              | **Platform migration wizard** — see step 7                                     | Re-shaping a provider export to CSV loses the configuration objects      |

The dashboard has two adjacent routes that both matter here, and the guide below uses them deliberately:

* **Audience → Import** — the CSV wizard itself (upload, map, preview, run). One wizard pass per file.
* **Audience → Imports** — the import-history page: every past job with status, per-job counters, the skipped-rows download, and a resume link into in-flight jobs. When you need "which import landed and what did it skip," this is the answer.

The one-click migration wizard and its operations page (**Settings → Migrations**) live separately — see [run a platform migration end-to-end](/guides/platform-migration-jobs).

## 2. Bulk CSV import to a contact list

The full endpoint-by-endpoint walkthrough is [import and reconcile contacts](/guides/import-contacts). The file-shape decisions are the load-bearing ones, and they are all settled *before* upload:

1. **One address per row.** Each row needs a `phone` or an `email`; rows with neither are skipped. Rows you do not fix at export become the skipped-rows CSV after the run.
2. **Phone format.** E.164 (`+14155552671`) is preferred. The server tolerates whitespace and punctuation, and PBX-extension suffixes (`x1234`). Rows it cannot parse are skipped rather than guessed.
3. **Opt-in columns.** Consent is a property of the *channel on the row*, not of the row itself — decide before import which file column maps to which consent signal. The mapping makes send-time suppression deterministic instead of retroactive (see step 4).
4. **Custom field headers.** A header that names a [custom field](/guides/custom-fields) key maps into that field. Define the field first: values are typed and validated on write, and a row that fails the validator is skipped (see step 5).

Run the file deliberately: idempotent re-import of the *same file* is safe — the merge strategy (`skip` = leave the existing record; `merge` = update fields from the row) is what decides whether re-import is a no-op or a refresh. Duplicates on phone anywhere (between the file and the tenant, or inside the file) are surfaced by the preview endpoint before anything is written, so a re-run never silently forks two contacts for one person.

## 3. The API path

Two endpoints implement the same job shape the dashboard wizard drives; the wizard and the API are not separate pipelines.

* `POST /api/v1/contacts/imports/preview` — dry-run: validates row shape, counts invalid rows, and reports duplicates (inside the file and against the existing tenant). Writes nothing.
* `POST /api/v1/contacts/imports` — enqueue the job: accepts up to 1,000,000 rows, returns `202` with a `job_id`. Poll `GET /api/v1/contacts/imports/{id}` until `status` leaves `pending` / `processing`; statuses are `pending → processing → {completed, failed, cancelled}`.
* `GET /api/v1/contacts/import-jobs/{id}/skipped.csv` — the row-error report: one row per skipped input row, with `row_index`, a human-readable `reason`, and the original columns.
* `POST /api/v1/contacts/imports/{id}/cancel` and `POST /api/v1/contacts/imports/{id}/rollback` — cancel stops an in-flight job; rollback hard-deletes the contacts the job *created*. The rollback window is 24 hours after the job finishes.

The row-shape and retry contract for these endpoints is [import and reconcile contacts](/guides/import-contacts); the endpoint-by-endpoint schema is the [Contacts API reference](/api-reference/endpoints/contacts). A `503` at enqueue means the queue is unavailable — fall back to the synchronous `POST /api/v1/contacts/bulk` (≤10,000 rows per request) or retry.

## 4. Opt-in and suppression on import

Importing an address is not consent, and an import job is not a suppression signal. Suppression and opt-out handling is enforced at **send time**, not at import time — so a row that lands cleanly still cannot receive a marketing send if it is opted out, and a row that imports with an opt-in marker still cannot bypass an existing opt-out.

Decide the opt-in mapping in the file shape (step 2), and treat the optics pair as a control:

* Rows whose opt-in column says "no" should land suppressed via an [opt-out list](/guides/opt-out-lists) before the first send — not merely tagged.
* Existing opted-out contacts must stay opted out across a re-import; choose `merge` on re-import only if the file's consent column actually carries new, better-granted consent.
* The import rollback cascade (step 3) removes consent records along with created contacts, so rolling back a bad import never leaves orphaned opt-in flags.

The consent and suppression model — and why enforced-at-send-time import is the safe default — lives in [consent and suppression](/concepts/consent-and-suppression-model).

## 5. Map custom fields without inventing headers

Custom-field mapping follows one rule that saves most import errors: the header is a field **key**, the field was **defined first**, and values are validated **on write**.

* **Naming.** Map the column header directly to the field key — no prefix, no Casing change. A header that does not match a defined key is either plain contact data (ignored on write) or a mistake that only shows up as skipped rows.
* **Type coercion.** CSV cells are strings; the validator coerces them into the field's declared type on write. `date` fields reject unparseable dates, `number` fields reject non-numeric text, and `boolean` expects the declared truthy set.
* **Common errors.** A header that collides with another mapped header (duplicate), a column that maps to a required field but is missing from the file (violates the required-field constant), and a row whose value fails the type check — each lands in the skipped-rows report with its own `reason` string.

Define the fields up front in [custom fields](/guides/custom-fields); the wizard's mapping step will not invent them.

## 6. CDP profile import

Reach for the CDP path when a row is more than one address — when it carries an identity (`userId`, an email and a phone together) plus traits and events, so identity resolution decides which existing profile it merges onto.

* **Identity merge on input.** The CDP ingest surfaces (`POST /api/v1/cdp/file-ingest` for CSV/JSON batch files, the HMAC SDK surface for streams) run each row through the same identity rules the [identity resolution model](/concepts/cdp-identity-resolution) defines: an incoming `userId` deterministically merges onto one contact; the survivorship policy decides which value wins when two profiles collide.
* **Re-import does not duplicate.** Because ingest keys on identity, re-importing the same file merges onto the profile it already created — it never forks a second identity. The identity rules and survivorship policy you author in the **Integrations → CDP** tabs are documented in [CDP data catalog and identity rules](/guides/cdp-data-catalog-and-identity-rules); preview the merge effects with simulation before any rule change (see that guide).
* **When not to take this path.** If all you have is a phone and a list of tags, the plain contact import is simpler — the CDP path pays for itself only when identity resolution is the point.

## 7. Migration jobs: moving off another CPaaS

When the source is one of the five supported platforms — Twilio, Telnyx, Klaviyo, MessageBird, Front — the migration wizard is the correct surface and a plain CSV import is the wrong one: the wizard validates the credential, lists the entities the source exposes (phone numbers, templates, contacts, configuration objects), and dry-runs before writing anything. Re-shaping a provider export into CSV loses the non-contact objects and adds a manual step.

* The operational lifecycle (connect → preview → run → reconcile, cancel, 24-hour rollback) is [run a platform migration end-to-end](/guides/platform-migration-jobs).
* The concept page explains the state machine, the per-source entity kinds, and credential handling — [import and migration lifecycle](/concepts/imports-migration-model).
* Monitor, cancel, and rollback outside the wizard from **Settings → Migrations** — see [migration jobs operations](/guides/migration-jobs-operations).
* The per-source concept mapping (what your Twilio numbers, Klaviyo lists, etc. become in Orbit) is [migrate from Twilio](/guides/migration-from-twilio) and [migrate from an enterprise CCaaS](/guides/migrate-from-enterprise-ccaas).

Use a plain import for this only when the source is a provider with no connector and you shaped the file yourself — and then decide it deliberately, not because the wizard looked like the wrong button.

## 8. Post-import: preview the net reachable audience

An imported audience is not a reachability number — suppressed, opted-out, and unreachable contacts count toward the imported total but not toward send-able recipients. Before the first campaign, dry-run the audience:

```bash theme={null}
curl -X POST "https://api.orbit.devotel.io/api/v1/campaigns/audience/preview" \
  -H "X-API-Key: $ORBIT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "audience": { "segment_id": "seg_xyz" }, "channel": "sms" }'
```

The preview returns the matching total plus the excluded cohorts — unreachable (missing address, opted out, suppressed) — with a sample of resolved recipients. A large suppressed cohort after an import almost always means the consent mapping (step 4) was not done in the file, or a prior opt-out list funnels the audience. Fix the mapping, re-import, and re-preview; do not launch until the net count is what you expect.

## 9. Troubleshooting

| Symptom                                     | Cause                                                                       | Fix                                                                             |
| ------------------------------------------- | --------------------------------------------------------------------------- | ------------------------------------------------------------------------------- |
| Rows skip with "Invalid phone number"       | Not E.164-parseable; extension or punctuation the server cannot normalize   | Normalize the column to E.164 at export; re-import just the fixed rows          |
| Rows skip on a mandatory custom field       | The header maps a field that does not exist, or a required field is missing | Define the field first, re-map, re-import the flagged rows                      |
| Duplicate rows collapse into one contact    | Same address within the file or against the tenant — the dedupe did its job | Check `in_csv_dup_count` in the preview; use `merge` or `skip` deliberately     |
| Import stuck in `queued` / `pending`        | Queue saturated or unavailable (`503` at enqueue)                           | Poll; fall back to `POST /contacts/bulk` under 10k rows if the queue stays down |
| Reachable count is far below imported count | Consent mapping skipped, or a suppression list wipes the cohort             | Import opt-in columns per step 4; preview again                                 |
| Roll back returns `409`                     | The 24-hour rollback window expired; the data is now ordinary contact data  | Delete instead: `POST /api/v1/contacts/bulk-delete`                             |

## See also

* [Import and reconcile contacts](/guides/import-contacts) — the CSV endpoint walkthrough this guide routes to
* [Run a platform migration end-to-end](/guides/platform-migration-jobs) — the connector path, chosen by the table above
* [Import and migration lifecycle](/concepts/imports-migration-model) — the concept that explains the migration state machine
* [Custom fields](/guides/custom-fields) — define import targets before mapping
* [Opt-out lists](/guides/opt-out-lists) — where imported opt-outs land
* [CDP data catalog and identity rules](/guides/cdp-data-catalog-and-identity-rules) — author the merge policy CDP ingest obeys
* [Campaign end-to-end](/guides/campaign-end-to-end) — launch once the preview count is clean
