Skip to content

FreePBX / Asterisk

The FreePBX adapter is beta-tier . It manages FreePBX 15+ and Asterisk through three independent transports: AMI for real-time call state, ARI for live channel operations, and the FreePBX REST API for configuration. The adapter includes credential encryption, a toll-fraud guard on outbound dial rules, source-IP-gated provisioning, and a shared CircuitBreaker across all three clients.

AreaDetails
ExtensionsList, create, update, delete; password rotation
TrunksSIP/IAX trunk CRUD
Ring groupsCreate, edit, delete; member management
QueuesCreate, edit, delete; agent membership
IVR menusFull IVR tree management
DIDs (inbound routes)Create, edit, delete
VoicemailMailbox config + greeting upload
Active callsLive call list (AMI), hangup channel (AMI)
CDRCall detail records (read)
Config applyEquivalent of the “Apply Config” button

Not supported: fax (no Asterisk fax stack integration), FreePBX Endpoint Manager templates (commercial module only), WebRTC bridging beyond channel-list view.

ChannelProtocolUsed forPort
AMIAsterisk Manager Interface (TCP text)Active calls, real-time event stream, hangup channel, call origination5038
ARIAsterisk REST Interface (HTTP + WebSocket)WebSocket call-event stream; ARI health probe8088
RESTFreePBX Admin REST API (HTTP)Configuration (extensions, trunks, IVR, etc.)443 / 80

All three credentials are stored inline on the PBX record - there is no separate /api/v1/credentials resource for FreePBX.

  • FreePBX 15 or later with the Admin REST API module installed and enabled.
  • AMI user configured in Asterisk (/etc/asterisk/manager.conf or FreePBX AMI Settings).
  • ARI user configured in /etc/asterisk/ari.conf.
  • FreeSDN host IP whitelisted in the FreePBX Admin → Advanced Settings → Allowed Networks (or the source-IP check on the provisioning endpoint will reject requests).

FreePBX does not use the generic /api/v1/credentials store. All three sets of secrets are passed as inline fields when creating (or updating) the PBX record and are encrypted at rest in dedicated Fernet columns on the PBX row (ami_secret_enc, ari_password_enc, web_password_enc). They are never returned by any read endpoint.

FieldPurpose
api_usernameFreePBX web REST API username (usually admin)
api_passwordFreePBX web REST API password
settings.ami_usernameAMI user (falls back to api_username if omitted)
settings.ami_secretAMI secret (stored in ami_secret_enc)
settings.ari_usernameARI user (falls back to api_username if omitted)
settings.ari_passwordARI password (stored in ari_password_enc)

Optionally, for FreePBX 16+ with the Admin REST API module’s M2M application:

FieldPurpose
api_client_idOAuth2 client ID (non-secret, stored in plain column)
api_client_secretOAuth2 client secret (stored in api_client_secret_enc)

When both api_client_id and api_client_secret are supplied the adapter prefers the OAuth2 client-credentials flow over web-session auth.

Terminal window
curl -X POST https://freesdn.example.com/api/v1/voip/pbx \
-H "Cookie: freesdn_access=<token>" \
-H "X-CSRF-Token: <csrf>" \
-H "Content-Type: application/json" \
-d '{
"name": "Main Office PBX",
"pbx_type": "freepbx",
"ip_address": "pbx.example.com",
"api_port": 443,
"site_id": "<site-uuid>",
"api_username": "admin",
"api_password": "<freepbx-admin-password>",
"settings": {
"ami_username": "freesdn_ami",
"ami_secret": "<ami-secret>",
"ari_username": "freesdn_ari",
"ari_password": "<ari-secret>"
}
}'

After creation, test connectivity:

Terminal window
curl -X POST https://freesdn.example.com/api/v1/voip/pbx/test-connection \
-H "Cookie: freesdn_access=<token>" \
-H "X-CSRF-Token: <csrf>" \
-H "Content-Type: application/json" \
-d '{
"pbx_type": "freepbx",
"ip_address": "pbx.example.com",
"api_port": 443,
"api_username": "admin",
"api_password": "<freepbx-admin-password>"
}'

