Authentication
Every request (except GET /health) requires an API key. The control panel uses the same API with the user's session.
API keys
Format: exo_live_<id><segreto> or exo_test_<id><segreto>. Two equivalent ways to send it:
# Entrambe le forme sono equivalenti
curl -s https://centralino.exovoip.it/api/v1/extensions -H "X-API-Key: $EXOPBX_API_KEY"
curl -s https://centralino.exovoip.it/api/v1/extensions -H "Authorization: Bearer $EXOPBX_API_KEY"// L'SDK invia la chiave nell'header X-API-Key
const pbx = new ExoPBX({ apiKey: process.env.EXOPBX_API_KEY });$pbx = new Client(['apiKey' => getenv('EXOPBX_API_KEY')]);import os
pbx = ExoPBX(api_key=os.environ["EXOPBX_API_KEY"])Each key has:
| Property | Meaning |
|---|---|
| Scopes | Granted permissions (e.g. extensions:read, calendars:write). |
| Allowed IPs | Optional: only these addresses or networks (CIDR). From other IPs: 403 IP_NOT_ALLOWED. |
| Expiry | Optional. After expiry: 401 API_KEY_EXPIRED. |
| Tenant | Keys created from a company's control panel operate only on that tenant. |
The secret is stored as a hash (SHA-256): Exobyte cannot recover it. If you lose it, rotate the key.
Creating, rotating and revoking keys via the API
Requires the api_keys:read / api_keys:write scopes.
curl -s -X POST https://centralino.exovoip.it/api/v1/api-keys \
-H "X-API-Key: $EXOPBX_API_KEY" -H "Content-Type: application/json" \
-d '{"name":"Integrazione CRM","scopes":["extensions:read","cdr:read","live:read"],
"allowedIps":["203.0.113.10"],"expiresAt":"2027-12-31T23:00:00.000Z"}'const { apiKey, secret } = await pbx.apiKeys.create({
name: 'Integrazione CRM',
scopes: ['extensions:read', 'cdr:read', 'live:read'],
allowedIps: ['203.0.113.10'],
expiresAt: '2027-12-31T23:00:00.000Z',
});
// secret è la chiave completa: mostrata solo ora, salvarla in un posto sicuro$out = $pbx->apiKeys->create([
'name' => 'Integrazione CRM',
'scopes' => ['extensions:read', 'cdr:read', 'live:read'],
'allowedIps' => ['203.0.113.10'],
'expiresAt' => '2027-12-31T23:00:00.000Z',
]);
$secret = $out['secret']; // mostrata una sola voltaout = pbx.api_keys.create({
"name": "Integrazione CRM",
"scopes": ["extensions:read", "cdr:read", "live:read"],
"allowedIps": ["203.0.113.10"],
"expiresAt": "2027-12-31T23:00:00.000Z",
})
secret = out["secret"] # mostrata una sola voltaRotation creates a new key with the same permissions; the previous one stays valid for the overlap period you specify, so you can update your systems without downtime.
# La vecchia chiave resta valida per 24 ore, poi scade
curl -s -X POST https://centralino.exovoip.it/api/v1/api-keys/12/rotate \
-H "X-API-Key: $EXOPBX_API_KEY" -H "Content-Type: application/json" \
-d '{"overlapHours":24}'
curl -s -X POST https://centralino.exovoip.it/api/v1/api-keys/12/revoke -H "X-API-Key: $EXOPBX_API_KEY"const { secret } = await pbx.apiKeys.rotate(12, { overlapHours: 24 });
await pbx.apiKeys.revoke(12); // revoca immediata$secret = $pbx->apiKeys->rotate(12, ['overlapHours' => 24])['secret'];
$pbx->apiKeys->revoke(12);secret = pbx.api_keys.rotate(12, {"overlapHours": 24})["secret"]
pbx.api_keys.revoke(12)| Request | Scope | SDK method |
|---|---|---|
GET /v1/api-keys | api_keys:read | apiKeys.list() · py: api_keys.list() |
POST /v1/api-keys | api_keys:write | apiKeys.create() · py: api_keys.create() |
GET /v1/api-keys/:id | api_keys:read | apiKeys.get() · py: api_keys.get() |
PATCH /v1/api-keys/:id | api_keys:write | apiKeys.update() · py: api_keys.update() |
POST /v1/api-keys/:id/rotate | api_keys:write | apiKeys.rotate() · py: api_keys.rotate() |
POST /v1/api-keys/:id/revoke | api_keys:write | apiKeys.revoke() · py: api_keys.revoke() |
GET /v1/api-keys/:id/usage | api_keys:read | apiKeys.usage() · py: api_keys.usage() |
GET /v1/api-keys/:id/usage/stats | api_keys:read | apiKeys.usageStats() · py: api_keys.usage_stats() |
Scopes
Format <resource>:<action>. <resource>:* grants all actions on the resource, * grants all tenant scopes.
| Scope | What it allows |
|---|---|
extensions:read / extensions:write | Extensions, SIP credentials, forwarding, feature codes |
trunks:read / trunks:write | The tenant's SIP trunks |
numbers:read / numbers:write | Numbers and their destination |
inbound_routes:read / inbound_routes:write | Inbound routes |
outbound_routes:read / outbound_routes:write | Outbound routes and blocks |
calendars:read / calendars:write | Business hours, holidays, exceptions, overrides |
ivrs:read / ivrs:write | IVR menus |
ring_groups:read / ring_groups:write | Ring groups |
queues:read / queues:write | Queues and agents |
media:read / media:write | Voice prompts and music on hold |
recordings:read / recordings:write | Recordings, transcripts, rules and settings |
voicemail:read / voicemail:write | Voicemail |
contacts:read / contacts:write | Phonebook |
cdr:read | Call log |
live:read | Real-time status and events |
calls:control | Call control |
notifications:read / notifications:write | Email and Telegram notifications |
webhooks:read / webhooks:write | Webhooks |
api_keys:read / api_keys:write | API keys |
Platform scopes (Exobyte global keys only): tenants:*, security:*, system:*. A tenant key can never obtain them, not even with *.
Control panel roles
Control panel users have permissions based on their role:
| Role | Permissions |
|---|---|
| Platform administrator | Everything, across all tenants |
| Company administrator | All tenant scopes |
| Supervisor | Read, real-time and call control |
| User | Own extension only: details, voicemail, call log, real time |
Global keys and X-Tenant-Id
Global keys (created by the platform administrator) operate across all tenants:
- on tenant endpoints the
X-Tenant-Idheader is required (otherwise400 TENANT_REQUIRED); - on endpoints with a global view (trunks, numbers, call log, keys, real time, webhooks), omitting the header returns data for all tenants, each with its own
tenantId; - on SSE and WebSocket streams the tenant can also be specified with
?tenantId=.
For tenant keys the header is ignored; a value other than the key's own tenant returns 403.
# Chiave globale (piattaforma): azienda indicata con X-Tenant-Id
curl -s https://centralino.exovoip.it/api/v1/extensions \
-H "X-API-Key: $EXOPBX_API_KEY" \
-H "X-Tenant-Id: 3"// Per tutto il client...
const acme = new ExoPBX({ apiKey: process.env.EXOPBX_API_KEY, tenantId: 3 });
// ...oppure per una singola richiesta
await pbx.extensions.list({ limit: 50 }, { tenantId: 3 });$acme = new Client(['apiKey' => getenv('EXOPBX_API_KEY'), 'tenantId' => 3]);
$pbx->extensions->list(limit: 50, options: ['tenantId' => 3]);acme = ExoPBX(tenant_id=3)
pbx.extensions.list(limit=50, tenant_id=3)Suspended tenants
While a tenant is suspended its keys work in read-only mode: writes return 403 TENANT_SUSPENDED. When a tenant is deactivated, access is denied.
Best practices
- One key per integration, with only the scopes it needs.
- Restrict IPs when the integration runs on servers with a fixed address.
- Rotate keys periodically and revoke unused ones immediately.
- Never put keys in public web pages or distributed apps: use them server-side only.
- Monitor each key's usage from the control panel (requests, errors, last access).
