Skip to main content
POST

Authorizations

X-API-Key
string
header
required

Authenticate every request by passing your API key in the X-API-Key header. Keys are scoped to your organization hierarchy.

Headers

Idempotency-Key
string

Optional idempotency key: a unique string of 16-200 printable ASCII characters (a UUID is recommended). Retrying the write with the same key within 24 hours returns the stored response of the first call with an X-Idempotent-Replayed: true response header instead of executing it again. Reusing a key with a different request body returns 422 (IDEMPOTENCY_MISMATCH); a duplicate sent while the first call is still running returns 409 with a Retry-After header. Keys are scoped per endpoint and organization.

Required string length: 16 - 200

Body

application/json
source_url
string<uri>
required

HTTPS URL of the CSV, such as a presigned storage URL. At most 50 MB; text/csv, application/csv, or text/plain. Private and link-local addresses are refused, on every redirect.

list_name
string
required

Name for the list. Names do not have to be unique. For do_not_contact, the name of the opt-out list the phone numbers go to.

Required string length: 1 - 255
organization_id
string<uuid>

Target organization for the import. Defaults to the organization that owns the API key. Send it to target a sub-organization under an agency-tier parent; the API key is always limited to its own organization tree (otherwise the request returns 403 ORG_ACCESS_DENIED).

brand_id
string<uuid>

Optional. When provided, the list is scoped to this brand and the brand's organization is used, so a brand in an accessible sub-organization works without organization_id. A brand that does not belong to an organization your API key can access returns 404 BRAND_NOT_FOUND. If both are sent and the brand belongs to a different accessible organization than organization_id, the request returns 400 BRAND_ORG_MISMATCH. Ignored for the emails of an import: an email list is organization-wide or scoped to one sending domain (email_domain_id).

purpose
enum<string>
default:audience

audience: a list you send to. do_not_contact: phone numbers become an opt-out list and emails become unsubscribes, both applied automatically to every send.

Available options:
audience,
do_not_contact
phone_column
string

The CSV header holding the phone number. Send this, email_column, or both. A header that is not in the file returns 400 PHONE_COLUMN_NOT_FOUND with details.headers.

Minimum string length: 1
email_column
string

The CSV header holding the email address. Must differ from phone_column. A header that is not in the file returns 400 VALIDATION_ERROR with details: { field: "email_column", headers }. Requires email to be enabled for the organization.

Minimum string length: 1
merge_tags
object

Maps CSV columns to contact merge fields. Unknown keys are rejected with a 400. When omitted, or when it maps nothing, every non-phone column gets a merge tag automatically: a header that matches a standard field (first name, last name, address, address line 2, city, state, zip, including common aliases) gets the standard tag, and any other column gets a tag named after its header in lowercase with underscores (for example Donor Tier becomes {donor_tier}). When supplied, the phone side maps only the columns named; the email side maps the named columns and every remaining column by its default name. With an email_column, custom field names must match ^[a-z][a-z0-9_]{0,63}$ (lowercase, starting with a letter, at most 64 characters).

Optional. Where the email addresses came from. Allowed only when purpose is audience and email_column is set; sent with any other combination it returns 400. Omit it and no consent record is stored.

email_domain_id
string<uuid> | null

Scopes the emails to one sending domain. Omit or send null for an organization-wide list any email campaign can use. An unknown domain returns 404 NOT_FOUND.

acquired
boolean

Optional, default false. true marks the email addresses as purchased or rented. Acquired lists are not blocked from sending, but they are sent more gradually, warming up on half steps.

Response

Import accepted and running as background jobs. Poll GET /contact-lists/{list_id} for both: import.status for the phone numbers and email.status for the emails, each until ready or failed. A do-not-contact import has no list_id.

success
boolean
required
data
object
required