Contacts
Base path: /contacts
List contacts
GET /contacts?page=1&page_size=20&group_id=&search=
search matches against phone, first name, or last name. Only active (non-opted-out) contacts
are returned.
Create a contact
POST /contacts
{ "phone": "0712345678", "first_name": "John", "last_name": "Doe", "group_id": null }409 if the phone number already exists in your contacts (per-tenant, not global).
Delete a contact
DELETE /contacts/{contact_id} — soft-delete: the contact is marked opted-out rather than
removed, preserving message history against them.
CSV import
Bulk-load contacts from a spreadsheet:
GET /contacts/template— download a sample CSV (phone,first_name,last_name).POST /contacts/import,multipart/form-datawith fieldsfile(the.csv) and optionalgroup_id.
{
"imported": 187,
"skipped": 4,
"errors": 2,
"message": "187 contacts imported successfully. 4 duplicates skipped. 2 rows had invalid phone numbers."
}The header row is optional — if the first column isn’t literally phone, rows are read
positionally as phone, first_name, last_name. Invalid phone numbers are skipped and counted
under errors; already-existing numbers are counted under skipped.
Groups
Contacts are organised into groups, which campaigns target.
GET /contacts/groups— list your groups.POST /contacts/groups—{ "name": "...", "description": "..." }.DELETE /contacts/groups/{group_id}.