Merge Contacts
curl --request POST \
--url https://api.bland.ai/v1/contacts/merge \
--header 'Content-Type: application/json' \
--header 'authorization: <authorization>' \
--data '
{
"primary_contact_id": "<string>",
"duplicate_contact_id": "<string>"
}
'import requests
url = "https://api.bland.ai/v1/contacts/merge"
payload = {
"primary_contact_id": "<string>",
"duplicate_contact_id": "<string>"
}
headers = {
"authorization": "<authorization>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {authorization: '<authorization>', 'Content-Type': 'application/json'},
body: JSON.stringify({primary_contact_id: '<string>', duplicate_contact_id: '<string>'})
};
fetch('https://api.bland.ai/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.bland.ai/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_contact_id' => '<string>',
'duplicate_contact_id' => '<string>'
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json",
"authorization: <authorization>"
],
]);
$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.bland.ai/v1/contacts/merge"
payload := strings.NewReader("{\n \"primary_contact_id\": \"<string>\",\n \"duplicate_contact_id\": \"<string>\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("authorization", "<authorization>")
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.bland.ai/v1/contacts/merge")
.header("authorization", "<authorization>")
.header("Content-Type", "application/json")
.body("{\n \"primary_contact_id\": \"<string>\",\n \"duplicate_contact_id\": \"<string>\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.bland.ai/v1/contacts/merge")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["authorization"] = '<authorization>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"primary_contact_id\": \"<string>\",\n \"duplicate_contact_id\": \"<string>\"\n}"
response = http.request(request)
puts response.read_body{
"data": {
"id": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee",
"org_id": "11111111-2222-3333-4444-555555555555",
"name": "John Doe",
"metadata": {
"source": "web_signup",
"phone_verified": true,
"email_verified": true
},
"created_at": "2025-07-20T10:30:00.000Z",
"updated_at": "2025-07-22T16:30:00.000Z"
},
"errors": null
}
{
"data": null,
"errors": [
{
"error": "BAD_REQUEST",
"message": "Cannot merge a contact with itself"
}
]
}
{
"data": null,
"errors": [
{
"error": "BAD_REQUEST",
"message": "Cannot merge contacts from different orgs"
}
]
}
{
"data": null,
"errors": [
{
"error": "NOT_FOUND",
"message": "Contact not found"
}
]
}
Contacts
Merge Contacts
Merge two contacts into one. All data from the duplicate contact is moved to the primary contact, and the duplicate is deleted.
POST
/
v1
/
contacts
/
merge
Merge Contacts
curl --request POST \
--url https://api.bland.ai/v1/contacts/merge \
--header 'Content-Type: application/json' \
--header 'authorization: <authorization>' \
--data '
{
"primary_contact_id": "<string>",
"duplicate_contact_id": "<string>"
}
'import requests
url = "https://api.bland.ai/v1/contacts/merge"
payload = {
"primary_contact_id": "<string>",
"duplicate_contact_id": "<string>"
}
headers = {
"authorization": "<authorization>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {authorization: '<authorization>', 'Content-Type': 'application/json'},
body: JSON.stringify({primary_contact_id: '<string>', duplicate_contact_id: '<string>'})
};
fetch('https://api.bland.ai/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.bland.ai/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_contact_id' => '<string>',
'duplicate_contact_id' => '<string>'
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json",
"authorization: <authorization>"
],
]);
$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.bland.ai/v1/contacts/merge"
payload := strings.NewReader("{\n \"primary_contact_id\": \"<string>\",\n \"duplicate_contact_id\": \"<string>\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("authorization", "<authorization>")
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.bland.ai/v1/contacts/merge")
.header("authorization", "<authorization>")
.header("Content-Type", "application/json")
.body("{\n \"primary_contact_id\": \"<string>\",\n \"duplicate_contact_id\": \"<string>\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.bland.ai/v1/contacts/merge")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["authorization"] = '<authorization>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"primary_contact_id\": \"<string>\",\n \"duplicate_contact_id\": \"<string>\"\n}"
response = http.request(request)
puts response.read_body{
"data": {
"id": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee",
"org_id": "11111111-2222-3333-4444-555555555555",
"name": "John Doe",
"metadata": {
"source": "web_signup",
"phone_verified": true,
"email_verified": true
},
"created_at": "2025-07-20T10:30:00.000Z",
"updated_at": "2025-07-22T16:30:00.000Z"
},
"errors": null
}
{
"data": null,
"errors": [
{
"error": "BAD_REQUEST",
"message": "Cannot merge a contact with itself"
}
]
}
{
"data": null,
"errors": [
{
"error": "BAD_REQUEST",
"message": "Cannot merge contacts from different orgs"
}
]
}
{
"data": null,
"errors": [
{
"error": "NOT_FOUND",
"message": "Contact not found"
}
]
}
Headers
string
required
Your API key for authentication.
Body Parameters
string
required
The ID of the contact that will remain after the merge. Data from the duplicate will be merged into this contact.
string
required
The ID of the contact to merge and delete. All associated data (calls, conversations, memory) will be transferred to the primary contact.
Response
object
The merged contact object.
string
Unique identifier for the merged contact (same as primary_contact_id).
string
Organization ID the contact belongs to.
string
Contact’s name (prefers primary, falls back to duplicate if primary is empty).
object
Merged metadata from both contacts (primary takes precedence for conflicting keys).
string
ISO timestamp when the contact was created.
string
ISO timestamp when the contact was last updated.
null
Error array (null on success).
Merge Behavior
When merging contacts:- Identifiers: All phone numbers, emails, and external IDs from the duplicate are added to the primary contact
- Metadata: Metadata objects are merged, with primary contact values taking precedence for conflicting keys
- Name: The primary contact’s name is kept, unless it’s empty (then the duplicate’s name is used)
- Calls: All calls associated with the duplicate are reassigned to the primary contact
- SMS Conversations: All SMS conversations are reassigned to the primary contact
- Contact Memory: Memory data is merged (facts, recent messages, summaries) for each persona/agent. If both contacts have memory for the same persona, the data is intelligently combined
{
"data": {
"id": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee",
"org_id": "11111111-2222-3333-4444-555555555555",
"name": "John Doe",
"metadata": {
"source": "web_signup",
"phone_verified": true,
"email_verified": true
},
"created_at": "2025-07-20T10:30:00.000Z",
"updated_at": "2025-07-22T16:30:00.000Z"
},
"errors": null
}
{
"data": null,
"errors": [
{
"error": "BAD_REQUEST",
"message": "Cannot merge a contact with itself"
}
]
}
{
"data": null,
"errors": [
{
"error": "BAD_REQUEST",
"message": "Cannot merge contacts from different orgs"
}
]
}
{
"data": null,
"errors": [
{
"error": "NOT_FOUND",
"message": "Contact not found"
}
]
}
Docs for agents: llms.txt
Was this page helpful?