Skip to content

Phone numbers ​

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/telephony/phone-numbersList the tenant's phone numbers, filterable by provider, status, country or number substring, paginated
POST/telephony/phone-numbersRegister a number the tenant already controls (BYO/SIP) without any carrier purchase, returning the new row
GET/telephony/phone-numbers/{id}Fetch one phone number row with its routing, hook, recording and capability configuration
PATCH/telephony/phone-numbers/{id}Update a number's routing destination, hooks, caller id, recording and status, returning the refreshed row
DELETE/telephony/phone-numbers/{id}Soft-delete the phone number row; releasing it at the carrier is a separate explicit action
POST/telephony/phone-numbers/{id}/configurePush CallAgent's webhook URLs for this number to the carrier and report whether the config is now in sync
POST/telephony/phone-numbers/{id}/releaseRelease the number at the carrier first, then mark the row released and disable inbound and outbound
POST/telephony/phone-numbers/importImport every number a carrier account already owns, updating existing rows and marking vanished ones released
POST/telephony/phone-numbers/purchaseBuy a number at the carrier, persist it only after the carrier confirms, then push webhook config best-effort
POST/telephony/phone-numbers/searchSearch a carrier's inventory for available numbers by country, type, area code or pattern before buying

Endpoints ​

List the tenant's phone numbers, filterable by provider, status, country or number substring, paginated ​

GET/telephony/phone-numbers
  • No scope is enforced in the handler — any valid key may call it.

Query parameters

NameTypeRequiredNotes
providerstring—
statusstring—
countrystring—
numberstring—
bash
curl -X GET "$CALLAGENT_API_URL/telephony/phone-numbers?provider=string&status=string" \
  -H "Authorization: Bearer $CALLAGENT_API_KEY" \
  -H "Accept: application/json"
js
const response = await fetch(`${process.env.CALLAGENT_API_URL}/telephony/phone-numbers?provider=string&status=string`, {
  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') . '/telephony/phone-numbers?provider=string&status=string');

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']}/telephony/phone-numbers?provider=string&status=string",
    headers={"Authorization": f"Bearer {os.environ['CALLAGENT_API_KEY']}"},
    timeout=30,
)
response.raise_for_status()
data = response.json()["data"]

Responses

StatusBody
200data-wrapped array

Example response, generated from the schema:

json
{
  "data": [
    "string"
  ],
  "meta": {
    "page": 1,
    "limit": 25,
    "total": 1,
    "pages": 1
  }
}

Register a number the tenant already controls (BYO/SIP) without any carrier purchase, returning the new row ​

POST/telephony/phone-numbers
  • No scope is enforced in the handler — any valid key may call it.
  • Request body keys are camelCase (e.g. friendlyName, sipTrunkId, assistantId), not the snake_case the rest of the wire format uses

Request body

FieldTypeRequiredNotes
numberstringyesmax 32 chars
friendlyNamestring | null—max 255 chars
sipTrunkIdstring | null—
assistantIdstring | null—
squadIdstring | null—
capabilitiesvoice · sms · mms · fax[] | null—
bash
curl -X POST "$CALLAGENT_API_URL/telephony/phone-numbers" \
  -H "Authorization: Bearer $CALLAGENT_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d '{
       "number": "+40712345678",
       "friendlyName": "Support Line",
       "capabilities": [
         "voice"
       ]
     }'
