Platform
ScaiWave ScaiGrid ScaiCore ScaiBot ScaiDrive ScaiKey Models Tools & Services
Solutions
Organisations Developers Internet Service Providers Managed Service Providers AI-in-a-Box
Resources
Support Documentation Blog Downloads
Company
About Research Careers Investment Opportunities Contact
Log in

API Reference

ScaiDial exposes three HTTP surfaces:

  • Admin (/v1/modules/scaidial/) — tenant-wide configuration and observability. Requires module permissions documented in permissions.
  • Portal (/v1/modules/scaidial/me/) — end-user self-service. No special permission; per-row access is gated by ExtensionGrant.
  • Internal (/v1/modules/scaidial/sip/...) — called by livekit-sip with a shared secret. Not exposed to callers; documented for completeness in the integration guide.

All routes return the ScaiGrid envelope: {"data": ..., "status": "ok"} or {"error": {"code": ..., "message": ..., "details": ...}}.


Trunks#

GET /trunks#

List trunks for the caller's tenant.

POST /trunks#

Create a trunk. Body:

Field Type Notes
name string Operator-visible label
sip_server_address string FQDN or IP of the carrier's SIP server
sip_server_port int Usually 5060 (UDP/TCP) or 5061 (TLS)
sip_transport string udp / tcp / tls
sip_auth_mode string credentials or ip
sip_auth_username string When mode=credentials
sip_auth_password string When mode=credentials
sip_inbound_numbers string[] E.164 numbers the carrier delivers to this trunk
sip_media_encryption string disabled / allowed / required
caller_id_e164 string Default outbound caller-ID

Returns the created trunk with sync_status="pending". Poll until synced (or error).

PATCH /trunks/{id}#

Update a trunk. Same body as POST; all fields optional. Changes trigger a re-sync.

DELETE /trunks/{id}#

Delete a trunk. Removes the registration on livekit-sip. Fails if any DID references the trunk.

POST /trunks/{id}/resync#

Force a re-sync after an error. Useful when you've fixed credentials on the carrier side.

POST /trunks/probe#

Test a trunk's connectivity without persisting it. Body is the same shape as POST. Returns {"ok": bool, "detail": string}.


DIDs#

GET /dids#

List DIDs for the caller's tenant.

POST /dids#

Create a DID. Body:

Field Type Notes
e164 string E.164 number
trunk_id UUID Which trunk delivers this number
dialplan_id UUID Which dialplan handles incoming calls
enabled bool Default true

PATCH /dids/{id}#

Partial update. enabled is the toggle the UI flips inline.

DELETE /dids/{id}#

Delete a DID.


Extensions#

GET /extensions#

List extensions for the caller's tenant.

POST /extensions#

Create an extension. Body:

Field Type Notes
number string Internal number (e.g. 1001)
type string wave / bot / voicemail / external / sip_endpoint / desk_queue
target_ref string For wave: user ID; for bot: bot ID; for voicemail: box ID; for external: E.164; for sip_endpoint: SIP URI
display_name string Operator-visible label
ring_timeout_s int Default 30; max 600
timeout_action string What happens on no-answer: voicemail / ring_extension / hangup
timeout_forward_to string When timeout_action=ring_extension, the next extension's number
outbound_caller_id_e164 string Override the trunk's default

PATCH /extensions/{id}#

Partial update.

DELETE /extensions/{id}#

Delete an extension. Fails if any dialplan rule references it.

GET /extensions/{id}/grants#

List grants (who has access to this extension).

POST /extensions/{id}/grants#

Create a grant.

Field Type Notes
user_id UUID Either this or group_id
group_id UUID Either this or user_id
role string owner / manage / answer / observe

DELETE /extensions/{id}/grants/{grant_id}#

Remove a grant.


Dialplans#

GET /dialplans#

List dialplans for the caller's tenant.

POST /dialplans#

Create a dialplan. Body: {"name": string}.

PATCH /dialplans/{id}#

Rename a dialplan.

DELETE /dialplans/{id}#

Delete. Fails if any DID references it.

GET /dialplans/{id}/rules#

List rules for a dialplan, ordered by priority.

