Skip to main content

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: [...])
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 with identifier, 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.
A freshly written membership answers 201. Re-adding a contact that is already on the list is idempotent: it answers 200 with 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).
Treat 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.
One pull carries up to 10,000 rows; for a larger audience run the background job at 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