js
const response = await fetch(`${process.env.CALLAGENT_API_URL}/telephony/phone-numbers`, {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.CALLAGENT_API_KEY}`,
    Accept: 'application/json',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    "number": "+40712345678",
    "friendlyName": "Support Line",
    "capabilities": [
      "voice"
    ]
  }),
});

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') . '/telephony/phone-numbers');

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([
    'number' => '+40712345678',
    'friendlyName' => 'Support Line',
    'capabilities' => [
        'voice',
    ],
]),
]);

$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']}/telephony/phone-numbers",
    headers={"Authorization": f"Bearer {os.environ['CALLAGENT_API_KEY']}"},
    json={
      "number": "+40712345678",
      "friendlyName": "Support Line",
      "capabilities": [
        "voice"
      ]
    },
    timeout=30,
)
response.raise_for_status()
data = response.json()["data"]

Responses

StatusBody
200JSON object
201data-wrapped PhoneNumber

Errors

StatusWhen it happens
409A non-released row for this E.164 number already exists in the organization
422Validation failed, or the number is not valid E.164

Example response, generated from the schema:

json
"string"

Fetch one phone number row with its routing, hook, recording and capability configuration ​

GET/telephony/phone-numbers/{id}
  • No scope is enforced in the handler — any valid key may call it.

Path parameters

NameTypeRequiredNotes
idstringyes
bash
curl -X GET "$CALLAGENT_API_URL/telephony/phone-numbers/01JQ0000000000000000000000" \
  -H "Authorization: Bearer $CALLAGENT_API_KEY" \
  -H "Accept: application/json"
js
const response = await fetch(`${process.env.CALLAGENT_API_URL}/telephony/phone-numbers/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') . '/telephony/phone-numbers/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']}/telephony/phone-numbers/01JQ0000000000000000000000",
    headers={"Authorization": f"Bearer {os.environ['CALLAGENT_API_KEY']}"},
    timeout=30,
)
response.raise_for_status()
data = response.json()["data"]

Responses

StatusBody
200data-wrapped PhoneNumber

Errors

StatusWhen it happens
404No such number for this tenant

Example response, generated from the schema:

json
{
  "data": {
    "id": "01JQ0000000000000000000000",
    "organization_id": "01JQ0000000000000000000000",
    "number": "+40712345678",
    "friendly_name": "Support Line",
    "provider": "twilio",
    "telephony_account_id": "01JQ0000000000000000000000",
    "provider_credential_id": "01JQ0000000000000000000000",
    "provider_number_sid": "string",
    "provider_account_sid": "string",
    "provider_metadata": {},
    "capabilities": {},
    "country": "string",
    "region": "string",
    "locality": "string",
    "number_type": "string",
    "address_requirement": "string",
    "destination_type": "assistant",
    "destination_id": "01JQ0000000000000000000000",
    "destination_value": "string",
    "fallback_destination_id": "01JQ0000000000000000000000",
    "fallback_destination_type": "assistant",
    "assistant_id": "01JQ0000000000000000000000",
    "squad_id": "01JQ0000000000000000000000",
    "hook_enabled": true,
    "hook_url": "https://example.com/hook",
    "hook_timeout_ms": 30,
    "hook_headers": {},
    "hook_fallback": "string",
    "hook_last_success_at": "2026-01-01T12:00:00+00:00",
    "hook_last_failure_at": "2026-01-01T12:00:00+00:00",
    "hook_last_error": "string",
    "outbound_enabled": true,
    "outbound_caller_id_number": "+40712345678",
    "outbound_caller_id_name": "Support Line",
    "sip_trunk_id": "01JQ0000000000000000000000",
    "caller_id_policy_id": "01JQ0000000000000000000000",
    "sms_enabled": true,
    "sms_assistant_id": "01JQ0000000000000000000000",
    "sms_webhook_url": "https://example.com/hook",
    "messaging_service_sid": "string",
    "recording_mode": "disabled",
    "recording_consent_required": true,
    "voice_webhook_url": "https://example.com/hook",
    "status_webhook_url": "https://example.com/hook",
    "webhooks_configured_at": "2026-01-01T12:00:00+00:00",
    "webhooks_in_sync": true,
    "max_concurrent_calls": 1,
    "status": "pending",
    "inbound_enabled": true,
    "imported_at": "2026-01-01T12:00:00+00:00",
    "purchased_at": "2026-01-01T12:00:00+00:00",
    "released_at": "2026-01-01T12:00:00+00:00",
    "last_inbound_at": "2026-01-01T12:00:00+00:00",
    "last_outbound_at": "2026-01-01T12:00:00+00:00",
    "monthly_cost": "string",
    "setup_cost": "string",
    "currency": "string",
    "notes": "string",
    "settings": {},
    "metadata": {},
    "created_at": "2026-01-01T12:00:00+00:00",
    "updated_at": "2026-01-01T12:00:00+00:00",
    "deleted_at": "2026-01-01T12:00:00+00:00",
    "phone_number_hook_id": "01JQ0000000000000000000000"
  }
}

