Merge Contacts
curl --request POST \
--url https://api.example.com/v1/contacts/merge \
--header 'Content-Type: application/json' \
--data '
{
"primary_id": "<string>",
"duplicate_ids": [
{}
],
"overwrite": true,
"dry_run": true
}
'import requests
url = "https://api.example.com/v1/contacts/merge"
payload = {
"primary_id": "<string>",
"duplicate_ids": [{}],
"overwrite": True,
"dry_run": True
}
headers = {"Content-Type": "application/json"}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({primary_id: '<string>', duplicate_ids: [{}], overwrite: true, dry_run: true})
};
fetch('https://api.example.com/v1/contacts/merge', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.example.com/v1/contacts/merge",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'primary_id' => '<string>',
'duplicate_ids' => [
[
]
],
'overwrite' => true,
'dry_run' => true
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.example.com/v1/contacts/merge"
payload := strings.NewReader("{\n \"primary_id\": \"<string>\",\n \"duplicate_ids\": [\n {}\n ],\n \"overwrite\": true,\n \"dry_run\": true\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.example.com/v1/contacts/merge")
.header("Content-Type", "application/json")
.body("{\n \"primary_id\": \"<string>\",\n \"duplicate_ids\": [\n {}\n ],\n \"overwrite\": true,\n \"dry_run\": true\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.example.com/v1/contacts/merge")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Content-Type"] = 'application/json'
request.body = "{\n \"primary_id\": \"<string>\",\n \"duplicate_ids\": [\n {}\n ],\n \"overwrite\": true,\n \"dry_run\": true\n}"
response = http.request(request)
puts response.read_body{
"data": {
"primary": {},
"merged_ids": [
{}
],
"fields_populated": [
{}
],
"counts": {}
}
}Bulk Operations
Merge Contacts
Merge one or more duplicate contacts into a primary record
POST
/
v1
/
contacts
/
merge
Merge Contacts
curl --request POST \
--url https://api.example.com/v1/contacts/merge \
--header 'Content-Type: application/json' \
--data '
{
"primary_id": "<string>",
"duplicate_ids": [
{}
],
"overwrite": true,
"dry_run": true
}
'import requests
url = "https://api.example.com/v1/contacts/merge"
payload = {
"primary_id": "<string>",
"duplicate_ids": [{}],
"overwrite": True,
"dry_run": True
}
headers = {"Content-Type": "application/json"}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({primary_id: '<string>', duplicate_ids: [{}], overwrite: true, dry_run: true})
};
fetch('https://api.example.com/v1/contacts/merge', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.example.com/v1/contacts/merge",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'primary_id' => '<string>',
'duplicate_ids' => [
[
]
],
'overwrite' => true,
'dry_run' => true
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.example.com/v1/contacts/merge"
payload := strings.NewReader("{\n \"primary_id\": \"<string>\",\n \"duplicate_ids\": [\n {}\n ],\n \"overwrite\": true,\n \"dry_run\": true\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.example.com/v1/contacts/merge")
.header("Content-Type", "application/json")
.body("{\n \"primary_id\": \"<string>\",\n \"duplicate_ids\": [\n {}\n ],\n \"overwrite\": true,\n \"dry_run\": true\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.example.com/v1/contacts/merge")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Content-Type"] = 'application/json'
request.body = "{\n \"primary_id\": \"<string>\",\n \"duplicate_ids\": [\n {}\n ],\n \"overwrite\": true,\n \"dry_run\": true\n}"
response = http.request(request)
puts response.read_body{
"data": {
"primary": {},
"merged_ids": [
{}
],
"fields_populated": [
{}
],
"counts": {}
}
}Request
Merges fold the duplicates into the primary contact. The primary keeps its UUID; duplicate UUIDs are retired. Non-empty fields on the primary are preserved; missing fields are filled from the duplicates in order. Notes, activities, tasks, emails, documents, and list memberships are re-homed to the primary.Headers
Authorization: Bearer wbk_your_api_key_here
Content-Type: application/json
string
Strongly recommended. Same key returns the same result within 24 hours.
Body Parameters
string
required
UUID of the contact that will remain after the merge.
array
required
UUIDs of contacts to merge into the primary. Maximum 20 per request.
boolean
default:"false"
When
true, non-empty fields on the primary can be overwritten by duplicate values. Defaults to fill-only behavior.boolean
default:"false"
When
true, the API returns a preview of the merge without making changes.Response
object
X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, and X-Request-ID. Each merged duplicate emits a contact.merged webhook event.
curl -X POST \
https://data.leadlex.com/functions/v1/api-gateway/v1/contacts/merge \
-H "Authorization: Bearer wbk_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"primary_id": "123e4567-e89b-12d3-a456-426614174000",
"duplicate_ids": ["456e7890-a1b2-34c5-d678-901234567890"]
}'
import requests
API_KEY = "wbk_your_api_key_here"
BASE_URL = "https://data.leadlex.com/functions/v1/api-gateway"
r = requests.post(
f"{BASE_URL}/v1/contacts/merge",
headers={
"Authorization": f"Bearer {API_KEY}",
"Content-Type": "application/json",
},
json={
"primary_id": "123e4567-e89b-12d3-a456-426614174000",
"duplicate_ids": ["456e7890-a1b2-34c5-d678-901234567890"],
},
)
print(r.json()["data"])
const res = await fetch(
'https://data.leadlex.com/functions/v1/api-gateway/v1/contacts/merge',
{
method: 'POST',
headers: {
'Authorization': 'Bearer wbk_your_api_key_here',
'Content-Type': 'application/json',
},
body: JSON.stringify({
primary_id: '123e4567-e89b-12d3-a456-426614174000',
duplicate_ids: ['456e7890-a1b2-34c5-d678-901234567890'],
}),
}
);
const { data } = await res.json();
console.log(data);
Example Response
{
"data": {
"primary": {
"id": "123e4567-e89b-12d3-a456-426614174000",
"full_name": "Jane Doe",
"email": "jane@acme.com",
"phone": "+1-555-0100"
},
"merged_ids": ["456e7890-a1b2-34c5-d678-901234567890"],
"fields_populated": ["phone", "linkedin_url"],
"counts": {
"notes": 3,
"activities": 11,
"tasks": 2,
"emails": 18,
"documents": 1,
"list_memberships": 4
}
}
}
Errors
| Status | Code | Description |
|---|---|---|
| 400 | validation_error | More than 20 duplicates, or primary_id appears in duplicate_ids |
| 401 | invalid_key | Invalid or expired API key |
| 403 | insufficient_permissions | Missing write:contacts permission |
| 404 | not_found | Primary or any duplicate ID was not found |
| 409 | merge_conflict | Duplicates reference each other in a circular chain |
| 429 | rate_limited | Rate limit exceeded |