Packages
Packages are ExoVoIP's commercial plans: each package sets what a company's phone system includes. The platform administrator creates packages, assigns one to every company and can set exceptions for a single company. The effective limits are the package's, with the company's exceptions replacing the matching items.
Package items
| Field | Meaning | When exceeded |
|---|---|---|
maxExt | Available extensions (0 = none) | POST /v1/extensions returns 409 LIMIT_REACHED |
maxCalls | Simultaneous external calls, inbound and outbound | The phone system rejects calls over the limit |
maxTrunks | The company's own lines (shared platform lines are not counted) | POST /v1/trunks returns 409 LIMIT_REACHED |
maxNumbers | Numbers assigned to the company | Creating and assigning numbers return 409 LIMIT_REACHED |
maxPrompts | Voice messages: messages, announcements, music and alerts (excluding forwarding alerts) | POST /v1/prompts and uploads return 409 LIMIT_REACHED |
maxStorageMb | Maximum recording space (MB) | The space chosen by the company (quotaMb) cannot exceed it |
aiMonthlyMinutes | AI transcription minutes per month (recordings and voicemail) | New transcriptions for the month are not performed |
maxAppDevices | ExoVoIP app devices per extension | Activating a new device returns 409 LIMIT_REACHED |
plugins | Included extra features | Features not included return 403 PLUGIN_REQUIRED |
null means unlimited (allowed for maxTrunks, maxNumbers, maxPrompts, maxStorageMb and aiMonthlyMinutes). The 409 LIMIT_REACHED error reports the item (limit), the maximum (max) and, where useful, the current usage (used) in details.
Company limits and usage
GET /v1/tenants/current/limits (scope extensions:read) returns the package of the key's company, the effective limits in limits and the usage in usage: extensions, lines, numbers, voice messages, recording space used (MB) and transcription minutes used in the current month.
# Limiti del pacchetto e utilizzo dell'azienda della chiave
curl -s https://centralino.exovoip.it/api/v1/tenants/current/limits -H "X-API-Key: $EXOPBX_API_KEY"
# Oltre un limite
# HTTP/1.1 409 Conflict
# {"error":{"code":"LIMIT_REACHED","message":"Il pacchetto comprende al massimo 10 messaggi vocali","details":{"limit":"prompts","max":10,"used":10}}}import { isExoPBXError } from '@exobyte/exopbx-sdk';
const { limits, usage } = await pbx.tenants.currentLimits();
console.log(`Interni: ${usage.extensions} su ${limits.maxExt}`);
try {
await pbx.prompts.createTts({ name: 'Benvenuto', text: 'Benvenuti!', kind: 'prompt' });
} catch (err) {
if (isExoPBXError(err, 'LIMIT_REACHED')) {
const { limit, max } = err.details as { limit: string; max: number };
console.log(`Limite del pacchetto raggiunto (${limit}: ${max})`);
} else throw err;
}use Exobyte\ExoPBX\ApiException;
$out = $pbx->tenants->currentLimits();
echo 'Interni: ', $out['usage']['extensions'], ' su ', $out['limits']['maxExt'], PHP_EOL;
try {
$pbx->prompts->createTts(['name' => 'Benvenuto', 'text' => 'Benvenuti!', 'kind' => 'prompt']);
} catch (ApiException $e) {
if ($e->getErrorCode() !== 'LIMIT_REACHED') throw $e;
echo 'Limite raggiunto: ', $e->getDetails()['limit'], PHP_EOL;
}from exopbx import ExoPBXError
out = pbx.tenants.current_limits()
print("Interni:", out["usage"]["extensions"], "su", out["limits"]["maxExt"])
try:
pbx.prompts.create_tts({"name": "Benvenuto", "text": "Benvenuti!", "kind": "prompt"})
except ExoPBXError as err:
if err.code != "LIMIT_REACHED":
raise
print("Limite raggiunto:", err.details["limit"])Managing packages (platform administrator)
The packages.* endpoints are reserved for the platform administrator (scopes tenants:read / tenants:write).
POST /v1/packagescreates a package with name, description and every item inlimits;isDefault: truemakes it the package offered to new companies (only one at a time).PATCH /v1/packages/:idchanges name, description and single items: the new limits apply right away to every company on the package, except items the company has as an exception.DELETE /v1/packages/:iddeletes an unassigned package; if it is assigned to companies or offered to new companies it returns409 IN_USE.
On installation the Standard package exists (50 extensions, 10 simultaneous calls, no other limits, no extra features): existing companies receive it and keep their previous limits as exceptions.
A company's package and exceptions
GET /v1/tenants/:id/package returns the company's package, exceptions (overrides), effective limits and usage. PUT /v1/tenants/:id/package assigns the package and, with overrides, replaces the exceptions ({} removes them all; without overrides the current ones stay). Exceptions equal to the package are removed automatically.
PUT /v1/tenants/:id/limits (extensions and calls), PUT /v1/tenants/:id/plugins (extra features) and aiMonthlyMinutes in PATCH /v1/tenants/:id also save company exceptions. Simultaneous calls and extra features apply to the phone system right away.
# Solo amministratore della piattaforma (chiave globale)
curl -s -X POST https://centralino.exovoip.it/api/v1/packages -H "X-API-Key: $EXOPBX_API_KEY" \
-H "Content-Type: application/json" -d '{
"name": "Professional",
"limits": {"maxExt": 100, "maxCalls": 20, "maxTrunks": 2, "maxNumbers": 10,
"maxPrompts": null, "maxStorageMb": 10240, "aiMonthlyMinutes": 300,
"maxAppDevices": 5, "plugins": ["recording"]}
}'
# Assegna il pacchetto all'azienda 3 con un'eccezione: 30 chiamate contemporanee
curl -s -X PUT https://centralino.exovoip.it/api/v1/tenants/3/package -H "X-API-Key: $EXOPBX_API_KEY" \
-H "Content-Type: application/json" -d '{"packageId": 2, "overrides": {"maxCalls": 30}}'const { package: pro } = await pbx.packages.create({
name: 'Professional',
limits: {
maxExt: 100,
maxCalls: 20,
maxTrunks: 2,
maxNumbers: 10,
maxPrompts: null,
maxStorageMb: 10240,
aiMonthlyMinutes: 300,
maxAppDevices: 5,
plugins: ['recording'],
},
});
const view = await pbx.tenants.setPackage(3, { packageId: pro.id, overrides: { maxCalls: 30 } });
console.log(view.limits.maxCalls, view.usage.extensions);
// Nuovi limiti del pacchetto: valgono subito per tutte le sue aziende
await pbx.packages.update(pro.id, { limits: { maxExt: 120 } });$out = $pbx->packages->create([
'name' => 'Professional',
'limits' => [
'maxExt' => 100, 'maxCalls' => 20, 'maxTrunks' => 2, 'maxNumbers' => 10,
'maxPrompts' => null, 'maxStorageMb' => 10240, 'aiMonthlyMinutes' => 300,
'maxAppDevices' => 5, 'plugins' => ['recording'],
],
]);
$view = $pbx->tenants->setPackage(3, [
'packageId' => $out['package']['id'],
'overrides' => ['maxCalls' => 30],
]);out = pbx.packages.create({
"name": "Professional",
"limits": {
"maxExt": 100, "maxCalls": 20, "maxTrunks": 2, "maxNumbers": 10,
"maxPrompts": None, "maxStorageMb": 10240, "aiMonthlyMinutes": 300,
"maxAppDevices": 5, "plugins": ["recording"],
},
})
view = pbx.tenants.set_package(3, {"packageId": out["package"]["id"], "overrides": {"maxCalls": 30}})Endpoints
| Request | Scope | SDK method |
|---|---|---|
GET /v1/packages | tenants:read | packages.list() |
POST /v1/packages | tenants:write | packages.create() |
GET /v1/packages/:id | tenants:read | packages.get() |
PATCH /v1/packages/:id | tenants:write | packages.update() |
DELETE /v1/packages/:id | tenants:write | packages.delete() |