Convenzioni
Regole comuni a tutti gli endpoint.
Formato
- Richieste e risposte in JSON, campi in
camelCase, codifica UTF-8. - Date e orari in ISO 8601 UTC (
2026-11-04T10:00:00.000Z); date sempliciAAAA-MM-GG. - Identificativi numerici, salvo dove indicato (es.
linkediddelle chiamate,iddei messaggi in segreteria). - PATCH aggiorna solo i campi inviati; un campo a
nullviene azzerato, un campo assente resta invariato. PUT sostituisce l'oggetto o l'impostazione indicata. - I segreti (password delle linee, PIN) non vengono mai restituiti: le risposte riportano
hasSecret/hasPin. - Valori mostrati una sola volta: password SIP alla creazione di un interno, chiave API alla creazione e rotazione, segreto dei webhook, password iniziale dell'amministratore di una nuova azienda.
Indirizzo base
L'indirizzo predefinito è https://centralino.exovoip.it/api. Negli SDK è un'opzione del costruttore: se l'indirizzo cambia basta aggiornarla.
export EXOPBX_BASE_URL="https://centralino.exovoip.it/api"
curl -s "$EXOPBX_BASE_URL/health"const pbx = new ExoPBX({
apiKey: process.env.EXOPBX_API_KEY,
baseUrl: 'https://centralino.exovoip.it/api', // predefinito: DEFAULT_BASE_URL
});$pbx = new Client([
'apiKey' => getenv('EXOPBX_API_KEY'),
'baseUrl' => 'https://centralino.exovoip.it/api', // predefinito: Defaults::BASE_URL
]);pbx = ExoPBX(base_url="https://centralino.exovoip.it/api") # predefinito: exopbx.DEFAULT_BASE_URLPaginazione
Le liste usano un cursore:
- query
limit(1–200, predefinito 50) ecursor; - risposta
{ "items": [...], "nextCursor": "..." };nextCursorènullsull'ultima pagina; - il cursore è opaco: va passato così com'è, senza interpretarlo.
# Prima pagina
curl -s "https://centralino.exovoip.it/api/v1/cdr?limit=100" -H "X-API-Key: $EXOPBX_API_KEY"
# Pagina successiva: valore nextCursor della risposta precedente
curl -s "https://centralino.exovoip.it/api/v1/cdr?limit=100&cursor=eyJ2IjoxfQ" -H "X-API-Key: $EXOPBX_API_KEY"// Tutte le pagine, una dopo l'altra
for await (const call of pbx.paginate(pbx.cdr.list, { limit: 100 })) {
console.log(call.linkedid);
}
// Una pagina alla volta
const page = await pbx.cdr.list({ limit: 100 });
const next = page.nextCursor ? await pbx.cdr.list({ limit: 100, cursor: page.nextCursor }) : null;foreach ($pbx->paginate([$pbx->cdr, 'list'], ['limit' => 100]) as $call) {
echo $call['linkedid'], PHP_EOL;
}for call in pbx.paginate(pbx.cdr.list, limit=100):
print(call["linkedid"])Idempotenza
Le richieste POST accettano l'header Idempotency-Key (fino a 255 caratteri). Ripetendo la stessa richiesta con la stessa chiave entro 24 ore si ottiene la stessa risposta, senza creare duplicati; con un corpo diverso si riceve 409 IDEMPOTENCY_CONFLICT. Gli SDK inviano una chiave casuale a ogni POST, così i nuovi tentativi automatici sono sicuri.
curl -s -X POST https://centralino.exovoip.it/api/v1/extensions \
-H "X-API-Key: $EXOPBX_API_KEY" -H "Content-Type: application/json" \
-H "Idempotency-Key: crm-utente-4821" \
-d '{"number":"105","name":"Giulia Verdi"}'// L'SDK genera una chiave casuale per ogni POST; per legarla a un dato del proprio sistema:
await pbx.extensions.create(
{ number: '105', name: 'Giulia Verdi' },
{ idempotencyKey: 'crm-utente-4821' },
);$pbx->extensions->create(['number' => '105', 'name' => 'Giulia Verdi'], ['idempotencyKey' => 'crm-utente-4821']);pbx.extensions.create({"number": "105", "name": "Giulia Verdi"}, idempotency_key="crm-utente-4821")Errori
Ogni errore ha lo stesso formato:
{
"error": {
"code": "VALIDATION_ERROR",
"message": "Dati non validi",
"details": [{ "path": ["number"], "message": "Obbligatorio" }]
}
}code è stabile e va usato nel codice; message è un testo leggibile che può cambiare.
| Stato | Codici principali | Significato |
|---|---|---|
| 400 | VALIDATION_ERROR, BAD_REQUEST, TENANT_REQUIRED | Dati non validi o azienda mancante |
| 401 | UNAUTHORIZED, API_KEY_INVALID, API_KEY_EXPIRED, API_KEY_REVOKED | Chiave mancante o non valida |
| 403 | FORBIDDEN, SCOPE_MISSING, IP_NOT_ALLOWED, TENANT_SUSPENDED | Permessi insufficienti |
| 404 | NOT_FOUND | Risorsa inesistente o di un'altra azienda |
| 409 | CONFLICT, IN_USE, LIMIT_REACHED, IDEMPOTENCY_CONFLICT | Duplicato, risorsa in uso, limite del piano |
| 413 / 415 | PAYLOAD_TOO_LARGE, UNSUPPORTED_MEDIA_TYPE | File troppo grande o formato non accettato |
| 429 | RATE_LIMITED | Troppe richieste |
| 501 | NOT_IMPLEMENTED | Funzione non ancora disponibile |
| 503 | SERVICE_UNAVAILABLE | Servizio temporaneamente non raggiungibile |
curl -s -X POST https://centralino.exovoip.it/api/v1/extensions \
-H "X-API-Key: $EXOPBX_API_KEY" -H "Content-Type: application/json" -d '{"number":""}'
# {"error":{"code":"VALIDATION_ERROR","message":"Dati non validi","details":[...]}}import { isExoPBXError } from '@exobyte/exopbx-sdk';
try {
await pbx.extensions.create({ number: '', name: '' });
} catch (err) {
if (isExoPBXError(err, 'VALIDATION_ERROR')) console.log(err.details);
else if (isExoPBXError(err)) console.log(err.status, err.code, err.message, err.requestId);
else throw err;
}use Exobyte\ExoPBX\ApiException;
try {
$pbx->extensions->create(['number' => '', 'name' => '']);
} catch (ApiException $e) {
echo $e->getStatus(), ' ', $e->getErrorCode(), ' ', $e->getMessage(), PHP_EOL;
print_r($e->getDetails());
}from exopbx import ExoPBXError
try:
pbx.extensions.create({"number": "", "name": ""})
except ExoPBXError as err:
print(err.status, err.code, err.message, err.details, err.request_id)Limiti di richieste
Ogni chiave può inviare fino a 600 richieste al minuto (valore predefinito della piattaforma). Oltre il limite si riceve 429 RATE_LIMITED con l'header Retry-After (secondi di attesa) e details.retryAfter. Gli SDK attendono e riprovano automaticamente.
# HTTP/1.1 429 Too Many Requests
# Retry-After: 12
# {"error":{"code":"RATE_LIMITED","message":"Limite di richieste superato per questa chiave","details":{"retryAfter":12}}}// Nuovi tentativi automatici su 429 (rispettando Retry-After) e su errori temporanei
const pbx = new ExoPBX({ apiKey: process.env.EXOPBX_API_KEY, maxRetries: 3, timeoutMs: 20_000 });$pbx = new Client(['apiKey' => getenv('EXOPBX_API_KEY'), 'maxRetries' => 3, 'timeout' => 20]);pbx = ExoPBX(max_retries=3, timeout=20)Altri limiti: corpo JSON fino a 1 MB; file audio secondo i limiti indicati nella guida Messaggi vocali; massimo 10 webhook per azienda.
Cache e header utili
- Le risposte API non vengono messe in cache (
Cache-Control: no-store). - Indica sempre
Accept: application/json(gli SDK lo fanno per te). - In caso di assistenza, comunica data e ora della richiesta, l'endpoint e il codice di errore.
Endpoint non ancora presenti nell'SDK
Se l'API aggiunge un endpoint prima che tu aggiorni l'SDK, puoi chiamarlo con la richiesta generica:
curl -s "https://centralino.exovoip.it/api/v1/extensions?limit=10" -H "X-API-Key: $EXOPBX_API_KEY"// Per un endpoint appena aggiunto all'API e non ancora nella versione dell'SDK in uso
const data = await pbx.request({ method: 'GET', path: '/v1/extensions', query: { limit: 10 } });$data = $pbx->request('GET', '/v1/extensions', ['limit' => 10]);data = pbx.request("GET", "/v1/extensions", query={"limit": 10})