Telephony accounts
7 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/accounts | List the tenant's carrier accounts ordered by name, paginated |
| POST | /telephony/accounts | Create a carrier account binding a provider to a stored credential, returning the new account |
| GET | /telephony/accounts/{id} | Fetch one carrier account with its credential binding, health and number-sync state |
| PATCH | /telephony/accounts/{id} | Update a carrier account's name, credential binding, default flag or settings, returning the refreshed row |
| DELETE | /telephony/accounts/{id} | Delete a carrier account |
| POST | /telephony/accounts/{id}/health | Live carrier health check; the result is persisted so the UI can show drift |
| GET | /telephony/providers | The capability matrix (§188). Clients render buttons from this rather than from a hard-coded list |
Endpoints
List the tenant's carrier accounts ordered by name, paginated
GET/telephony/accounts
- No scope is enforced in the handler — any valid key may call it.
bash
curl -X GET "$CALLAGENT_API_URL/telephony/accounts" \
-H "Authorization: Bearer $CALLAGENT_API_KEY" \
-H "Accept: application/json"js
const response = await fetch(`${process.env.CALLAGENT_API_URL}/telephony/accounts`, {
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/accounts');
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/accounts",
headers={"Authorization": f"Bearer {os.environ['CALLAGENT_API_KEY']}"},
timeout=30,
)
response.raise_for_status()
data = response.json()["data"]Responses
| Status | Body |
|---|---|
200 | data-wrapped array |
Example response, generated from the schema:
json
{
"data": [
"string"
],
"meta": {
"page": 1,
"limit": 25,
"total": 1,
"pages": 1
}
}Create a carrier account binding a provider to a stored credential, returning the new account
POST/telephony/accounts
- No scope is enforced in the handler — any valid key may call it.
- Request body keys are camelCase (e.g. providerCredentialId, externalAccountId, isDefault), not the snake_case the rest of the wire format uses
Request body
| Field | Type | Required | Notes |
|---|---|---|---|
provider | twilio · 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. |
name | string | yes | max 255 chars |
description | string | null | — | max 500 chars |
providerCredentialId | string | null | — | |
externalAccountId | string | null | — | max 255 chars |
isDefault | boolean | null | — | |
settings | string[] | null | — |
bash
curl -X POST "$CALLAGENT_API_URL/telephony/accounts" \
-H "Authorization: Bearer $CALLAGENT_API_KEY" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{
"provider": "twilio",
"name": "Support Line",
"isDefault": true
}'js
const response = await fetch(`${process.env.CALLAGENT_API_URL}/telephony/accounts`, {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.CALLAGENT_API_KEY}`,
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
"provider": "twilio",
"name": "Support Line",
"isDefault": 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') . '/telephony/accounts');
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',
'name' => 'Support Line',
'isDefault' => 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']}/telephony/accounts",
headers={"Authorization": f"Bearer {os.environ['CALLAGENT_API_KEY']}"},
json={
"provider": "twilio",
"name": "Support Line",
"isDefault": True
},
timeout=30,
)
response.raise_for_status()
data = response.json()["data"]Responses
| Status | Body |
|---|---|
201 | data-wrapped TelephonyAccount |
Errors
| Status | When it happens |
|---|---|
422 | Validation failed, or the referenced provider credential does not exist |
Example response, generated from the schema:
json
{
"data": {
"id": "01JQ0000000000000000000000",
"organization_id": "01JQ0000000000000000000000",
"provider": "twilio",
"name": "Support Line",
"description": "string",
"provider_credential_id": "01JQ0000000000000000000000",
"external_account_id": "01JQ0000000000000000000000",
"external_account_label": "string",
"is_subaccount": true,
"parent_external_account_id": "01JQ0000000000000000000000",
"voice_webhook_url": "https://example.com/hook",
"status_webhook_url": "https://example.com/hook",
"sms_webhook_url": "https://example.com/hook",
"is_default": true,
"active": true,
"health_status": "string",
"health_checked_at": "2026-01-01T12:00:00+00:00",
"health_message": "string",
"numbers_synced_at": "2026-01-01T12:00:00+00:00",
"numbers_count": 1,
"capabilities": {},
"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"
}
}Fetch one carrier account with its credential binding, health and number-sync state
GET/telephony/accounts/{id}
- No scope is enforced in the handler — any valid key may call it.
Path parameters
| Name | Type | Required | Notes |
|---|---|---|---|
id | string | yes |
bash
curl -X GET "$CALLAGENT_API_URL/telephony/accounts/01JQ0000000000000000000000" \
-H "Authorization: Bearer $CALLAGENT_API_KEY" \
-H "Accept: application/json"js
const response = await fetch(`${process.env.CALLAGENT_API_URL}/telephony/accounts/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/accounts/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/accounts/01JQ0000000000000000000000",
headers={"Authorization": f"Bearer {os.environ['CALLAGENT_API_KEY']}"},
timeout=30,
)
response.raise_for_status()
data = response.json()["data"]Responses
| Status | Body |
|---|---|
200 | data-wrapped TelephonyAccount |
Errors
| Status | When it happens |
|---|---|
404 | No such account for this tenant |
Example response, generated from the schema:
json
{
"data": {
"id": "01JQ0000000000000000000000",
"organization_id": "01JQ0000000000000000000000",
"provider": "twilio",
"name": "Support Line",
"description": "string",
"provider_credential_id": "01JQ0000000000000000000000",
"external_account_id": "01JQ0000000000000000000000",
"external_account_label": "string",
"is_subaccount": true,
"parent_external_account_id": "01JQ0000000000000000000000",
"voice_webhook_url": "https://example.com/hook",
"status_webhook_url": "https://example.com/hook",
"sms_webhook_url": "https://example.com/hook",
"is_default": true,
"active": true,
"health_status": "string",
"health_checked_at": "2026-01-01T12:00:00+00:00",
"health_message": "string",
"numbers_synced_at": "2026-01-01T12:00:00+00:00",
"numbers_count": 1,
"capabilities": {},
"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"
}
}Update a carrier account's name, credential binding, default flag or settings, returning the refreshed row
PATCH/telephony/accounts/{id}
- No scope is enforced in the handler — any valid key may call it.
- Request body keys are camelCase (e.g. providerCredentialId, externalAccountId, isDefault), not the snake_case the rest of the wire format uses
Path parameters
| Name | Type | Required | Notes |
|---|---|---|---|
id | string | yes |
Request body
| Field | Type | Required | Notes |
|---|---|---|---|
name | string | — | max 255 chars |
description | string | null | — | max 500 chars |
providerCredentialId | string | null | — | |
externalAccountId | string | null | — | max 255 chars |
isDefault | boolean | null | — | |
active | boolean | null | — | |
settings | string[] | null | — |
bash
curl -X PATCH "$CALLAGENT_API_URL/telephony/accounts/01JQ0000000000000000000000" \
-H "Authorization: Bearer $CALLAGENT_API_KEY" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{
"name": "Support Line",
"isDefault": true,
"active": true
}'js
const response = await fetch(`${process.env.CALLAGENT_API_URL}/telephony/accounts/01JQ0000000000000000000000`, {
method: 'PATCH',
headers: {
Authorization: `Bearer ${process.env.CALLAGENT_API_KEY}`,
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
"name": "Support Line",
"isDefault": true,
"active": 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') . '/telephony/accounts/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([
'name' => 'Support Line',
'isDefault' => true,
'active' => 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.patch(
f"{os.environ['CALLAGENT_API_URL']}/telephony/accounts/01JQ0000000000000000000000",
headers={"Authorization": f"Bearer {os.environ['CALLAGENT_API_KEY']}"},
json={
"name": "Support Line",
"isDefault": True,
"active": True
},
timeout=30,
)
response.raise_for_status()
data = response.json()["data"]Responses
| Status | Body |
|---|---|
200 | data-wrapped object |
Errors
| Status | When it happens |
|---|---|
404 | No such account for this tenant |
422 | Validation failed, or the referenced provider credential does not exist |
Example response, generated from the schema:
json
{
"data": {
"id": "01JQ0000000000000000000000",
"organization_id": "01JQ0000000000000000000000",
"provider": "twilio",
"name": "Support Line",
"description": "string",
"provider_credential_id": "01JQ0000000000000000000000",
"external_account_id": "01JQ0000000000000000000000",
"external_account_label": "string",
"is_subaccount": true,
"parent_external_account_id": "01JQ0000000000000000000000",
"voice_webhook_url": "https://example.com/hook",
"status_webhook_url": "https://example.com/hook",
"sms_webhook_url": "https://example.com/hook",
"is_default": true,
"active": true,
"health_status": "string",
"health_checked_at": "2026-01-01T12:00:00+00:00",
"health_message": "string",
"numbers_synced_at": "2026-01-01T12:00:00+00:00",
"numbers_count": 1,
"capabilities": {},
"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"
}
}Delete a carrier account
DELETE/telephony/accounts/{id}
- No scope is enforced in the handler — any valid key may call it.
- This response is not
data-wrapped.
Path parameters
| Name | Type | Required | Notes |
|---|---|---|---|
id | string | yes |
bash
curl -X DELETE "$CALLAGENT_API_URL/telephony/accounts/01JQ0000000000000000000000" \
-H "Authorization: Bearer $CALLAGENT_API_KEY" \
-H "Accept: application/json"js
const response = await fetch(`${process.env.CALLAGENT_API_URL}/telephony/accounts/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/accounts/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/accounts/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 |
|---|---|
404 | No such account for this tenant |
Live carrier health check; the result is persisted so the UI can show drift
POST/telephony/accounts/{id}/health
- No scope is enforced in the handler — any valid key may call it.
Path parameters
| Name | Type | Required | Notes |
|---|---|---|---|
id | string | yes |
bash
curl -X POST "$CALLAGENT_API_URL/telephony/accounts/01JQ0000000000000000000000/health" \
-H "Authorization: Bearer $CALLAGENT_API_KEY" \
-H "Accept: application/json"js
const response = await fetch(`${process.env.CALLAGENT_API_URL}/telephony/accounts/01JQ0000000000000000000000/health`, {
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/accounts/01JQ0000000000000000000000/health');
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/accounts/01JQ0000000000000000000000/health",
headers={"Authorization": f"Bearer {os.environ['CALLAGENT_API_KEY']}"},
timeout=30,
)
response.raise_for_status()
data = response.json()["data"]Responses
| Status | Body |
|---|---|
200 | JSON object |
Errors
| Status | When it happens |
|---|---|
404 | No such account for this tenant |
422 | The account has no provider credential, or the credential is incomplete |
501 | The provider has no adapter implemented |
502 | The carrier rejected the health call |
504 | The carrier could not be reached |
Example response, generated from the schema:
json
{
"data": {
"status": "string",
"latency_ms": 1,
"message": "string",
"details": {}
}
}The capability matrix (§188). Clients render buttons from this rather than from a hard-coded list
GET/telephony/providers
- No scope is enforced in the handler — any valid key may call it.
bash
curl -X GET "$CALLAGENT_API_URL/telephony/providers" \
-H "Authorization: Bearer $CALLAGENT_API_KEY" \
-H "Accept: application/json"js
const response = await fetch(`${process.env.CALLAGENT_API_URL}/telephony/providers`, {
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/providers');
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/providers",
headers={"Authorization": f"Bearer {os.environ['CALLAGENT_API_KEY']}"},
timeout=30,
)
response.raise_for_status()
data = response.json()["data"]Responses
| Status | Body |
|---|---|
200 | data-wrapped object |
Example response, generated from the schema:
json
{
"data": {}
}