Update a number's routing destination, hooks, caller id, recording and status, returning the refreshed row ​

PATCH/telephony/phone-numbers/{id}
  • No scope is enforced in the handler — any valid key may call it.
  • camelCase body keys (friendlyName, hookUrl, ...); hookSecret is encrypted on write and never readable back

Path parameters

NameTypeRequiredNotes
idstringyes

Request body

FieldTypeRequiredNotes
friendlyNamestring | null—max 255 chars
destinationTypeassistant · squad · queue · ivr · ring_group · extension … | null—
destinationIdstring | null—max 32 chars
destinationValuestring | null—max 255 chars
assistantIdstring | null—max 32 chars
squadIdstring | null—max 32 chars
sipTrunkIdstring | null—
callerIdPolicyIdstring | null—
phoneNumberHookIdstring | null—
hookEnabledboolean | null—
hookUrlstring | null—uri. max 1024 chars
hookSecretstring | null—max 255 chars
hookTimeoutMsinteger | null—min 200. max 3000
hookFallbackstatic · reject · voicemail · null | null—
inboundEnabledboolean | null—
outboundEnabledboolean | null—
outboundCallerIdNumberstring | null—max 32 chars
outboundCallerIdNamestring | null—max 255 chars
smsEnabledboolean | null—
smsAssistantIdstring | null—max 32 chars
recordingModedisabled · mono · dual_channel · inherit | null—
recordingConsentRequiredboolean | null—
maxConcurrentCallsinteger | null—min 1
statuspending · active · suspended · released · error | null—
metadatastring[] | null—
bash
curl -X PATCH "$CALLAGENT_API_URL/telephony/phone-numbers/01JQ0000000000000000000000" \
  -H "Authorization: Bearer $CALLAGENT_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d '{
       "friendlyName": "Support Line",
       "destinationType": "assistant",
       "phoneNumberHookId": "+40712345678",
       "hookEnabled": true,
       "hookUrl": "https://example.com/hook",
       "hookTimeoutMs": 30,
       "hookFallback": "static"
     }'
js
const response = await fetch(`${process.env.CALLAGENT_API_URL}/telephony/phone-numbers/01JQ0000000000000000000000`, {
  method: 'PATCH',
  headers: {
    Authorization: `Bearer ${process.env.CALLAGENT_API_KEY}`,
    Accept: 'application/json',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    "friendlyName": "Support Line",
    "destinationType": "assistant",
    "phoneNumberHookId": "+40712345678",
    "hookEnabled": true,
    "hookUrl": "https://example.com/hook",
    "hookTimeoutMs": 30,
    "hookFallback": "static"
  }),
});

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') . '/telephony/phone-numbers/01JQ0000000000000000000000');

curl_setopt_array($handle, [
    CURLOPT_CUSTOMREQUEST => 'PATCH',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('CALLAGENT_API_KEY'),
        'Accept: application/json',
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode([
    'friendlyName' => 'Support Line',
    'destinationType' => 'assistant',
    'phoneNumberHookId' => '+40712345678',
    'hookEnabled' => true,
    'hookUrl' => 'https://example.com/hook',
    'hookTimeoutMs' => 30,
    'hookFallback' => 'static',
]),
]);

$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.patch(
    f"{os.environ['CALLAGENT_API_URL']}/telephony/phone-numbers/01JQ0000000000000000000000",
    headers={"Authorization": f"Bearer {os.environ['CALLAGENT_API_KEY']}"},
    json={
      "friendlyName": "Support Line",
      "destinationType": "assistant",
      "phoneNumberHookId": "+40712345678",
      "hookEnabled": True,
      "hookUrl": "https://example.com/hook",
      "hookTimeoutMs": 30,
      "hookFallback": "static"
    },
    timeout=30,
)
response.raise_for_status()
data = response.json()["data"]

