Autenticazione
Ogni richiesta (tranne GET /health) richiede una chiave API. Il pannello usa le stesse API con la sessione dell'utente.
Chiavi API
Formato: exo_live_<id><segreto> oppure exo_test_<id><segreto>. Due modi equivalenti di inviarla:
# 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"])Ogni chiave ha:
| Proprietà | Significato |
|---|---|
| Scope | Permessi concessi (es. extensions:read, calendars:write). |
| IP consentiti | Facoltativo: solo questi indirizzi o reti (CIDR). Da altri IP: 403 IP_NOT_ALLOWED. |
| Scadenza | Facoltativa. Dopo la scadenza: 401 API_KEY_EXPIRED. |
| Azienda | Le chiavi create dal pannello di un'azienda operano solo su quella azienda. |
Il segreto è salvato come impronta (SHA-256): Exobyte non può recuperarlo. Se lo perdi, ruota la chiave.
Creazione, rotazione e revoca via API
Servono gli scope api_keys:read / api_keys:write.
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 voltaLa rotazione crea una nuova chiave con gli stessi permessi; la precedente resta valida per il periodo di sovrapposizione indicato, così puoi aggiornare i sistemi senza interruzioni.
# 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)| Descrizione | Richiesta | Scope | Metodo SDK |
|---|---|---|---|
| Elenco chiavi API | GET /v1/api-keys | api_keys:read | apiKeys.list() · py: api_keys.list() |
| Crea chiave (segreto mostrato una volta) | POST /v1/api-keys | api_keys:write | apiKeys.create() · py: api_keys.create() |
| Dettaglio chiave | GET /v1/api-keys/:id | api_keys:read | apiKeys.get() · py: api_keys.get() |
| Modifica nome, scope, IP, scadenza | PATCH /v1/api-keys/:id | api_keys:write | apiKeys.update() · py: api_keys.update() |
| Rotazione con periodo di sovrapposizione | POST /v1/api-keys/:id/rotate | api_keys:write | apiKeys.rotate() · py: api_keys.rotate() |
| Revoca immediata | POST /v1/api-keys/:id/revoke | api_keys:write | apiKeys.revoke() · py: api_keys.revoke() |
| Log di utilizzo | GET /v1/api-keys/:id/usage | api_keys:read | apiKeys.usage() · py: api_keys.usage() |
| Consumi giornalieri (30 giorni) | GET /v1/api-keys/:id/usage/stats | api_keys:read | apiKeys.usageStats() · py: api_keys.usage_stats() |
Scope
Formato <risorsa>:<azione>. <risorsa>:* concede tutte le azioni della risorsa, * tutti gli scope aziendali.
| Scope | Cosa permette |
|---|---|
extensions:read / extensions:write | Interni, credenziali SIP, deviazioni, codici funzione |
trunks:read / trunks:write | Linee SIP dell'azienda |
numbers:read / numbers:write | Numeri e loro destinazione |
inbound_routes:read / inbound_routes:write | Regole in entrata |
outbound_routes:read / outbound_routes:write | Regole di uscita e blocchi |
calendars:read / calendars:write | Orari, festività, eccezioni, forzature |
ivrs:read / ivrs:write | Menu vocali |
ring_groups:read / ring_groups:write | Gruppi di squillo |
queues:read / queues:write | Code e operatori |
media:read / media:write | Messaggi vocali e musica d'attesa |
recordings:read / recordings:write | Registrazioni, trascrizioni, regole e impostazioni |
voicemail:read / voicemail:write | Segreteria |
contacts:read / contacts:write | Rubrica |
cdr:read | Registro chiamate |
live:read | Stato in tempo reale ed eventi |
calls:control | Controllo chiamate |
notifications:read / notifications:write | Notifiche email e Telegram |
webhooks:read / webhooks:write | Webhook |
api_keys:read / api_keys:write | Chiavi API |
Scope di piattaforma (solo chiavi globali Exobyte): tenants:*, security:*, system:*. Una chiave aziendale non può mai ottenerli, nemmeno con *.
Ruoli del pannello
Gli utenti del pannello hanno permessi in base al ruolo:
| Ruolo | Permessi |
|---|---|
| Amministratore piattaforma | Tutto, su tutte le aziende |
| Amministratore azienda | Tutti gli scope aziendali |
| Supervisore | Lettura, tempo reale e controllo chiamate |
| Utente | Solo il proprio interno: dettagli, segreteria, registro chiamate, tempo reale |
Chiavi globali e X-Tenant-Id
Le chiavi globali (create dall'amministratore della piattaforma) operano su tutte le aziende:
- sugli endpoint di un'azienda l'header
X-Tenant-Idè obbligatorio (altrimenti400 TENANT_REQUIRED); - sugli endpoint con vista globale (linee, numeri, registro chiamate, chiavi, tempo reale, webhook) senza header si ottengono i dati di tutte le aziende, ognuno con il proprio
tenantId; - sui flussi SSE e WebSocket l'azienda si può indicare anche con
?tenantId=.
Per le chiavi aziendali l'header è ignorato; un valore diverso dalla propria azienda restituisce 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)Aziende sospese
Con l'azienda sospesa le chiavi aziendali funzionano in sola lettura: le scritture restituiscono 403 TENANT_SUSPENDED. Con l'azienda disattivata l'accesso è negato.
Buone pratiche
- Una chiave per integrazione, con i soli scope necessari.
- Limita gli IP quando l'integrazione gira su server con indirizzo fisso.
- Ruota le chiavi periodicamente e revoca subito quelle non più usate.
- Non inserire chiavi in pagine web pubbliche o app distribuite: usale solo lato server.
- Controlla l'utilizzo di ogni chiave dal pannello (richieste, errori, ultimi accessi).
