Skip to content

Extensions ​

8 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/contact-center/extensionsList extensions with a summary projection, filterable by type and active flag, paginated
POST/contact-center/extensionsProvisioning returns the SIP secret exactly once — this response is the only time it is ever readable
GET/contact-center/extensions/{id}Fetch one extension with its full detail projection and its SIP devices (never their secrets)
PATCH/contact-center/extensions/{id}Update an extension's identity, voicemail, forwarding and DND settings, then re-push the FreeSWITCH directory
DELETE/contact-center/extensions/{id}Soft-delete an extension and re-push the FreeSWITCH directory for its organization
POST/contact-center/extensions/{id}/devicesAdd a SIP device to an extension and return its generated secret once; it is never readable again
DELETE/contact-center/extensions/{id}/devices/{deviceId}Revoke a SIP device: deactivate it, soft-delete it, re-push the directory and flush FreeSWITCH's cache
POST/contact-center/extensions/{id}/devices/{deviceId}/rotateIssue a new SIP secret for a device, returning it once; the old secret dies on the next directory push

Endpoints ​

List extensions with a summary projection, filterable by type and active flag, paginated ​

GET/contact-center/extensions
  • No scope is enforced in the handler — any valid key may call it.

Query parameters

NameTypeRequiredNotes
typestring—
activeboolean—default false
bash
curl -X GET "$CALLAGENT_API_URL/contact-center/extensions?type=string&active=false" \
  -H "Authorization: Bearer $CALLAGENT_API_KEY" \
  -H "Accept: application/json"
js
const response = await fetch(`${process.env.CALLAGENT_API_URL}/contact-center/extensions?type=string&active=false`, {
  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') . '/contact-center/extensions?type=string&active=false');

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']}/contact-center/extensions?type=string&active=false",
    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": {},
  "meta": {
    "page": 1,
    "limit": 25,
    "total": 1,
    "pages": 1
  }
}

Provisioning returns the SIP secret exactly once — this response is the only time it is ever readable ​

POST/contact-center/extensions
  • Requires extension.write.
  • extension.write is a user permission checked via Gate; API-key requests have no user, so the check is skipped

Request body

FieldTypeRequiredNotes
extensionstring | null—max 24 chars
namestringyesmax 255 chars
descriptionstring | null—max 255 chars
typeuser · operator · virtual · queue · ivr · ring_group … | null—
user_idstring | null—
voicemail_box_idstring | null—
outbound_caller_id_numberstring | null—max 32 chars
outbound_caller_id_namestring | null—max 64 chars
ring_timeout_secondsinteger | null—min 5. max 120
with_web_phoneboolean | null—
bash
curl -X POST "$CALLAGENT_API_URL/contact-center/extensions" \
  -H "Authorization: Bearer $CALLAGENT_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d '{
       "name": "Support Line",
       "type": "user",
       "user_id": "01JQ0000000000000000000000",
       "voicemail_box_id": "01JQ0000000000000000000000",
       "outbound_caller_id_number": "+40712345678",
       "outbound_caller_id_name": "Support Line",
       "ring_timeout_seconds": 30
     }'