Responses

StatusBody
200data-wrapped object

Errors

StatusWhen it happens
404No such number for this tenant
422Validation failed, or a referenced trunk/policy/hook id does not exist

Example response, generated from the schema:

json
{
  "data": {
    "id": "01JQ0000000000000000000000",
    "organization_id": "01JQ0000000000000000000000",
    "number": "+40712345678",
    "friendly_name": "Support Line",
    "provider": "twilio",
    "telephony_account_id": "01JQ0000000000000000000000",
    "provider_credential_id": "01JQ0000000000000000000000",
    "provider_number_sid": "string",
    "provider_account_sid": "string",
    "provider_metadata": {},
    "capabilities": {},
    "country": "string",
    "region": "string",
    "locality": "string",
    "number_type": "string",
    "address_requirement": "string",
    "destination_type": "assistant",
    "destination_id": "01JQ0000000000000000000000",
    "destination_value": "string",
    "fallback_destination_id": "01JQ0000000000000000000000",
    "fallback_destination_type": "assistant",
    "assistant_id": "01JQ0000000000000000000000",
    "squad_id": "01JQ0000000000000000000000",
    "hook_enabled": true,
    "hook_url": "https://example.com/hook",
    "hook_timeout_ms": 30,
    "hook_headers": {},
    "hook_fallback": "string",
    "hook_last_success_at": "2026-01-01T12:00:00+00:00",
    "hook_last_failure_at": "2026-01-01T12:00:00+00:00",
    "hook_last_error": "string",
    "outbound_enabled": true,
    "outbound_caller_id_number": "+40712345678",
    "outbound_caller_id_name": "Support Line",
    "sip_trunk_id": "01JQ0000000000000000000000",
    "caller_id_policy_id": "01JQ0000000000000000000000",
    "sms_enabled": true,
    "sms_assistant_id": "01JQ0000000000000000000000",
    "sms_webhook_url": "https://example.com/hook",
    "messaging_service_sid": "string",
    "recording_mode": "disabled",
    "recording_consent_required": true,
    "voice_webhook_url": "https://example.com/hook",
    "status_webhook_url": "https://example.com/hook",
    "webhooks_configured_at": "2026-01-01T12:00:00+00:00",
    "webhooks_in_sync": true,
    "max_concurrent_calls": 1,
    "status": "pending",
    "inbound_enabled": true,
    "imported_at": "2026-01-01T12:00:00+00:00",
    "purchased_at": "2026-01-01T12:00:00+00:00",
    "released_at": "2026-01-01T12:00:00+00:00",
    "last_inbound_at": "2026-01-01T12:00:00+00:00",
    "last_outbound_at": "2026-01-01T12:00:00+00:00",
    "monthly_cost": "string",
    "setup_cost": "string",
    "currency": "string",
    "notes": "string",
    "settings": {},
    "metadata": {},
    "created_at": "2026-01-01T12:00:00+00:00",
    "updated_at": "2026-01-01T12:00:00+00:00",
    "deleted_at": "2026-01-01T12:00:00+00:00",
    "phone_number_hook_id": "01JQ0000000000000000000000"
  }
}

Soft-delete the phone number row; releasing it at the carrier is a separate explicit action ​

DELETE/telephony/phone-numbers/{id}
  • No scope is enforced in the handler — any valid key may call it.
  • This response is not data-wrapped.

Path parameters

NameTypeRequiredNotes
idstringyes
bash
curl -X DELETE "$CALLAGENT_API_URL/telephony/phone-numbers/01JQ0000000000000000000000" \
  -H "Authorization: Bearer $CALLAGENT_API_KEY" \
  -H "Accept: application/json"
