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

Quickstart

This walks you from "nothing configured" to "a call rings my extension" in one sitting.

Prerequisites#

  • A ScaiGrid tenant with the scaidial:trunks:manage, scaidial:extensions:manage, and scaidial:dialplan:manage permissions on your API key. (Tenant admins get all three.)
  • A SIP trunk from any ITSP — Twilio Elastic SIP, Telnyx, Voxbone, your own Asterisk / FreeSWITCH, etc. The credentials you need: server FQDN, username, password.
  • One DID (phone number) the carrier will deliver to that trunk.

If you only have a carrier account but no trunk configured yet, do that first on the carrier side — ScaiDial registers as a SIP client to whatever you create.

1. Add the trunk#

bash
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
curl -X POST https://scaigrid.scailabs.ai/v1/modules/scaidial/trunks \
  -H "Authorization: Bearer $SCAIGRID_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "primary",
    "sip_server_address": "sip.example.com",
    "sip_server_port": 5060,
    "sip_transport": "udp",
    "sip_auth_mode": "credentials",
    "sip_auth_username": "your-trunk-user",
    "sip_auth_password": "your-trunk-secret",
    "sip_inbound_numbers": ["+31201234567"],
    "sip_media_encryption": "disabled"
  }'
python
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
import httpx, os

resp = httpx.post(
    "https://scaigrid.scailabs.ai/v1/modules/scaidial/trunks",
    headers={"Authorization": f"Bearer {os.environ['SCAIGRID_API_KEY']}"},
    json={
        "name": "primary",
        "sip_server_address": "sip.example.com",
        "sip_server_port": 5060,
        "sip_transport": "udp",
        "sip_auth_mode": "credentials",
        "sip_auth_username": "your-trunk-user",
        "sip_auth_password": "your-trunk-secret",
        "sip_inbound_numbers": ["+31201234567"],
        "sip_media_encryption": "disabled",
    },
)
print(resp.json())
typescript
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
const resp = await fetch(
  "https://scaigrid.scailabs.ai/v1/modules/scaidial/trunks",
  {
    method: "POST",
    headers: {
      "Authorization": `Bearer ${process.env.SCAIGRID_API_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      name: "primary",
      sip_server_address: "sip.example.com",
      sip_server_port: 5060,
      sip_transport: "udp",
      sip_auth_mode: "credentials",
      sip_auth_username: "your-trunk-user",
      sip_auth_password: "your-trunk-secret",
      sip_inbound_numbers: ["+31201234567"],
      sip_media_encryption: "disabled",
    }),
  },
);
console.log(await resp.json());

The response includes a sync_status field. Poll the trunk until it transitions to synced — that means livekit-sip has successfully REGISTERed to your carrier. If it parks in error, read sync_last_error for the carrier's response and either fix the credentials or retry via POST /trunks/{id}/resync.

If your carrier requires source-IP whitelisting, fetch the tuple to whitelist from GET /network-info: egress IPv4, SIP port, and the RTP UDP range.

2. Create a dialplan and an extension#

A minimal "ring my desk when anyone calls the DID" setup needs two objects: an extension to ring, and a dialplan whose only rule fires ring_extension on the always-match condition.

bash
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# Extension — type=wave routes the call to a tenant user.
EXT_ID=$(curl -s -X POST https://scaigrid.scailabs.ai/v1/modules/scaidial/extensions \
  -H "Authorization: Bearer $SCAIGRID_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "number": "1001",
    "type": "wave",
    "target_ref": "usr_your_user_id_here",
    "display_name": "My desk",
    "ring_timeout_s": 30
  }' | jq -r .data.id)

# Dialplan with one rule: always → ring extension 1001.
DPLAN_ID=$(curl -s -X POST https://scaigrid.scailabs.ai/v1/modules/scaidial/dialplans \
  -H "Authorization: Bearer $SCAIGRID_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name": "main"}' | jq -r .data.id)

curl -X POST https://scaigrid.scailabs.ai/v1/modules/scaidial/dialplans/$DPLAN_ID/rules \
  -H "Authorization: Bearer $SCAIGRID_API_KEY" \
  -H "Content-Type: application/json" \
  -d "{
    \"priority\": 100,
    \"match_type\": \"always\",
    \"match_params\": {},
    \"action_type\": \"ring_extension\",
    \"action_params\": {\"extension_id\": \"$EXT_ID\"}
  }"

3. Wire the DID to the dialplan#

bash
1
2
3
4
5
6
7
8
9
curl -X POST https://scaigrid.scailabs.ai/v1/modules/scaidial/dids \
  -H "Authorization: Bearer $SCAIGRID_API_KEY" \
  -H "Content-Type: application/json" \
  -d "{
    \"e164\": \"+31201234567\",
    \"trunk_id\": \"$TRUNK_ID\",
    \"dialplan_id\": \"$DPLAN_ID\",
    \"enabled\": true
  }"

4. Test the inbound path#

Dial the DID from your phone. The dialplan fires the always → ring_extension rule, ScaiDial places the call into a LiveKit room, and your wave user gets a ring via the in-browser softphone (if they're logged into the admin) or via voicemail fallback after ring_timeout_s.

You can watch this happen live on the admin page at /scaidial/calls — the call appears in ringing, then active once you answer, then disappears when you hang up. The full record stays in /scaidial/history.

Next steps#

  • Place outbound. With the same trunk synced, your owned extension can click-to-call any E.164 or SIP URI from /my/dial. See concepts/architecture for the outbound flow.
  • Route to a bot. Change the extension type to bot and point target_ref at a ScaiBot bot ID. Calls land in a LiveKit room with the bot worker; no extra wiring on the ScaiBot side.
  • Add time-of-day routing. Use the time_window match type to ring different extensions during business hours vs. after-hours. The visual builder on /scaidial/dialplans handles the day-of-week + start/end picker for you.
  • Forwarding rules. Each tenant user manages their own from /my/dial: forward on busy, on no-answer, on unavailable, or always.
Updated 2026-06-23 01:06:32 View source (.md) rev 1