A healthy response has status: "success" and a details object with ami.connected: true, ari.connected: true, and rest.connected: true.

Terminal window
curl https://freesdn.example.com/api/v1/voip/extensions?pbx_id=<pbx-uuid> \
-H "Cookie: freesdn_access=<token>"
Terminal window
curl -X POST https://freesdn.example.com/api/v1/voip/pbx/<pbx-uuid>/extensions \
-H "Cookie: freesdn_access=<token>" \
-H "X-CSRF-Token: <csrf>" \
-H "Content-Type: application/json" \
-d '{
"extension_number": "201",
"display_name": "Alice Smith",
"voicemail_enabled": true
}'
Terminal window
curl https://freesdn.example.com/api/v1/voip/pbx/<pbx-uuid>/active-calls \
-H "Cookie: freesdn_access=<token>"
Terminal window
curl -X POST https://freesdn.example.com/api/v1/voip/pbx/<pbx-uuid>/call/hangup \
-H "Cookie: freesdn_access=<token>" \
-H "X-CSRF-Token: <csrf>" \
-H "Content-Type: application/json" \
-d '{"channel": "<channel-id>"}'

After making config changes through FreeSDN, trigger a background sync (equivalent to clicking “Apply Config” in the FreePBX web UI). The endpoint returns 202 Accepted immediately; watch progress via pbx.sync.* WebSocket events:

Terminal window
curl -X POST https://freesdn.example.com/api/v1/voip/pbx/<pbx-uuid>/sync \
-H "Cookie: freesdn_access=<token>" \
-H "X-CSRF-Token: <csrf>"

The adapter validates outbound call destinations (originate and transfer) against a blocklist of premium-rate and bare international prefixes (e.g. 1900, 011, satellite ranges). Blocked prefixes can be allow-listed per PBX via pbx.settings.allowed_outbound_prefixes. This check fires in the adapter layer (_check_destination_safe in adapter.py), not the staging layer, and applies only to call origination and transfer - not to trunk configuration.

  • PBXPage - PBX list view
  • PBXDetailPage - 11 tabs: Overview, Extensions, Trunks, Ring Groups, Queues, IVR, DIDs, Active Calls, Voicemail, Config, Settings

VoIP module mounts 11 sub-routers:

  • /api/v1/voip/pbx/* - PBX CRUD + test + apply; trunks, queues, IVR, DIDs, active-calls, and config are sub-paths of /pbx/{pbx_id}/
  • /api/v1/voip/extensions/*
  • /api/v1/voip/ring-groups/*
  • /api/v1/voip/call-logs/* - CDR
  • /api/v1/voip/voicemails/*
  • /api/v1/voip/phones/*
  • /api/v1/voip/discovery/*
  • /api/v1/voip/templates/*
  • /api/v1/voip/provisioning/*
  • /api/v1/voip/fleet/*
  • /api/v1/voip/firmware/*
CBRRTSSSRFRG

Dual-gate writes (DG) are not applicable because PBX config writes go directly through the FreePBX REST API (the PBX itself is the authority, not a FreeSDN staging table). Configuration changes go directly to the FreePBX REST API with no intermediate staging table. Configuration writes (extension, trunk, ring group, IVR, DID, queue CRUD) are applied immediately to the FreePBX REST API - they do not call the read-only gate and are unaffected by ADAPTER_READ_ONLY. Real-time operator actions (originate call, hangup, transfer, config reload) pass through the adapter’s _check_write_allowed guard, but the service layer always supplies force=True, which unconditionally bypasses that gate regardless of the ADAPTER_READ_ONLY env var. As a result, ADAPTER_READ_ONLY=true (the default) does not block any write reachable from the HTTP API in normal operation. Changes are applied immediately to the PBX and logged in the FreeSDN audit trail.