js
const response = await fetch(`${process.env.CALLAGENT_API_URL}/contact-center/extensions`, {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.CALLAGENT_API_KEY}`,
    Accept: 'application/json',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    "name": "Support Line",
    "type": "user",
    "user_id": "01JQ0000000000000000000000",
    "voicemail_box_id": "01JQ0000000000000000000000",
    "outbound_caller_id_number": "+40712345678",
    "outbound_caller_id_name": "Support Line",
    "ring_timeout_seconds": 30
  }),
});

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') . '/contact-center/extensions');

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([
    'name' => 'Support Line',
    'type' => 'user',
    'user_id' => '01JQ0000000000000000000000',
    'voicemail_box_id' => '01JQ0000000000000000000000',
    'outbound_caller_id_number' => '+40712345678',
    'outbound_caller_id_name' => 'Support Line',
    'ring_timeout_seconds' => 30,
]),
]);

$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']}/contact-center/extensions",
    headers={"Authorization": f"Bearer {os.environ['CALLAGENT_API_KEY']}"},
    json={
      "name": "Support Line",
      "type": "user",
      "user_id": "01JQ0000000000000000000000",
      "voicemail_box_id": "01JQ0000000000000000000000",
      "outbound_caller_id_number": "+40712345678",
      "outbound_caller_id_name": "Support Line",
      "ring_timeout_seconds": 30
    },
    timeout=30,
)
response.raise_for_status()
data = response.json()["data"]

Responses

StatusBody
200JSON object

Errors

StatusWhen it happens
403Session user lacks the extension.write permission
409The requested extension number is already in use, including soft-deleted rows
422Validation failed, or a referenced user/voicemail box does not exist

Fetch one extension with its full detail projection and its SIP devices (never their secrets) ​

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

Responses

StatusBody
200data-wrapped object

Errors

StatusWhen it happens
404No such extension for this tenant

Example response, generated from the schema:

json
{
  "data": {}
}

Update an extension's identity, voicemail, forwarding and DND settings, then re-push the FreeSWITCH directory ​

PATCH/contact-center/extensions/{id}
  • Requires extension.write.
  • extension.write is a user permission checked via Gate; API-key requests have no user, so the check is skipped

Path parameters

NameTypeRequiredNotes
idstringyes

Request body

FieldTypeRequiredNotes
namestring—max 255 chars
descriptionstring | null—max 255 chars
user_idstring | null—
voicemail_box_idstring | null—
voicemail_enabledboolean | null—
outbound_caller_id_numberstring | null—max 32 chars
outbound_caller_id_namestring | null—max 64 chars
ring_timeout_secondsinteger | null—min 5. max 120
do_not_disturbboolean | null—
call_waitingboolean | null—
forward_always_enabledboolean | null—
forward_always_destinationstring | null—max 128 chars
is_activeboolean | null—
bash
curl -X PATCH "$CALLAGENT_API_URL/contact-center/extensions/01JQ0000000000000000000000" \
  -H "Authorization: Bearer $CALLAGENT_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d '{
       "name": "Support Line",
       "user_id": "01JQ0000000000000000000000",
       "voicemail_box_id": "01JQ0000000000000000000000",
       "voicemail_enabled": true,
       "outbound_caller_id_number": "+40712345678",
       "outbound_caller_id_name": "Support Line",
       "ring_timeout_seconds": 30
     }'
js
const response = await fetch(`${process.env.CALLAGENT_API_URL}/contact-center/extensions/01JQ0000000000000000000000`, {
  method: 'PATCH',
  headers: {
    Authorization: `Bearer ${process.env.CALLAGENT_API_KEY}`,
    Accept: 'application/json',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    "name": "Support Line",
    "user_id": "01JQ0000000000000000000000",
    "voicemail_box_id": "01JQ0000000000000000000000",
    "voicemail_enabled": true,
    "outbound_caller_id_number": "+40712345678",
    "outbound_caller_id_name": "Support Line",
    "ring_timeout_seconds": 30
  }),
});

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') . '/contact-center/extensions/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',
    'user_id' => '01JQ0000000000000000000000',
    'voicemail_box_id' => '01JQ0000000000000000000000',
    'voicemail_enabled' => true,
    'outbound_caller_id_number' => '+40712345678',
    'outbound_caller_id_name' => 'Support Line',
    'ring_timeout_seconds' => 30,
]),
]);

$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']}/contact-center/extensions/01JQ0000000000000000000000",
    headers={"Authorization": f"Bearer {os.environ['CALLAGENT_API_KEY']}"},
    json={
      "name": "Support Line",
      "user_id": "01JQ0000000000000000000000",
      "voicemail_box_id": "01JQ0000000000000000000000",
      "voicemail_enabled": True,
      "outbound_caller_id_number": "+40712345678",
      "outbound_caller_id_name": "Support Line",
      "ring_timeout_seconds": 30
    },
    timeout=30,
)
response.raise_for_status()
data = response.json()["data"]

Responses

StatusBody
200JSON object

Errors

StatusWhen it happens
403Session user lacks the extension.write permission
404No such extension for this tenant
422Validation failed, or a referenced user/voicemail box does not exist

Soft-delete an extension and re-push the FreeSWITCH directory for its organization ​

DELETE/contact-center/extensions/{id}
  • Requires extension.write.
  • This response is not data-wrapped.
  • extension.write is a user permission checked via Gate; API-key requests have no user, so the check is skipped

Path parameters

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

Responses

StatusBody
200Bare JSON object, not data-wrapped

Errors

StatusWhen it happens
403Session user lacks the extension.write permission
404No such extension for this tenant

Add a SIP device to an extension and return its generated secret once; it is never readable again ​

POST/contact-center/extensions/{id}/devices
  • Requires extension.write.
  • Writes a sip_device.provisioned audit entry and re-pushes the FreeSWITCH directory. extension.write is a Gate permission that API-key requests skip (no user)

Path parameters

NameTypeRequiredNotes
idstringyes

Request body

FieldTypeRequiredNotes
namestring | null—max 255 chars
device_typewebrtc · softphone · desk_phone · mobile · ata · null | null—
mac_addressstring | null—max 32 chars
vendorstring | null—max 48 chars
modelstring | null—max 48 chars
bash
curl -X POST "$CALLAGENT_API_URL/contact-center/extensions/01JQ0000000000000000000000/devices" \
  -H "Authorization: Bearer $CALLAGENT_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d '{
       "name": "Support Line",
       "device_type": "webrtc"
     }'
js
const response = await fetch(`${process.env.CALLAGENT_API_URL}/contact-center/extensions/01JQ0000000000000000000000/devices`, {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.CALLAGENT_API_KEY}`,
    Accept: 'application/json',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    "name": "Support Line",
    "device_type": "webrtc"
  }),
});

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') . '/contact-center/extensions/01JQ0000000000000000000000/devices');

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([
    'name' => 'Support Line',
    'device_type' => 'webrtc',
]),
]);

