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>/ # soft delete
DELETE /api/contacts/<id>/?forget=true # hard delete + PII scrub
Merge
POST /api/contacts/<id>/merge/
{ "loser_id": "c099-…" }
The loser's emails, calls, and references migrate to the winner inside a transaction. The winner's own fields take precedence on conflicts.
CSV upload
POST /api/contacts/upload/
Content-Type: multipart/form-data
file=<contacts.csv>
The response is the same per-row shape as the Leads upload — created, updated, skipped, errors.