---
summary: How rules match incoming calls and decide what happens next. Six match types,
  six action types, priority-ordered evaluation, no fall-through except by design.
title: Dialplans
path: concepts/dialplans
status: published
---

A dialplan is a list of rules. Each rule has a **match** (when does it apply?) and an **action** (what does it do?). The dialplan engine evaluates rules in priority order — lowest number first — and stops at the first match. Rules with no match are skipped; if no rule matches, the call hangs up with `end_reason=no_rule_matched`.

## Match types

Six match types, all stored as typed JSON in `match_params`. The admin UI's visual builder writes them for you; the wire shape is here for completeness.

### `always`

Always matches. Useful as a fallback at the bottom of the rule list (priority 999) so calls don't fall through to a 404.

```json
{"match_type": "always", "match_params": {}}
```

### `did`

Matches when the call arrived on a specific DID.

```json
{"match_type": "did", "match_params": {"did_id": "did_abc123"}}
```

Use when one dialplan serves multiple DIDs and you want different behaviour per number. A more common pattern is one dialplan per DID — wire the Did row directly to the dialplan and use other match types inside.

### `extension`

Matches when the call is being routed to a specific extension. This is for sub-dialplans called from other dialplans (a v1.1 feature), not the typical inbound flow.

```json
{"match_type": "extension", "match_params": {"extension_id": "ext_abc123"}}
```

### `caller_prefix`

Matches when the caller's E.164 starts with a given prefix. Useful for country-based routing.

```json
{"match_type": "caller_prefix", "match_params": {"prefix": "+31"}}
```

The prefix must be a valid E.164 starting with `+` and 1-15 digits.

### `time_window`

Matches when the call arrives within a day-of-week + time-of-day window. Useful for business-hours routing.

```json
{
  "match_type": "time_window",
  "match_params": {
    "days": [0, 1, 2, 3, 4],
    "start_time": "09:00",
    "end_time": "17:00",
    "timezone": "Europe/Amsterdam"
  }
}
```

Days are 0-6 with Monday = 0. Times are 24-hour `HH:MM`. Timezone is an IANA name (or `UTC`). The visual builder normalises this — duplicate days are removed, sort order is enforced, and the time range is sanity-checked.

End time before start time means "this is an overnight window" (e.g. 22:00-06:00 → night-shift routing).

## Action types

Six actions. Each writes to `action_params`.

### `ring_extension`

The most common action — ring an extension. Type and ringing semantics come from the extension itself.

```json
{
  "action_type": "ring_extension",
  "action_params": {"extension_id": "ext_abc123"}
}
```

If the extension's type is `wave`, the call rings the user's softphone. If `bot`, ScaiBot's voice worker joins the room. If `voicemail`, the call goes straight to voicemail. If `external` or `sip_endpoint`, the call is forwarded.

### `ring_bot`

Skip the extension layer and ring a bot directly. Useful when you want a "press 0 to talk to a bot" rule without a placeholder extension row.

```json
{
  "action_type": "ring_bot",
  "action_params": {"bot_id": "bot_abc123"}
}
```

### `voicemail`

Record the caller into a voicemail box. `extension_id` is optional — when set, the voicemail is associated with that extension (and the extension owner sees it in `/my/dial`); when unset, it lands in the tenant's default box.

```json
{
  "action_type": "voicemail",
  "action_params": {"extension_id": "ext_abc123"}
}
```

If the tenant opted into voicemail transcripts (Settings page), the recorder runs ScaiEcho after the call ends and stores the transcript + summary on the row.

### `forward`

Forward the call to an external E.164 or SIP URI via SIP REFER. The caller is removed from the original room once the carrier accepts the REFER.

```json
{
  "action_type": "forward",
  "action_params": {"to": "+31201234567"}
}
```

### `hangup`

Terminate the call. Useful as a final rule when you want a blocklist:

```json
{
  "priority": 1,
  "match_type": "caller_prefix",
  "match_params": {"prefix": "+44900"},
  "action_type": "hangup",
  "action_params": {}
}
```

### `play_message`

Play a TTS message then hang up. Useful for after-hours announcements.

```json
{
  "action_type": "play_message",
  "action_params": {
    "text": "Our offices are closed. Please call back during business hours.",
    "voice_id": "vc_abc123"
  }
}
```

The `voice_id` is a ScaiSpeak voice your tenant has access to.

## Priority and ordering

Lower priority numbers fire first. The builder defaults new rules to 100; if you want a rule to act as a fallback, set it to 999. We recommend leaving room (100, 200, 300...) so you can insert rules later without renumbering.

Rules with the same priority are evaluated in created-at order, but you shouldn't rely on that — set distinct priorities.

## A real-world example

A small office wants:

1. Calls during business hours ring the reception desk (ext 100).
2. Calls during business hours that go unanswered after 25s ring the support bot.
3. Calls outside business hours go to voicemail.
4. Calls from a known spam prefix get dropped.

```
priority 10  | caller_prefix +44900       | hangup
priority 100 | time_window Mon-Fri 9-17   | ring_extension ext_reception
priority 200 | time_window Mon-Fri 9-17   | ring_bot bot_support_assistant
priority 999 | always                     | voicemail
```

Rule 100 fires for in-hours calls. The "ring then bot" failover comes from setting `ring_timeout_s = 25` on the reception extension and `timeout_action = ring_bot` with `timeout_forward_to = bot_support_assistant`. The dialplan stays simple; the failover lives on the extension.

## What dialplans don't do

- **No call queues.** v1 calls ring one extension; if a queue with multiple agents is what you want, route to a bot that bridges to a queue product.
- **No "press 1 to..." menus.** IVR menus are a v1.1 feature. The current `play_message` action is one-way.
- **No simultaneous ring across extensions.** A rule can ring exactly one extension or one bot. The "ring my desk and my mobile" pattern lives in [forwarding rules](../reference/api#forward-rules) on the extension, not the dialplan.
