Contacts API
The Contacts API follows the same conventions as Leads API: paginated lists, JSON bodies, JWT auth, tenant-scoped automatically.
List
GET /api/contacts/?account=<account_id>&search=jane
Authorization: Bearer <token>
{
"count": 84,
"next": null,
"previous": null,
"results": [
{
"id": "c012-…",
"first_name": "Jane",
"last_name": "Doe",
"email": "jane@acme.com",
"phone": "+1 555 1234",
"title": "VP Engineering",
"account": { "id": "a001-…", "name": "Acme Corp" },
"linkedin_url": "https://linkedin.com/in/janedoe",
"owner": { "id": "u034-…", "email": "rep@yours.com" },
"custom_fields": { "preferred_channel": "email" },
"created_at": "2026-02-14T09:22:01Z"
}
]
}
Filterable fields: account, owner, search (matches name, email, phone), tags, created_at__gte, cf_<key>.
Create
POST /api/contacts/
Content-Type: application/json
Authorization: Bearer <token>
{
"first_name": "Jane",
"last_name": "Doe",
"email": "jane@acme.com",
"phone": "+1 555 1234",
"title": "VP Engineering",
"account_id": "a001-…",
"custom_fields": { "preferred_channel": "email" }
}
account_id is optional, pass null for standalone contacts.
Update / delete
PATCH /api/contacts/<id>/ # partial
PUT /api/contacts/<id>/ # full
DELETE /api/contacts/<id>/ # hard delete
DELETE removes the row. There is no soft delete, no ?forget=true variant and no PII scrub step;
those were documented here and none of them exists.
No merge
There is no POST /api/contacts/<id>/merge/. Duplicate contacts are resolved by hand.
CSV import
Contacts use a two-step import rather than a single upload, so you can see what a file will do before it does it:
POST /api/contacts/import/preview/
POST /api/contacts/import/commit/
Both take multipart/form-data with a file field, capped at 5 MB. preview parses and validates
and reports per-row errors as {row, field, message} without writing anything; commit performs
the insert.
Neither is reachable from the web app or the phone yet, so this is an API-only path today.