POST /dialplans/{id}/rules#

Create a rule. Body:

Field Type Notes
priority int Lower fires first; 0-999
match_type string See dialplans
match_params object Per-type — see dialplans concepts
action_type string See dialplans
action_params object Per-type — see dialplans concepts
enabled bool Default true

PATCH /dialplans/{id}/rules/{rule_id}#

Partial update.

DELETE /dialplans/{id}/rules/{rule_id}#

Delete a rule.

GET /dialplan-pickers#

One fetch, multiple lists — used by the visual builder. Returns {match_types, action_types, dids, extensions, bots, users, voicemail_boxes}.


Active calls#

GET /calls/active#

List calls in state ringing, active, or held. Each call includes its current legs.

GET /calls/{id}#

Single call detail.

GET /calls/history#

Paginated history of ended calls. Filters:

  • extension_id — calls where any leg touched this extension
  • from_e164 — substring match on caller
  • end_reason — exact match
  • started_after / started_before — ISO-8601
  • limit — capped at 500
  • cursor_started_at — keyset pagination

Returns {items: [...], next_cursor_started_at: string | null}.

Per-leg controls#

  • POST /legs/{id}/hangup — end the leg
  • POST /legs/{id}/hold — mute the leg's audio track
  • POST /legs/{id}/unhold — restore
  • POST /legs/{id}/transfer — blind transfer. Body: {to_extension, mode: "blind"}. 409 on attended (not yet implemented) or carrier-rejected REFER.

Voicemail#

GET /voicemail#

List voicemail messages. Query: extension_id (filter), only_unheard (bool), limit.

GET /voicemail/{id}/audio#

Stream the recording bytes as audio/wav. Browsers can play this directly via <audio src>.

POST /voicemail/{id}/listened#

Mark listened. Idempotent.

DELETE /voicemail/{id}#

Delete a voicemail.


Forward rules#

GET /extensions/{id}/forward-rules#

List forward rules on an extension.

POST /extensions/{id}/forward-rules#

Create a rule. Body:

Field Type Notes
condition string always / busy / no_answer / unavailable
forward_to string E.164 or SIP URI
priority int Lower fires first
enabled bool

DELETE /extensions/{id}/forward-rules/{rule_id}#

Delete.


Tenant policy#

GET /policy#

Read the tenant's policy. Returns defaults if no row exists.

json
1
{"voicemail_transcript_enabled": false}

PATCH /policy#

Update. Only fields explicitly sent are touched. Stamps updated_by_user_id automatically.


Network info#

GET /network-info#

The four-tuple a carrier needs for whitelisting:

json
1
2
3
4
5
6
{
  "egress": {"v4": "203.0.113.5", "v6": "2001:db8::1"},
  "sip": {"port": 5060, "transports": ["UDP", "TCP"]},
  "rtp": {"port_start": 10000, "port_end": 10200, "transport": "UDP"},
  "guidance": "..."
}

End-user portal (/me/...)#

Distinct surface for tenant users without admin permission. Per-row access is gated by ExtensionGrant: read operations need any grant role (owner / manage / answer / observe); edit operations need owner.

Route What
GET /me/extensions My extensions, each with my role
PATCH /me/extensions/{id} Edit personal config (DND, ring timeout, display name, voicemail greeting). Owner-only.
GET /me/voicemail Voicemail across my owned/managed extensions
GET /me/voicemail/{id}/audio Stream a voicemail
POST /me/voicemail/{id}/listened Mark listened
DELETE /me/voicemail/{id} Delete a voicemail
GET /me/extensions/{id}/forward-rules List my forward rules (owner-only)
POST /me/extensions/{id}/forward-rules Add a forward rule (owner-only)
DELETE /me/extensions/{id}/forward-rules/{rule_id} Delete a forward rule
GET /me/calls/history Calls I placed or that touched any of my extensions
POST /me/click-to-call Originate a call. Body {to, from_extension_id}. Returns {call_id, leg_id, livekit_url, livekit_token, room_name} — the connection bundle for the browser softphone.
Updated 2026-06-23 01:06:32 View source (.md) rev 1