Contacts & lists

A contact is a person you may message and the record of why you may. A list is a group of them, and it is what a campaign sends to.

MethodPathScope
GET/v1/contactscontacts:read
POST/v1/contactscontacts:write
POST/v1/contacts/importcontacts:write
GET/v1/listscontacts:read
POST/v1/listscontacts:write
DELETE/v1/lists/{id}contacts:write

Creating one

POST /v1/contacts

{
  "phone_number": "+447400123456",
  "first_name": "Ada",
  "last_name": "Lovelace"
}
{
  "id": "01j8xk2p7n4qc3m9v0wz5bt6ay",
  "object": "contact",
  "reference": "K7Q0M1",
  "phone_number": "+447400123456",
  "country": "GB",
  "first_name": "Ada",
  "last_name": "Lovelace",
  "consent_status": "unknown",
  "created_at": "2026-09-17T09:14:22+00:00"
}

The reference

Every contact gets a six-character code. It is there to be read aloud: somebody rings up, quotes theirs, and you find them.

The alphabet is Crockford base-32 — digits, then A–Z with I, L, O and U left out. I, L and O go because they are indistinguishable from 1, 1 and 0 in most fonts and over a phone line. U goes so a random code cannot spell anything unfortunate, which matters because these appear in links sent under your brand.

GET /v1/contacts?reference=K7QOM1

Lookup is forgiving, and that is the whole point. An O is read as a zero, an I or an L as a one, a U as a V, case is ignored, and spaces and hyphens are stripped. k7q-om1 finds K7Q0M1. Nobody should have to say "that's a letter O, not a zero".

Importing in bulk

POST /v1/contacts/import

{
  "contacts": [
    { "phone_number": "+447400123456", "first_name": "Ada" },
    { "phone_number": "+447400123457", "first_name": "Grace" }
  ],
  "list_id": "01j8xk…",
  "consent_status": "opted_in",
  "consent_source": "Booking form, March 2026"
}
{
  "object": "import_result",
  "imported": 2,
  "already_suppressed": 0,
  "invalid": [],
  "list_size": 2
}

Up to 5,000 rows a call. Three fields in the answer are worth reading rather than ignoring:

Importing someone again does not un-opt them out. The suppression wins, every time. If it did not, the way to resurrect a list of people who had asked you to stop would be to upload it again.

consent_source is not a formality

Setting consent_status to opted_in requires it, and the sentence is stored against every contact in the file. Marketing needs recorded prior opt-in under PECR; if somebody complains, that line is the answer. The import is also the only moment anybody still knows what it was.

Lists

POST /v1/lists          { "name": "Regulars" }
GET  /v1/contacts?list_id=01j8xk…

A campaign sends to a list, so this is the step between having contacts and being able to message them. Deleting a list does not delete its contacts.

Searching

GET /v1/contacts?search=lovelace
GET /v1/contacts?search=447400
GET /v1/contacts?search=K7Q0M1

One box across name, number and reference. A code-shaped term is also resolved forgivingly, so the search box behaves the same way the reference filter does.