js
const response = await fetch(`${process.env.CALLAGENT_API_URL}/telephony/phone-numbers/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') . '/telephony/phone-numbers/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']}/telephony/phone-numbers/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
404No such number for this tenant

Push CallAgent's webhook URLs for this number to the carrier and report whether the config is now in sync ​

POST/telephony/phone-numbers/{id}/configure
  • No scope is enforced in the handler — any valid key may call it.
  • On failure it still returns a {"data": {configured: false}} body with status 502, not the usual error envelope

Path parameters

NameTypeRequiredNotes
idstringyes
bash
curl -X POST "$CALLAGENT_API_URL/telephony/phone-numbers/01JQ0000000000000000000000/configure" \
  -H "Authorization: Bearer $CALLAGENT_API_KEY" \
  -H "Accept: application/json"
js
const response = await fetch(`${process.env.CALLAGENT_API_URL}/telephony/phone-numbers/01JQ0000000000000000000000/configure`, {
  method: 'POST',
  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') . '/telephony/phone-numbers/01JQ0000000000000000000000/configure');

curl_setopt_array($handle, [
    CURLOPT_CUSTOMREQUEST => 'POST',
    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.post(
    f"{os.environ['CALLAGENT_API_URL']}/telephony/phone-numbers/01JQ0000000000000000000000/configure",
    headers={"Authorization": f"Bearer {os.environ['CALLAGENT_API_KEY']}"},
    timeout=30,
)
response.raise_for_status()
data = response.json()["data"]

Responses

StatusBody
200JSON object

Errors

StatusWhen it happens
404No such number for this tenant
502Carrier configuration failed

Example response, generated from the schema:

json
"string"

Release the number at the carrier first, then mark the row released and disable inbound and outbound ​

POST/telephony/phone-numbers/{id}/release
  • No scope is enforced in the handler — any valid key may call it.

Path parameters

NameTypeRequiredNotes
idstringyes
bash
curl -X POST "$CALLAGENT_API_URL/telephony/phone-numbers/01JQ0000000000000000000000/release" \
  -H "Authorization: Bearer $CALLAGENT_API_KEY" \
  -H "Accept: application/json"
js
const response = await fetch(`${process.env.CALLAGENT_API_URL}/telephony/phone-numbers/01JQ0000000000000000000000/release`, {
  method: 'POST',
  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') . '/telephony/phone-numbers/01JQ0000000000000000000000/release');

curl_setopt_array($handle, [
    CURLOPT_CUSTOMREQUEST => 'POST',
    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.post(
    f"{os.environ['CALLAGENT_API_URL']}/telephony/phone-numbers/01JQ0000000000000000000000/release",
    headers={"Authorization": f"Bearer {os.environ['CALLAGENT_API_KEY']}"},
    timeout=30,
)
response.raise_for_status()
data = response.json()["data"]

Responses

StatusBody
200JSON object

Errors

StatusWhen it happens
404No such number for this tenant
422No active credential, unknown provider, or the row has no carrier id
501The provider has no adapter or no release API
502The carrier rejected the release
504The carrier could not be reached

Example response, generated from the schema:

json
{
  "data": {
    "id": "01JQ0000000000000000000000",
    "organization_id": "01JQ0000000000000000000000",
    "number": "+40712345678",
    "friendly_name": "Support Line",
    "provider": "twilio",
    "telephony_account_id": "01JQ0000000000000000000000",
    "provider_credential_id": "01JQ0000000000000000000000",
    "provider_number_sid": "string",
    "provider_account_sid": "string",
    "provider_metadata": {},
    "capabilities": {},
    "country": "string",
    "region": "string",
    "locality": "string",
    "number_type": "string",
    "address_requirement": "string",
    "destination_type": "assistant",
    "destination_id": "01JQ0000000000000000000000",
    "destination_value": "string",
    "fallback_destination_id": "01JQ0000000000000000000000",
    "fallback_destination_type": "assistant",
    "assistant_id": "01JQ0000000000000000000000",
    "squad_id": "01JQ0000000000000000000000",
    "hook_enabled": true,
    "hook_url": "https://example.com/hook",
    "hook_timeout_ms": 30,
    "hook_headers": {},
    "hook_fallback": "string",
    "hook_last_success_at": "2026-01-01T12:00:00+00:00",
    "hook_last_failure_at": "2026-01-01T12:00:00+00:00",
    "hook_last_error": "string",
    "outbound_enabled": true,
    "outbound_caller_id_number": "+40712345678",
    "outbound_caller_id_name": "Support Line",
    "sip_trunk_id": "01JQ0000000000000000000000",
    "caller_id_policy_id": "01JQ0000000000000000000000",
    "sms_enabled": true,
    "sms_assistant_id": "01JQ0000000000000000000000",
    "sms_webhook_url": "https://example.com/hook",
    "messaging_service_sid": "string",
    "recording_mode": "disabled",
    "recording_consent_required": true,
    "voice_webhook_url": "https://example.com/hook",
    "status_webhook_url": "https://example.com/hook",
    "webhooks_configured_at": "2026-01-01T12:00:00+00:00",
    "webhooks_in_sync": true,
    "max_concurrent_calls": 1,
    "status": "pending",
    "inbound_enabled": true,
    "imported_at": "2026-01-01T12:00:00+00:00",
    "purchased_at": "2026-01-01T12:00:00+00:00",
    "released_at": "2026-01-01T12:00:00+00:00",
    "last_inbound_at": "2026-01-01T12:00:00+00:00",
    "last_outbound_at": "2026-01-01T12:00:00+00:00",
    "monthly_cost": "string",
    "setup_cost": "string",
    "currency": "string",
    "notes": "string",
    "settings": {},
    "metadata": {},
    "created_at": "2026-01-01T12:00:00+00:00",
    "updated_at": "2026-01-01T12:00:00+00:00",
    "deleted_at": "2026-01-01T12:00:00+00:00",
    "phone_number_hook_id": "01JQ0000000000000000000000"
  }
}

Import every number a carrier account already owns, updating existing rows and marking vanished ones released ​

POST/telephony/phone-numbers/import
  • No scope is enforced in the handler — any valid key may call it.
  • Request body keys are camelCase (e.g. telephonyAccountId), not the snake_case the rest of the wire format uses

Request body

FieldTypeRequiredNotes
providertwilio · telnyx · didww · plivo · zadarma · didlogic …yes§8.1. The telephony providers CallAgent can terminate or originate through. The value is what is stored in phone_numbers.provider, sip_trunks.provider and provider_credentials.provider — keep them stable, they are persisted.
telephonyAccountIdstring | null—
bash
curl -X POST "$CALLAGENT_API_URL/telephony/phone-numbers/import" \
  -H "Authorization: Bearer $CALLAGENT_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d '{
       "provider": "twilio"
     }'
js
const response = await fetch(`${process.env.CALLAGENT_API_URL}/telephony/phone-numbers/import`, {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.CALLAGENT_API_KEY}`,
    Accept: 'application/json',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    "provider": "twilio"
  }),
});

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') . '/telephony/phone-numbers/import');

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([
    'provider' => 'twilio',
]),
]);

