Docs/Reference

API reference

Everything the panel does, the REST API (v1) can do — extensions are API entities. Plus the separate JSON API the Android app talks to.

Keys

Issue a key in the panel (API Keys) or through the API itself. The raw key is shown once at creation; only its SHA-256 is stored. Keys are Bearer tokens:

KEY="pbx_..."
AUTH="Authorization: Bearer $KEY"
  • Admin keys have full access.
  • User keys can only read their own extension: GET /api/v1/extensions, GET /api/v1/extensions/{own} and GET /api/v1/me.
  • Revoke a key at any time on the same page. The edge's security alerts use their own key — see Edge server.

Extensions

# list extensions
curl -s -H "$AUTH" http://127.0.0.1:8001/api/v1/extensions

# create one (sip_secret + login password auto-generated, returned once)
curl -s -H "$AUTH" -H 'Content-Type: application/json' \
  -d '{"username":"alice","exten":"8802","display_name":"Alice"}' \
  http://127.0.0.1:8001/api/v1/extensions

# read / patch / delete
curl -s -H "$AUTH" http://127.0.0.1:8001/api/v1/extensions/8802
curl -s -H "$AUTH" -H 'Content-Type: application/json' -X PATCH \
  -d '{"display_name":"Alice A.","user_record":true}' \
  http://127.0.0.1:8001/api/v1/extensions/8802
curl -s -H "$AUTH" -X DELETE http://127.0.0.1:8001/api/v1/extensions/8802

# rotate the SIP secret (returned once) / reset the login password
curl -s -H "$AUTH" -X POST http://127.0.0.1:8001/api/v1/extensions/8802/rotate-secret
curl -s -H "$AUTH" -H 'Content-Type: application/json' \
  -d '{"password":"new-strong-password"}' \
  http://127.0.0.1:8001/api/v1/extensions/8802/reset-password

# keys: list / issue / revoke (admin only)
curl -s -H "$AUTH" http://127.0.0.1:8001/api/v1/api-keys
curl -s -H "$AUTH" -H 'Content-Type: application/json' \
  -d '{"username":"alice","name":"provisioning"}' \
  http://127.0.0.1:8001/api/v1/api-keys
curl -s -H "$AUTH" -X DELETE http://127.0.0.1:8001/api/v1/api-keys/3

Rules

  • sip_secret and the login password are returned once — at create, rotate or reset time — and never appear in GET responses.
  • Mutations regenerate pjsip.conf and reload PJSIP through a root-owned wrapper, sudo-scoped to the service user. The response reports "applied": true/false; on false, retry with POST /system/reload.
  • PATCH accepts display_name, vm_email, record_admin, user_record, max_messages and enabled. Identity fields (username, exten, sip_username, role) are set at creation — delete and recreate to change them.

Live status

The dashboard is a thin consumer of these endpoints — the same JSON any external tool can use. pbx-brain is the source of truth for calls (from its Asterisk events); Asterisk is the source for registrations. User keys only see their own device and calls.

# registered SIP devices (deduped per real device)
curl -s -H "$AUTH" http://127.0.0.1:8001/api/v1/devices

# live calls, straight from the brain
curl -s -H "$AUTH" http://127.0.0.1:8001/api/v1/calls/live

# panel sign-in history (admin)
curl -s -H "$AUTH" 'http://127.0.0.1:8001/api/v1/signins?limit=50&failed_only=1'

Panel sign-ins, success and failure, are audited to the database. The dashboard renders devices, live calls and recent sign-ins from these endpoints and refreshes every 10 seconds.

Safety: kill switch and safety lock

# status: {"kill_switch":..,"safety_lock":..,"lock_mode":"off"|"full"|"admin"}
curl -s -H "$AUTH" http://127.0.0.1:8001/api/v1/safety

# safety lock, full (default): routine mutations on the admin panel, the API
# AND My Phone are rejected with 403
curl -s -H "$AUTH" -H 'Content-Type: application/json' \
  -d '{"locked":true,"mode":"full"}' http://127.0.0.1:8001/api/v1/safety/lock

# safety lock, admin only: admin panel + API are frozen, but users can keep
# using My Phone (send texts, change their settings, voicemail, ...)
curl -s -H "$AUTH" -H 'Content-Type: application/json' \
  -d '{"locked":true,"mode":"admin"}' http://127.0.0.1:8001/api/v1/safety/lock

