Contact Lists API
A contact list is a static audience you curate explicitly: you create the list, then add each member yourself, one at a time or in bulk. Membership changes only when you change it. That is the difference from segments, which are rule-defined: a segment’s members are recomputed from criteria (lifecycle_stage:active, tags:vip, …) and shift as contacts change. Use a list when a human decides who is on it (a field-ops call sheet, a VIP list for the launch message); use a segment when a rule decides. The two models side by side are walked through in List vs segment membership models.
Lists live under /api/v1/contacts/lists, with membership as the /members sub-resource and a per-contact reverse read at /api/v1/contacts/{id}/lists. The endpoint-level reference for every operation on the resource (update, delete, duplicate, stats) lives under Contacts endpoints.
Base path: /api/v1/contacts
Authentication: An organization API key (sent as X-API-Key or Authorization: Bearer dv_live_sk_…). The CSV export additionally requires an owner, admin, or developer role.
Using the SDKs
This page’s endpoints have no typed helper yet, so each language reaches them through the same generic escape hatch, which keeps auth, retries, and the response envelope identical to the typed resources. The samples below run the same calls as the curl further down the page.- Node.js (
@devotel-orbit/node) —orbit.request<T>(method, path, body?) - Python (
orbit_sdk) —client.request(method, path, json_body={...}) - Go (
orbit-go) —client.Request(ctx, method, path, query, body, out, idempotencyKey) - Ruby (
orbit_sdk) —client.request(method, path, json_body: {...}) - PHP (Devotel\Orbit) —
$client->request(method: ..., path: ..., jsonBody: [...])
- Node.js
- Python
- Go
- Ruby
- PHP
Create a list, then add a member by phone number. The escape hatch returns the
unwrapped
data object, so type it to the shape this endpoint returns (see
“Response shapes” below):Worked use case: a field-ops list, two members, one CSV
The run-through that ties the three sub-resources together: create a phone-only list, patch two members onto it, then pull the whole list back out as a CSV.1. Create the list
name is required (1–255 characters); description is optional (up to 1000).
2. Patch two members onto it
Add each member withidentifier, a free-form phone, email, or contact id that
the API resolves to the matching contact. A phone in national format and a bare
email both work; an unresolvable identifier answers 404 with
CONTACT_NOT_FOUND_FOR_IDENTIFIER so you can tell “no such contact” apart from a
bad request.
already_member: true and writes
nothing, so a retry loop never needs special-casing. When you already hold
canonical contact ids, POST /lists/{id}/members/bulk accepts up to 1,000
contact_ids in one call and reports added plus any
failed_contact_ids.
3. Read the members back
GET /api/v1/contacts/lists/{id}/members pages the membership cursor-style; the
same ?q= filter the dashboard’s member box uses narrows it server-side, either
as free text (a name fragment, phone, or email) or as the Stripe-style DSL
(email:*@example.com).
meta.pagination.cursor as opaque, keep the same filters across pages, and
stop when has_more is false. The reverse read,
GET /api/v1/contacts/{id}/lists, returns the lists one contact belongs to.
4. Export the list as CSV
POST /api/v1/contacts/export with a list_id filter streams the list back as a
downloadable CSV, every custom-field value included. Pass ?format=xlsx for a
spreadsheet instead.
POST /contacts/bulk/export instead. Each export is audit-logged, so a
compliance review can see who pulled which list and when.
Response shapes
Contact list row
Member row
Writes answer a smaller body:
{ list_id, contact_id, already_member } under
data, with 201 for a new membership and 200 for the idempotent re-add.
Every response on this page follows the same envelope: data plus a meta block
carrying request_id, timestamp, and, on paged reads, pagination. Errors use
the { error, meta } envelope with codes such as
CONTACT_NOT_FOUND_FOR_IDENTIFIER (404) and VALIDATION_ERROR (422).
See also
- Contacts endpoints — the full operation reference for this resource, plus create/read/search on the underlying contact records
- List vs segment membership models — when to curate a list, when to define a segment
- Segments API — rule-defined audiences that recompute as contacts change
- Messaging — send to the audience you built here
- Contact lists for static targeting — the dashboard-side guide to the same workflow