$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']}/contact-center/extensions/01JQ0000000000000000000000/devices",
    headers={"Authorization": f"Bearer {os.environ['CALLAGENT_API_KEY']}"},
    json={
      "name": "Support Line",
      "device_type": "webrtc"
    },
    timeout=30,
)
response.raise_for_status()
data = response.json()["data"]

Responses

StatusBody
200JSON object

Errors

StatusWhen it happens
403Session user lacks the extension.write permission
404No such extension for this tenant
422Validation failed

Revoke a SIP device: deactivate it, soft-delete it, re-push the directory and flush FreeSWITCH's cache ​

DELETE/contact-center/extensions/{id}/devices/{deviceId}
  • Requires extension.write.
  • This response is not data-wrapped.
  • extension.write is a user permission checked via Gate; API-key requests have no user, so the check is skipped

Path parameters

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

Responses

StatusBody
200Bare JSON object, not data-wrapped

Errors

StatusWhen it happens
403Session user lacks the extension.write permission
404No such device on this extension

Issue a new SIP secret for a device, returning it once; the old secret dies on the next directory push ​

POST/contact-center/extensions/{id}/devices/{deviceId}/rotate
  • Requires extension.write.
  • Marks the device unregistered and writes a sip_device.secret_rotated audit entry. extension.write is a Gate permission that API-key requests skip (no user)

Path parameters

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

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']}/contact-center/extensions/01JQ0000000000000000000000/devices/01JQ0000000000000000000000/rotate",
    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
403Session user lacks the extension.write permission
404No such device on this extension

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