# release either lock
curl -s -H "$AUTH" -H 'Content-Type: application/json' \
  -d '{"locked":false}' http://127.0.0.1:8001/api/v1/safety/lock

# kill switch: hangs up all active calls, renders an empty pjsip.conf
# (no registrations, no trunk) and reloads. Bypasses the safety lock.
# The brain also hangs up any new channel while the switch is on.
curl -s -H "$AUTH" -H 'Content-Type: application/json' \
  -d '{"engaged":true}' http://127.0.0.1:8001/api/v1/safety/kill-switch
# release when the emergency is over (config re-renders, trunk re-registers)
curl -s -H "$AUTH" -H 'Content-Type: application/json' \
  -d '{"engaged":false}' http://127.0.0.1:8001/api/v1/safety/kill-switch

Both are also on the dashboard: a status banner with Kill switch, Lock admin / Lock everything, and Unlock buttons. See Security for when to use them.

My Phone app API (Android)

The Android app uses its own long-lived sign-in. Device tokens (pbxd_...) only work for the signed-in user's own My Phone data — never for admin pages.

# sign in (or: POST /api/v1/app/pair {"code":"ABCD2345"} with a code from My Phone > App)
curl -s -H 'Content-Type: application/json' \
  -d '{"username":"alice","password":"...","device_name":"Pixel 8"}' \
  https://myphone.example.com/api/v1/app/login        # -> {"token":"pbxd_...","ttl_days":60,...}
AUTH="Authorization: Bearer pbxd_..."

curl -s -H "$AUTH" https://myphone.example.com/api/v1/me/overview
curl -s -H "$AUTH" https://myphone.example.com/api/v1/me/threads
curl -s -H "$AUTH" https://myphone.example.com/api/v1/me/threads/8801
curl -s -H "$AUTH" -d to=8801 -d body=hello https://myphone.example.com/api/v1/me/messages/send
curl -s -H "$AUTH" -d ids=12 -d ids=13 -d convs=8801 https://myphone.example.com/api/v1/me/messages/delete

The Android app itself signs in with pairing codes only; /app/login stays available for other API clients. Tokens expire 60 days after last use, and a password change signs out every other phone.

Reads (GET)Changes (POST, form fields like the web forms)
/me/overview, /me/threads, /me/threads/{peer}?before=&limit=, /me/messages/since?after=/me/messages/send, /me/messages/delete (ids, convs, or mode=all)
/me/calls?view=all|missed|hidden, /me/voicemail?folder=Old, /me/voicemail/{id}/audio/me/calls/hide (ids; unhide=1 to show again), /me/calls/seen, /me/voicemail/delete, /me/voicemail/{id}/heard, /me/voicemail/{id}/move
/me/recordings, /me/recordings/{id}/audio/me/recordings/delete
/me/settings, /me/devices/me/settings/calls, /me/settings/prefs, /me/settings/messaging, /me/settings/password, /me/dnd, /me/block, /me/report

Changes answer {"ok": bool, "code": "<key>", "message": "<text>"} — HTTP 400 on a refused change, 423 when the safety lock is on. Also: DELETE /me/devices/{id}, PUT /me/push {"endpoint":"https://..."} (UnifiedPush), and POST /api/v1/app/logout.

Details worth knowing

  • Calls can't be deleted by users, only hidden (per user). Hidden calls drop out of the all/missed views and are listed under view=hidden until the 90-day call-history purge removes them.
  • /me/overview returns missed_new — the missed calls this phone hasn't seen and the user hasn't hidden. POST /me/calls/seen (the app calls it when Calls is opened) resets it to 0.
  • Instant notifications: GET /me/wait?msg=&call=&vm=&timeout=50 is a long poll. It answers as soon as there is a text, an unhidden missed call or an Inbox voicemail newer than the IDs given (checked every 2 s), or after timeout seconds (max 55 — under Cloudflare's 100 s limit), with the newest IDs and changed.
  • Calling: GET /me/sip gives the app's built-in phone what it needs to call as the user's own extension through the edge, on the extension's app line: enabled (false, with a reason, when the extension, its phone password or the Admin → Network domain is missing — the password is then left out), username (<sip_username>-app, a second endpoint with the same password and caller ID that pbx-brain rings alongside the main line; main_username is the desk-phone login), password, domain, port, transport (tls), srtp (sdes) and codecs.
  • Pairing QR codes hold myphone://pair?s=<server>&c=<code> (rendering them needs the segno package, which deploy.sh installs). See Android app for how pairing works.