Customers
10 endpoints. Every path below is relative to the API base URL and already includes the /api/v1 prefix — see conventions for authentication, pagination, errors and idempotency.
| Endpoint | ||
|---|---|---|
| GET | /customers | List active customers with their identifiers, filterable by source or a free-text search |
| POST | /customers | Create a customer, optionally with identifiers |
| GET | /customers/{customer} | Retrieve one customer with identifiers, attributes and conversation history |
| PUT | /customers/{customer} | Update a customer's profile fields and return the customer with its identifiers |
| DELETE | /customers/{customer} | Soft-delete a customer and return an empty body |
| POST | /customers/{customer}/attributes | Set customer attributes; feeds {{customer.*}} in the variable engine |
| GET | /customers/{customer}/duplicates | Candidate duplicates: other customers sharing a normalised identifier |
| POST | /customers/{customer}/identifiers | Attach a phone, email or external id to an existing customer |
| POST | /customers/{customer}/merge | Merge another customer into this one, leaving the loser pointing at the survivor |
| POST | /customers/resolve | Find or create a customer from a single identifier, returning the same customer on every call |
Endpoints
List active customers with their identifiers, filterable by source or a free-text search
- Requires
customer.read. - Page size comes from
per_page(default 25, max 100).searchmatches display_name, company and identifier value case-insensitively.
curl -X GET "$CALLAGENT_API_URL/customers" \
-H "Authorization: Bearer $CALLAGENT_API_KEY" \
-H "Accept: application/json"const response = await fetch(`${process.env.CALLAGENT_API_URL}/customers`, {
method: 'GET',
headers: {
Authorization: `Bearer ${process.env.CALLAGENT_API_KEY}`,
Accept: 'application/json',
},
});
if (!response.ok) {
const { error } = await response.json();
throw new Error(`${error.type}: ${error.message}`);
}
const { data } = await response.json();<?php
$handle = curl_init(getenv('CALLAGENT_API_URL') . '/customers');
curl_setopt_array($handle, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('CALLAGENT_API_KEY'),
'Accept: application/json',
],
]);
$body = json_decode(curl_exec($handle), true);
$status = curl_getinfo($handle, CURLINFO_HTTP_CODE);
curl_close($handle);
if ($status >= 400) {
throw new RuntimeException("{$body['error']['type']}: {$body['error']['message']}");
}
$data = $body['data'];import os, requests
response = requests.get(
f"{os.environ['CALLAGENT_API_URL']}/customers",
headers={"Authorization": f"Bearer {os.environ['CALLAGENT_API_KEY']}"},
timeout=30,
)
response.raise_for_status()
data = response.json()["data"]Responses
| Status | Body |
|---|---|
200 | Array of CustomerResource |
Errors
| Status | When it happens |
|---|---|
403 | The API key does not carry the customer.read scope |
Example response, generated from the schema:
{
"data": [
{
"id": "01JQ0000000000000000000000",
"object": "string",
"display_name": "Support Line",
"name": "Support Line",
"first_name": "Support Line",
"last_name": "Support Line",
"company": "string",
"locale": "string",
"timezone": "string",
"source": "string",
"external_id": "01JQ0000000000000000000000",
"externally_owned": true,
"do_not_contact": true,
"conversation_count": 1,
"first_seen_at": "2026-01-01T12:00:00+00:00",
"last_seen_at": "2026-01-01T12:00:00+00:00",
"merged_into_customer_id": "01JQ0000000000000000000000",
"metadata": {},
"notes": "string",
"created_at": "2026-01-01T12:00:00+00:00",
"identifiers": [],
"attributes": [],
"conversations": []
}
]
}Create a customer, optionally with identifiers
- Requires
customer.write.
Request body
| Field | Type | Required | Notes |
|---|---|---|---|
display_name | string | null | — | max 255 chars |
first_name | string | null | — | max 255 chars |
last_name | string | null | — | max 255 chars |
company | string | null | — | max 255 chars |
locale | string | null | — | max 16 chars |
timezone | string | null | — | max 64 chars |
source | string | null | — | max 48 chars |
external_id | string | null | — | max 255 chars |
notes | string | null | — | |
metadata | string[] | null | — | |
do_not_contact | boolean | null | — | |
identifiers | object[] | null | — |
curl -X POST "$CALLAGENT_API_URL/customers" \
-H "Authorization: Bearer $CALLAGENT_API_KEY" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{
"display_name": "Support Line",
"first_name": "Support Line",
"last_name": "Support Line",
"external_id": "01JQ0000000000000000000000",
"do_not_contact": true,
"identifiers": [
{
"type": "phone",
"value": "string",
"is_primary": true
}
]
}'const response = await fetch(`${process.env.CALLAGENT_API_URL}/customers`, {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.CALLAGENT_API_KEY}`,
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
"display_name": "Support Line",
"first_name": "Support Line",
"last_name": "Support Line",
"external_id": "01JQ0000000000000000000000",
"do_not_contact": true,
"identifiers": [
{
"type": "phone",
"value": "string",
"is_primary": true
}
]
}),
});
if (!response.ok) {
const { error } = await response.json();
throw new Error(`${error.type}: ${error.message}`);
}
const { data } = await response.json();<?php
$handle = curl_init(getenv('CALLAGENT_API_URL') . '/customers');
curl_setopt_array($handle, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('CALLAGENT_API_KEY'),
'Accept: application/json',
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode([
'display_name' => 'Support Line',
'first_name' => 'Support Line',
'last_name' => 'Support Line',
'external_id' => '01JQ0000000000000000000000',
'do_not_contact' => true,
'identifiers' => [
[
'type' => 'phone',
'value' => 'string',
'is_primary' => true,
],
],
]),
]);
$body = json_decode(curl_exec($handle), true);
$status = curl_getinfo($handle, CURLINFO_HTTP_CODE);
curl_close($handle);
if ($status >= 400) {
throw new RuntimeException("{$body['error']['type']}: {$body['error']['message']}");
}
$data = $body['data'];import os, requests
response = requests.post(
f"{os.environ['CALLAGENT_API_URL']}/customers",
headers={"Authorization": f"Bearer {os.environ['CALLAGENT_API_KEY']}"},
json={
"display_name": "Support Line",
"first_name": "Support Line",
"last_name": "Support Line",
"external_id": "01JQ0000000000000000000000",
"do_not_contact": True,
"identifiers": [
{
"type": "phone",
"value": "string",
"is_primary": True
}
]
},
timeout=30,
)
response.raise_for_status()
data = response.json()["data"]Responses
| Status | Body |
|---|---|
201 | data-wrapped object |
Errors
| Status | When it happens |
|---|---|
403 | The API key does not carry the customer.write scope |
422 | Field validation failed |
Example response, generated from the schema:
{
"data": {
"id": "01JQ0000000000000000000000",
"object": "string",
"display_name": "Support Line",
"name": "Support Line",
"first_name": "Support Line",
"last_name": "Support Line",
"company": "string",
"locale": "string",
"timezone": "string",
"source": "string",
"external_id": "01JQ0000000000000000000000",
"externally_owned": true,
"do_not_contact": true,
"conversation_count": 1,
"first_seen_at": "2026-01-01T12:00:00+00:00",
"last_seen_at": "2026-01-01T12:00:00+00:00",
"merged_into_customer_id": "01JQ0000000000000000000000",
"metadata": {},
"notes": "string",
"created_at": "2026-01-01T12:00:00+00:00",
"identifiers": [
{}
],
"attributes": [
"string"
],
"conversations": [
{}
]
}
}Retrieve one customer with identifiers, attributes and conversation history
- Requires
customer.read.
Path parameters
| Name | Type | Required | Notes |
|---|---|---|---|
customer | string | yes | The customer ID |
curl -X GET "$CALLAGENT_API_URL/customers/01JQ0000000000000000000000" \
-H "Authorization: Bearer $CALLAGENT_API_KEY" \
-H "Accept: application/json"const response = await fetch(`${process.env.CALLAGENT_API_URL}/customers/01JQ0000000000000000000000`, {
method: 'GET',
headers: {
Authorization: `Bearer ${process.env.CALLAGENT_API_KEY}`,
Accept: 'application/json',
},
});
if (!response.ok) {
const { error } = await response.json();
throw new Error(`${error.type}: ${error.message}`);
}
const { data } = await response.json();<?php
$handle = curl_init(getenv('CALLAGENT_API_URL') . '/customers/01JQ0000000000000000000000');
curl_setopt_array($handle, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('CALLAGENT_API_KEY'),
'Accept: application/json',
],
]);
$body = json_decode(curl_exec($handle), true);
$status = curl_getinfo($handle, CURLINFO_HTTP_CODE);
curl_close($handle);
if ($status >= 400) {
throw new RuntimeException("{$body['error']['type']}: {$body['error']['message']}");
}
$data = $body['data'];import os, requests
response = requests.get(
f"{os.environ['CALLAGENT_API_URL']}/customers/01JQ0000000000000000000000",
headers={"Authorization": f"Bearer {os.environ['CALLAGENT_API_KEY']}"},
timeout=30,
)
response.raise_for_status()
data = response.json()["data"]Responses
| Status | Body |
|---|---|
200 | CustomerResource |
Errors
| Status | When it happens |
|---|---|
403 | The API key does not carry the customer.read scope |
404 | No customer with that id in this organization |
Example response, generated from the schema:
{
"data": {
"id": "01JQ0000000000000000000000",
"object": "string",
"display_name": "Support Line",
"name": "Support Line",
"first_name": "Support Line",
"last_name": "Support Line",
"company": "string",
"locale": "string",
"timezone": "string",
"source": "string",
"external_id": "01JQ0000000000000000000000",
"externally_owned": true,
"do_not_contact": true,
"conversation_count": 1,
"first_seen_at": "2026-01-01T12:00:00+00:00",
"last_seen_at": "2026-01-01T12:00:00+00:00",
"merged_into_customer_id": "01JQ0000000000000000000000",
"metadata": {},
"notes": "string",
"created_at": "2026-01-01T12:00:00+00:00",
"identifiers": [
{}
],
"attributes": [
"string"
],
"conversations": [
{}
]
}
}Update a customer's profile fields and return the customer with its identifiers
- Requires
customer.write. PATCHis accepted on the same path, with the same body.- Every field is
sometimes, so a PUT behaves as a partial update;sourceandexternal_idcannot be changed here
Path parameters
| Name | Type | Required | Notes |
|---|---|---|---|
customer | string | yes | The customer ID |
Request body
| Field | Type | Required | Notes |
|---|---|---|---|
display_name | string | null | — | max 255 chars |
first_name | string | null | — | max 255 chars |
last_name | string | null | — | max 255 chars |
company | string | null | — | max 255 chars |
locale | string | null | — | max 16 chars |
timezone | string | null | — | max 64 chars |
notes | string | null | — | |
metadata | string[] | null | — | |
do_not_contact | boolean | — |
curl -X PUT "$CALLAGENT_API_URL/customers/01JQ0000000000000000000000" \
-H "Authorization: Bearer $CALLAGENT_API_KEY" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{
"display_name": "Support Line",
"first_name": "Support Line",
"last_name": "Support Line",
"do_not_contact": true
}'const response = await fetch(`${process.env.CALLAGENT_API_URL}/customers/01JQ0000000000000000000000`, {
method: 'PUT',
headers: {
Authorization: `Bearer ${process.env.CALLAGENT_API_KEY}`,
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
"display_name": "Support Line",
"first_name": "Support Line",
"last_name": "Support Line",
"do_not_contact": true
}),
});
if (!response.ok) {
const { error } = await response.json();
throw new Error(`${error.type}: ${error.message}`);
}
const { data } = await response.json();<?php
$handle = curl_init(getenv('CALLAGENT_API_URL') . '/customers/01JQ0000000000000000000000');
curl_setopt_array($handle, [
CURLOPT_CUSTOMREQUEST => 'PUT',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('CALLAGENT_API_KEY'),
'Accept: application/json',
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode([
'display_name' => 'Support Line',
'first_name' => 'Support Line',
'last_name' => 'Support Line',
'do_not_contact' => true,
]),
]);
$body = json_decode(curl_exec($handle), true);
$status = curl_getinfo($handle, CURLINFO_HTTP_CODE);
curl_close($handle);
if ($status >= 400) {
throw new RuntimeException("{$body['error']['type']}: {$body['error']['message']}");
}
$data = $body['data'];import os, requests
response = requests.put(
f"{os.environ['CALLAGENT_API_URL']}/customers/01JQ0000000000000000000000",
headers={"Authorization": f"Bearer {os.environ['CALLAGENT_API_KEY']}"},
json={
"display_name": "Support Line",
"first_name": "Support Line",
"last_name": "Support Line",
"do_not_contact": True
},
timeout=30,
)
response.raise_for_status()
data = response.json()["data"]Responses
| Status | Body |
|---|---|
200 | CustomerResource |
Errors
| Status | When it happens |
|---|---|
403 | The API key does not carry the customer.write scope |
404 | No customer with that id in this organization |
422 | Field validation failed |
Example response, generated from the schema:
{
"data": {
"id": "01JQ0000000000000000000000",
"object": "string",
"display_name": "Support Line",
"name": "Support Line",
"first_name": "Support Line",
"last_name": "Support Line",
"company": "string",
"locale": "string",
"timezone": "string",
"source": "string",
"external_id": "01JQ0000000000000000000000",
"externally_owned": true,
"do_not_contact": true,
"conversation_count": 1,
"first_seen_at": "2026-01-01T12:00:00+00:00",
"last_seen_at": "2026-01-01T12:00:00+00:00",
"merged_into_customer_id": "01JQ0000000000000000000000",
"metadata": {},
"notes": "string",
"created_at": "2026-01-01T12:00:00+00:00",
"identifiers": [
{}
],
"attributes": [
"string"
],
"conversations": [
{}
]
}
}Soft-delete a customer and return an empty body
- Requires
customer.delete. - This response is not
data-wrapped. - Soft delete: the row and its conversation history survive, so historical references still resolve
Path parameters
| Name | Type | Required | Notes |
|---|---|---|---|
customer | string | yes | The customer ID |
curl -X DELETE "$CALLAGENT_API_URL/customers/01JQ0000000000000000000000" \
-H "Authorization: Bearer $CALLAGENT_API_KEY" \
-H "Accept: application/json"const response = await fetch(`${process.env.CALLAGENT_API_URL}/customers/01JQ0000000000000000000000`, {
method: 'DELETE',
headers: {
Authorization: `Bearer ${process.env.CALLAGENT_API_KEY}`,
Accept: 'application/json',
},
});
if (!response.ok) {
const { error } = await response.json();
throw new Error(`${error.type}: ${error.message}`);
}
const { data } = await response.json();<?php
$handle = curl_init(getenv('CALLAGENT_API_URL') . '/customers/01JQ0000000000000000000000');
curl_setopt_array($handle, [
CURLOPT_CUSTOMREQUEST => 'DELETE',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('CALLAGENT_API_KEY'),
'Accept: application/json',
],
]);
$body = json_decode(curl_exec($handle), true);
$status = curl_getinfo($handle, CURLINFO_HTTP_CODE);
curl_close($handle);
if ($status >= 400) {
throw new RuntimeException("{$body['error']['type']}: {$body['error']['message']}");
}
$data = $body['data'];import os, requests
response = requests.delete(
f"{os.environ['CALLAGENT_API_URL']}/customers/01JQ0000000000000000000000",
headers={"Authorization": f"Bearer {os.environ['CALLAGENT_API_KEY']}"},
timeout=30,
)
response.raise_for_status()
data = response.json()["data"]Responses
| Status | Body |
|---|---|
204 | No content |
Errors
| Status | When it happens |
|---|---|
403 | The API key does not carry the customer.delete scope |
404 | No customer with that id in this organization |
Set customer attributes; feeds {{customer.*}} in the variable engine
- Requires
customer.write. - Returns 200, not 201, and upserts key by key: keys not in the request keep their previous values rather than being cleared
Path parameters
| Name | Type | Required | Notes |
|---|---|---|---|
customer | string | yes | The customer ID |
Request body
| Field | Type | Required | Notes |
|---|---|---|---|
attributes | string[] | yes | min 1 items |
source | string | null | — | max 48 chars |
call_id | string | null | — |
curl -X POST "$CALLAGENT_API_URL/customers/01JQ0000000000000000000000/attributes" \
-H "Authorization: Bearer $CALLAGENT_API_KEY" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{
"attributes": [
"string"
],
"call_id": "01JQ0000000000000000000000"
}'const response = await fetch(`${process.env.CALLAGENT_API_URL}/customers/01JQ0000000000000000000000/attributes`, {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.CALLAGENT_API_KEY}`,
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
"attributes": [
"string"
],
"call_id": "01JQ0000000000000000000000"
}),
});
if (!response.ok) {
const { error } = await response.json();
throw new Error(`${error.type}: ${error.message}`);
}
const { data } = await response.json();<?php
$handle = curl_init(getenv('CALLAGENT_API_URL') . '/customers/01JQ0000000000000000000000/attributes');
curl_setopt_array($handle, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('CALLAGENT_API_KEY'),
'Accept: application/json',
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode([
'attributes' => [
'string',
],
'call_id' => '01JQ0000000000000000000000',
]),
]);
$body = json_decode(curl_exec($handle), true);
$status = curl_getinfo($handle, CURLINFO_HTTP_CODE);
curl_close($handle);
if ($status >= 400) {
throw new RuntimeException("{$body['error']['type']}: {$body['error']['message']}");
}
$data = $body['data'];import os, requests
response = requests.post(
f"{os.environ['CALLAGENT_API_URL']}/customers/01JQ0000000000000000000000/attributes",
headers={"Authorization": f"Bearer {os.environ['CALLAGENT_API_KEY']}"},
json={
"attributes": [
"string"
],
"call_id": "01JQ0000000000000000000000"
},
timeout=30,
)
response.raise_for_status()
data = response.json()["data"]Responses
| Status | Body |
|---|---|
200 | CustomerResource |
Errors
| Status | When it happens |
|---|---|
403 | The API key does not carry the customer.write scope |
404 | No customer with that id in this organization |
422 | Field validation failed on attributes or call_id |
Example response, generated from the schema:
{
"data": {
"id": "01JQ0000000000000000000000",
"object": "string",
"display_name": "Support Line",
"name": "Support Line",
"first_name": "Support Line",
"last_name": "Support Line",
"company": "string",
"locale": "string",
"timezone": "string",
"source": "string",
"external_id": "01JQ0000000000000000000000",
"externally_owned": true,
"do_not_contact": true,
"conversation_count": 1,
"first_seen_at": "2026-01-01T12:00:00+00:00",
"last_seen_at": "2026-01-01T12:00:00+00:00",
"merged_into_customer_id": "01JQ0000000000000000000000",
"metadata": {},
"notes": "string",
"created_at": "2026-01-01T12:00:00+00:00",
"identifiers": [
{}
],
"attributes": [
"string"
],
"conversations": [
{}
]
}
}Candidate duplicates: other customers sharing a normalised identifier
- Requires
customer.read. - Unpaginated and capped at ten candidates by the merger service
Path parameters
| Name | Type | Required | Notes |
|---|---|---|---|
customer | string | yes | The customer ID |
curl -X GET "$CALLAGENT_API_URL/customers/01JQ0000000000000000000000/duplicates" \
-H "Authorization: Bearer $CALLAGENT_API_KEY" \
-H "Accept: application/json"const response = await fetch(`${process.env.CALLAGENT_API_URL}/customers/01JQ0000000000000000000000/duplicates`, {
method: 'GET',
headers: {
Authorization: `Bearer ${process.env.CALLAGENT_API_KEY}`,
Accept: 'application/json',
},
});
if (!response.ok) {
const { error } = await response.json();
throw new Error(`${error.type}: ${error.message}`);
}
const { data } = await response.json();<?php
$handle = curl_init(getenv('CALLAGENT_API_URL') . '/customers/01JQ0000000000000000000000/duplicates');
curl_setopt_array($handle, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('CALLAGENT_API_KEY'),
'Accept: application/json',
],
]);
$body = json_decode(curl_exec($handle), true);
$status = curl_getinfo($handle, CURLINFO_HTTP_CODE);
curl_close($handle);
if ($status >= 400) {
throw new RuntimeException("{$body['error']['type']}: {$body['error']['message']}");
}
$data = $body['data'];import os, requests
response = requests.get(
f"{os.environ['CALLAGENT_API_URL']}/customers/01JQ0000000000000000000000/duplicates",
headers={"Authorization": f"Bearer {os.environ['CALLAGENT_API_KEY']}"},
timeout=30,
)
response.raise_for_status()
data = response.json()["data"]Responses
| Status | Body |
|---|---|
200 | Array of CustomerResource |
Errors
| Status | When it happens |
|---|---|
403 | The API key does not carry the customer.read scope |
404 | No customer with that id in this organization |
Example response, generated from the schema:
{
"data": [
{
"id": "01JQ0000000000000000000000",
"object": "string",
"display_name": "Support Line",
"name": "Support Line",
"first_name": "Support Line",
"last_name": "Support Line",
"company": "string",
"locale": "string",
"timezone": "string",
"source": "string",
"external_id": "01JQ0000000000000000000000",
"externally_owned": true,
"do_not_contact": true,
"conversation_count": 1,
"first_seen_at": "2026-01-01T12:00:00+00:00",
"last_seen_at": "2026-01-01T12:00:00+00:00",
"merged_into_customer_id": "01JQ0000000000000000000000",
"metadata": {},
"notes": "string",
"created_at": "2026-01-01T12:00:00+00:00",
"identifiers": [],
"attributes": [],
"conversations": []
}
]
}Attach a phone, email or external id to an existing customer
- Requires
customer.write.
Path parameters
| Name | Type | Required | Notes |
|---|---|---|---|
customer | string | yes | The customer ID |
Request body
| Field | Type | Required | Notes |
|---|---|---|---|
type | phone · email · external_id · sip_uri · whatsapp | yes | |
value | string | yes | max 191 chars |
source | string | null | — | max 48 chars |
is_primary | boolean | null | — |
curl -X POST "$CALLAGENT_API_URL/customers/01JQ0000000000000000000000/identifiers" \
-H "Authorization: Bearer $CALLAGENT_API_KEY" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{
"type": "phone",
"value": "string",
"is_primary": true
}'const response = await fetch(`${process.env.CALLAGENT_API_URL}/customers/01JQ0000000000000000000000/identifiers`, {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.CALLAGENT_API_KEY}`,
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
"type": "phone",
"value": "string",
"is_primary": true
}),
});
if (!response.ok) {
const { error } = await response.json();
throw new Error(`${error.type}: ${error.message}`);
}
const { data } = await response.json();<?php
$handle = curl_init(getenv('CALLAGENT_API_URL') . '/customers/01JQ0000000000000000000000/identifiers');
curl_setopt_array($handle, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('CALLAGENT_API_KEY'),
'Accept: application/json',
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode([
'type' => 'phone',
'value' => 'string',
'is_primary' => true,
]),
]);
$body = json_decode(curl_exec($handle), true);
$status = curl_getinfo($handle, CURLINFO_HTTP_CODE);
curl_close($handle);
if ($status >= 400) {
throw new RuntimeException("{$body['error']['type']}: {$body['error']['message']}");
}
$data = $body['data'];import os, requests
response = requests.post(
f"{os.environ['CALLAGENT_API_URL']}/customers/01JQ0000000000000000000000/identifiers",
headers={"Authorization": f"Bearer {os.environ['CALLAGENT_API_KEY']}"},
json={
"type": "phone",
"value": "string",
"is_primary": True
},
timeout=30,
)
response.raise_for_status()
data = response.json()["data"]Responses
| Status | Body |
|---|---|
201 | data-wrapped object |
Errors
| Status | When it happens |
|---|---|
403 | The API key does not carry the customer.write scope |
404 | No customer with that id in this organization |
422 | Unsupported identifier type or a value that will not normalise |
Example response, generated from the schema:
{
"data": {
"id": "01JQ0000000000000000000000",
"object": "string",
"display_name": "Support Line",
"name": "Support Line",
"first_name": "Support Line",
"last_name": "Support Line",
"company": "string",
"locale": "string",
"timezone": "string",
"source": "string",
"external_id": "01JQ0000000000000000000000",
"externally_owned": true,
"do_not_contact": true,
"conversation_count": 1,
"first_seen_at": "2026-01-01T12:00:00+00:00",
"last_seen_at": "2026-01-01T12:00:00+00:00",
"merged_into_customer_id": "01JQ0000000000000000000000",
"metadata": {},
"notes": "string",
"created_at": "2026-01-01T12:00:00+00:00",
"identifiers": [
{}
],
"attributes": [
"string"
],
"conversations": [
{}
]
}
}Merge another customer into this one, leaving the loser pointing at the survivor
The loser is kept with merged_into_customer_id set, so every historical reference to it still resolves here.
- Requires
customer.write. - The loser is kept with merged_into_customer_id set rather than deleted. The path customer is the survivor; the body's customer_id is absorbed.
Path parameters
| Name | Type | Required | Notes |
|---|---|---|---|
customer | string | yes | The customer ID |
Request body
| Field | Type | Required | Notes |
|---|---|---|---|
customer_id | string | yes |
curl -X POST "$CALLAGENT_API_URL/customers/01JQ0000000000000000000000/merge" \
-H "Authorization: Bearer $CALLAGENT_API_KEY" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{
"customer_id": "01JQ0000000000000000000000"
}'const response = await fetch(`${process.env.CALLAGENT_API_URL}/customers/01JQ0000000000000000000000/merge`, {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.CALLAGENT_API_KEY}`,
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
"customer_id": "01JQ0000000000000000000000"
}),
});
if (!response.ok) {
const { error } = await response.json();
throw new Error(`${error.type}: ${error.message}`);
}
const { data } = await response.json();<?php
$handle = curl_init(getenv('CALLAGENT_API_URL') . '/customers/01JQ0000000000000000000000/merge');
curl_setopt_array($handle, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('CALLAGENT_API_KEY'),
'Accept: application/json',
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode([
'customer_id' => '01JQ0000000000000000000000',
]),
]);
$body = json_decode(curl_exec($handle), true);
$status = curl_getinfo($handle, CURLINFO_HTTP_CODE);
curl_close($handle);
if ($status >= 400) {
throw new RuntimeException("{$body['error']['type']}: {$body['error']['message']}");
}
$data = $body['data'];import os, requests
response = requests.post(
f"{os.environ['CALLAGENT_API_URL']}/customers/01JQ0000000000000000000000/merge",
headers={"Authorization": f"Bearer {os.environ['CALLAGENT_API_KEY']}"},
json={
"customer_id": "01JQ0000000000000000000000"
},
timeout=30,
)
response.raise_for_status()
data = response.json()["data"]Responses
| Status | Body |
|---|---|
200 | CustomerResource |
Errors
| Status | When it happens |
|---|---|
403 | The API key does not carry the customer.write scope |
404 | No such customer to merge in, or the target customer does not exist |
422 | customer_id is missing or is not a ULID |
Example response, generated from the schema:
{
"data": {
"id": "01JQ0000000000000000000000",
"object": "string",
"display_name": "Support Line",
"name": "Support Line",
"first_name": "Support Line",
"last_name": "Support Line",
"company": "string",
"locale": "string",
"timezone": "string",
"source": "string",
"external_id": "01JQ0000000000000000000000",
"externally_owned": true,
"do_not_contact": true,
"conversation_count": 1,
"first_seen_at": "2026-01-01T12:00:00+00:00",
"last_seen_at": "2026-01-01T12:00:00+00:00",
"merged_into_customer_id": "01JQ0000000000000000000000",
"metadata": {},
"notes": "string",
"created_at": "2026-01-01T12:00:00+00:00",
"identifiers": [
{}
],
"attributes": [
"string"
],
"conversations": [
{}
]
}
}Find or create a customer from a single identifier, returning the same customer on every call
This is what an inbound call uses: give it the caller's number and it returns the same customer every time, creating one on first contact.
- Requires
customer.write. - Always 200, never 201, even when it creates the customer.
typeis guessed fromvaluewhen omitted, andattributesare merged onto the record.
Request body
| Field | Type | Required | Notes |
|---|---|---|---|
type | phone · email · external_id · sip_uri · whatsapp · null | null | — | |
value | string | yes | max 191 chars |
source | string | null | — | max 48 chars |
attributes | string[] | null | — |
curl -X POST "$CALLAGENT_API_URL/customers/resolve" \
-H "Authorization: Bearer $CALLAGENT_API_KEY" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{
"value": "string",
"type": "phone"
}'const response = await fetch(`${process.env.CALLAGENT_API_URL}/customers/resolve`, {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.CALLAGENT_API_KEY}`,
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
"value": "string",
"type": "phone"
}),
});
if (!response.ok) {
const { error } = await response.json();
throw new Error(`${error.type}: ${error.message}`);
}
const { data } = await response.json();<?php
$handle = curl_init(getenv('CALLAGENT_API_URL') . '/customers/resolve');
curl_setopt_array($handle, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('CALLAGENT_API_KEY'),
'Accept: application/json',
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode([
'value' => 'string',
'type' => 'phone',
]),
]);
$body = json_decode(curl_exec($handle), true);
$status = curl_getinfo($handle, CURLINFO_HTTP_CODE);
curl_close($handle);
if ($status >= 400) {
throw new RuntimeException("{$body['error']['type']}: {$body['error']['message']}");
}
$data = $body['data'];import os, requests
response = requests.post(
f"{os.environ['CALLAGENT_API_URL']}/customers/resolve",
headers={"Authorization": f"Bearer {os.environ['CALLAGENT_API_KEY']}"},
json={
"value": "string",
"type": "phone"
},
timeout=30,
)
response.raise_for_status()
data = response.json()["data"]Responses
| Status | Body |
|---|---|
200 | data-wrapped object |
Errors
| Status | When it happens |
|---|---|
403 | The API key does not carry the customer.write scope |
422 | Unsupported identifier type, unusable value, or no bound organization |
Example response, generated from the schema:
{
"data": {
"id": "01JQ0000000000000000000000",
"object": "string",
"display_name": "Support Line",
"name": "Support Line",
"first_name": "Support Line",
"last_name": "Support Line",
"company": "string",
"locale": "string",
"timezone": "string",
"source": "string",
"external_id": "01JQ0000000000000000000000",
"externally_owned": true,
"do_not_contact": true,
"conversation_count": 1,
"first_seen_at": "2026-01-01T12:00:00+00:00",
"last_seen_at": "2026-01-01T12:00:00+00:00",
"merged_into_customer_id": "01JQ0000000000000000000000",
"metadata": {},
"notes": "string",
"created_at": "2026-01-01T12:00:00+00:00",
"identifiers": [
{}
],
"attributes": [
"string"
],
"conversations": [
{}
]
}
}