$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']}/telephony/phone-numbers/import",
    headers={"Authorization": f"Bearer {os.environ['CALLAGENT_API_KEY']}"},
    json={
      "provider": "twilio"
    },
    timeout=30,
)
response.raise_for_status()
data = response.json()["data"]

Responses

StatusBody
200JSON object

Errors

StatusWhen it happens
422Validation failed, unknown provider, or no active credential for the provider
501The provider has no adapter or no inventory API wired up
502The carrier rejected the inventory call
504The carrier could not be reached

Example response, generated from the schema:

json
{
  "data": {
    "imported": 1,
    "updated": 1,
    "missing": 1,
    "numbers": [
      "string"
    ]
  }
}

Buy a number at the carrier, persist it only after the carrier confirms, then push webhook config best-effort ​

POST/telephony/phone-numbers/purchase
  • No scope is enforced in the handler — any valid key may call it.
  • Request body keys are camelCase (e.g. telephonyAccountId, friendlyName), not the snake_case the rest of the wire format uses

Request body

FieldTypeRequiredNotes
providertwilio · telnyx · didww · plivo · zadarma · didlogic …yes§8.1. The telephony providers CallAgent can terminate or originate through. The value is what is stored in phone_numbers.provider, sip_trunks.provider and provider_credentials.provider — keep them stable, they are persisted.
numberstringyesmax 32 chars
telephonyAccountIdstring | null—
friendlyNamestring | null—max 255 chars
bash
curl -X POST "$CALLAGENT_API_URL/telephony/phone-numbers/purchase" \
  -H "Authorization: Bearer $CALLAGENT_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d '{
       "provider": "twilio",
       "number": "+40712345678",
       "friendlyName": "Support Line"
     }'
