Find and merge duplicates
GET /api/v1/contacts/duplicates
Likely duplicate contact pairs: the same name once case and punctuation are ignored, or the same phone number. These are suspects, not verdicts, so review a pair before merging it. Requires the read:contacts scope.
GET /api/v1/contacts/duplicates?limit=25 HTTP/1.1
Host: agentlyleads.com
Authorization: Bearer alk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxlimit is 1 to 100 pairs (default 25).
{
"duplicates": [
{
"a": { "id": "cmq2cont0alk34567890", "name": "Ann Lee", "email": "ann@acme.com", "company": "Acme" },
"b": { "id": "cmq2cont1alk34567890", "name": "ann lee", "email": null, "company": null },
"reason": "same name"
}
]
}POST /api/v1/contacts/{id}/merge
Merges a duplicate into the contact at {id}, which survives. Requires the write:contacts scope.
POST /api/v1/contacts/cmq2cont0alk34567890/merge HTTP/1.1
Host: agentlyleads.com
Authorization: Bearer alk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Content-Type: application/json
{ "duplicateId": "cmq2cont1alk34567890", "confirm": true }What happens, in one transaction:
- All history moves to the survivor: communications, emails, deals, tasks, notes, briefs, attachments, campaign and outreach memberships.
- Tags are combined.
- Empty fields on the survivor are filled from the duplicate. Values the survivor already has are never overwritten.
- The duplicate is deleted.
This cannot be undone, so the body must include "confirm": true. Both the path ID and duplicateId accept the agentlyleads ID or your externalId. Pick as the survivor the contact with the better email and more history.
{
"survivorId": "cmq2cont0alk34567890",
"survivorName": "Ann Lee",
"deletedId": "cmq2cont1alk34567890",
"repointed": 7,
"collisionsDropped": 0
}repointed counts records moved to the survivor. collisionsDropped counts rows dropped because the survivor was already on the same campaign or outreach match.
| Status | When |
|---|---|
400 | confirm missing, or both IDs name the same contact |
404 | Either contact is not in your workspace |