Skip to content

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/customersList active customers with their identifiers, filterable by source or a free-text search
POST/customersCreate 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}/attributesSet customer attributes; feeds {{customer.*}} in the variable engine
GET/customers/{customer}/duplicatesCandidate duplicates: other customers sharing a normalised identifier
POST/customers/{customer}/identifiersAttach a phone, email or external id to an existing customer
POST/customers/{customer}/mergeMerge another customer into this one, leaving the loser pointing at the survivor
POST/customers/resolveFind 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 ​

GET/customers
  • Requires customer.read.
  • Page size comes from per_page (default 25, max 100). search matches display_name, company and identifier value case-insensitively.
bash
curl -X GET "$CALLAGENT_API_URL/customers" \
  -H "Authorization: Bearer $CALLAGENT_API_KEY" \
  -H "Accept: application/json"
js
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
<?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'];
python
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

StatusBody
200Array of CustomerResource

Errors

StatusWhen it happens
403The API key does not carry the customer.read scope

Example response, generated from the schema:

json
{
  "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 ​

POST/customers
  • Requires customer.write.

Request body

FieldTypeRequiredNotes
display_namestring | null—max 255 chars
first_namestring | null—max 255 chars
last_namestring | null—max 255 chars
companystring | null—max 255 chars
localestring | null—max 16 chars
timezonestring | null—max 64 chars
sourcestring | null—max 48 chars
external_idstring | null—max 255 chars
notesstring | null—
metadatastring[] | null—
do_not_contactboolean | null—
identifiersobject[] | null—
bash
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
         }
       ]
     }'
js
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
<?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'];
python
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

StatusBody
201data-wrapped object

Errors

StatusWhen it happens
403The API key does not carry the customer.write scope
422Field validation failed

Example response, generated from the schema:

json
{
  "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 ​

GET/customers/{customer}
  • Requires customer.read.

Path parameters

NameTypeRequiredNotes
customerstringyesThe customer ID
bash
curl -X GET "$CALLAGENT_API_URL/customers/01JQ0000000000000000000000" \
  -H "Authorization: Bearer $CALLAGENT_API_KEY" \
  -H "Accept: application/json"
js
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
<?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'];
python
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

StatusBody
200CustomerResource

Errors

StatusWhen it happens
403The API key does not carry the customer.read scope
404No customer with that id in this organization

Example response, generated from the schema:

json
{
  "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 ​

PUT/customers/{customer}
  • Requires customer.write.
  • PATCH is accepted on the same path, with the same body.
  • Every field is sometimes, so a PUT behaves as a partial update; source and external_id cannot be changed here

Path parameters

NameTypeRequiredNotes
customerstringyesThe customer ID

Request body

FieldTypeRequiredNotes
display_namestring | null—max 255 chars
first_namestring | null—max 255 chars
last_namestring | null—max 255 chars
companystring | null—max 255 chars
localestring | null—max 16 chars
timezonestring | null—max 64 chars
notesstring | null—
metadatastring[] | null—
do_not_contactboolean—
bash
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
     }'
js
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
<?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'];
python
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

StatusBody
200CustomerResource

Errors

StatusWhen it happens
403The API key does not carry the customer.write scope
404No customer with that id in this organization
422Field validation failed

Example response, generated from the schema:

json
{
  "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 ​

DELETE/customers/{customer}
  • 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

NameTypeRequiredNotes
customerstringyesThe customer ID
bash
curl -X DELETE "$CALLAGENT_API_URL/customers/01JQ0000000000000000000000" \
  -H "Authorization: Bearer $CALLAGENT_API_KEY" \
  -H "Accept: application/json"
js
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
<?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'];
python
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

StatusBody
204No content

Errors

StatusWhen it happens
403The API key does not carry the customer.delete scope
404No customer with that id in this organization

Set customer attributes; feeds {{customer.*}} in the variable engine ​

POST/customers/{customer}/attributes
  • 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

NameTypeRequiredNotes
customerstringyesThe customer ID

Request body

FieldTypeRequiredNotes
attributesstring[]yesmin 1 items
sourcestring | null—max 48 chars
call_idstring | null—
bash
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"
     }'
js
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
<?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'];
python
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

StatusBody
200CustomerResource

Errors

StatusWhen it happens
403The API key does not carry the customer.write scope
404No customer with that id in this organization
422Field validation failed on attributes or call_id

Example response, generated from the schema:

json
{
  "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 ​

GET/customers/{customer}/duplicates
  • Requires customer.read.
  • Unpaginated and capped at ten candidates by the merger service

Path parameters

NameTypeRequiredNotes
customerstringyesThe customer ID
bash
curl -X GET "$CALLAGENT_API_URL/customers/01JQ0000000000000000000000/duplicates" \
  -H "Authorization: Bearer $CALLAGENT_API_KEY" \
  -H "Accept: application/json"
js
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
<?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'];
python
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

StatusBody
200Array of CustomerResource

Errors

StatusWhen it happens
403The API key does not carry the customer.read scope
404No customer with that id in this organization

Example response, generated from the schema:

json
{
  "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 ​

POST/customers/{customer}/identifiers
  • Requires customer.write.

Path parameters

NameTypeRequiredNotes
customerstringyesThe customer ID

Request body

FieldTypeRequiredNotes
typephone · email · external_id · sip_uri · whatsappyes
valuestringyesmax 191 chars
sourcestring | null—max 48 chars
is_primaryboolean | null—
bash
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
     }'
js
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
<?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'];
python
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

StatusBody
201data-wrapped object

Errors

StatusWhen it happens
403The API key does not carry the customer.write scope
404No customer with that id in this organization
422Unsupported identifier type or a value that will not normalise

Example response, generated from the schema:

json
{
  "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 ​

POST/customers/{customer}/merge

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

NameTypeRequiredNotes
customerstringyesThe customer ID

Request body

FieldTypeRequiredNotes
customer_idstringyes
bash
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"
     }'
js
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
<?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'];
python
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

StatusBody
200CustomerResource

Errors

StatusWhen it happens
403The API key does not carry the customer.write scope
404No such customer to merge in, or the target customer does not exist
422customer_id is missing or is not a ULID

Example response, generated from the schema:

json
{
  "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 ​

POST/customers/resolve

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. type is guessed from value when omitted, and attributes are merged onto the record.

Request body

FieldTypeRequiredNotes
typephone · email · external_id · sip_uri · whatsapp · null | null—
valuestringyesmax 191 chars
sourcestring | null—max 48 chars
attributesstring[] | null—
bash
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"
     }'
js
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
<?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'];
python
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

StatusBody
200data-wrapped object

Errors

StatusWhen it happens
403The API key does not carry the customer.write scope
422Unsupported identifier type, unusable value, or no bound organization

Example response, generated from the schema:

json
{
  "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": [
      {}
    ]
  }
}

Documented against the code. Anything not yet implemented is labelled, never described as working.