js
const response = await fetch(`${process.env.CALLAGENT_API_URL}/telephony/phone-numbers/purchase`, {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.CALLAGENT_API_KEY}`,
    Accept: 'application/json',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    "provider": "twilio",
    "number": "+40712345678",
    "friendlyName": "Support Line"
  }),
});

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') . '/telephony/phone-numbers/purchase');

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([
    'provider' => 'twilio',
    'number' => '+40712345678',
    'friendlyName' => 'Support Line',
]),
]);

$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']}/telephony/phone-numbers/purchase",
    headers={"Authorization": f"Bearer {os.environ['CALLAGENT_API_KEY']}"},
    json={
      "provider": "twilio",
      "number": "+40712345678",
      "friendlyName": "Support Line"
    },
    timeout=30,
)
response.raise_for_status()
data = response.json()["data"]

Responses

StatusBody
200JSON object
201data-wrapped PhoneNumber

Errors

StatusWhen it happens
422Validation failed, unknown provider, or no active credential for the provider
501The provider has no adapter or no programmatic purchase API
502The carrier rejected the purchase
504The carrier could not be reached

Example response, generated from the schema:

json
"string"

Search a carrier's inventory for available numbers by country, type, area code or pattern before buying ​

POST/telephony/phone-numbers/search
  • No scope is enforced in the handler — any valid key may call it.
  • Request body keys are camelCase (e.g. areaCode), not the snake_case the rest of the wire format uses

Request body

FieldTypeRequiredNotes
providertwilio · telnyx · didww · plivo · zadarma · didlogic …yes§8.1. The telephony providers CallAgent can terminate or originate through. The value is what is stored in phone_numbers.provider, sip_trunks.provider and provider_credentials.provider — keep them stable, they are persisted.
countrystringyesmax 2 chars. min 2 chars
typelocal · mobile · toll_free · national · null | null—
areaCodestring | null—max 8 chars
containsstring | null—max 32 chars
regionstring | null—max 64 chars
localitystring | null—max 64 chars
capabilitiesstring[] | null—
limitinteger | null—min 1. max 100
bash
curl -X POST "$CALLAGENT_API_URL/telephony/phone-numbers/search" \
  -H "Authorization: Bearer $CALLAGENT_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d '{
       "provider": "twilio",
       "country": "string",
       "type": "local",
       "limit": 25
     }'
js
const response = await fetch(`${process.env.CALLAGENT_API_URL}/telephony/phone-numbers/search`, {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.CALLAGENT_API_KEY}`,
    Accept: 'application/json',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    "provider": "twilio",
    "country": "string",
    "type": "local",
    "limit": 25
  }),
});

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') . '/telephony/phone-numbers/search');

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([
    'provider' => 'twilio',
    'country' => 'string',
    'type' => 'local',
    'limit' => 25,
]),
]);

$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']}/telephony/phone-numbers/search",
    headers={"Authorization": f"Bearer {os.environ['CALLAGENT_API_KEY']}"},
    json={
      "provider": "twilio",
      "country": "string",
      "type": "local",
      "limit": 25
    },
    timeout=30,
)
response.raise_for_status()
data = response.json()["data"]

Responses

StatusBody
200JSON object

Errors

StatusWhen it happens
422Validation failed, unknown provider, or no active credential for the provider
501The provider has no adapter or no number-search API
502The carrier rejected the search
504The carrier could not be reached

Example response, generated from the schema:

json
{
  "data": {}
}

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