> ## Documentation Index
> Fetch the complete documentation index at: https://docs.orbit.devotel.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Pick your IVR surface: canvas or DSL

> Decide between the visual IVR builder canvas and the Flow DSL API — both write the same graph. Compare drag-and-drop authoring with the JSON contract, learn canvas editor behaviors, simulate before publish, and roll back with version history.

One IVR flow, two doors in. The visual canvas in the dashboard (**Voice → IVR Builder**) and the Flow DSL over the API write the same `{ nodes, edges }` graph, run the same validator, and publish the same live snapshot. This walkthrough puts the two side by side so you can pick the right surface per change — and switch mid-flight without re-authoring, since both read and write the same flow.

## 1. The Flow DSL

The DSL is a JSON graph you submit to `POST /api/v1/voice/ivr-flows` and update with `PUT /api/v1/voice/ivr-flows/:id`. Each entry in `nodes` carries an `id`, a `type` (`ivrStart`, `playAudio`, `menu`, `speechInput`, `queue`, `transfer`, `voicemail`, `hangup`), and an optional `data` block; each edge in `edges` links a `source` to a `target`, with a `sourceHandle` naming the branch (`"1"`, `"default"`, an intent name).

```json Definition theme={null}
{
  "nodes": [
    { "id": "start", "type": "ivrStart" },
    { "id": "menu", "type": "menu", "data": { "menuOptions": [{ "key": "1", "label": "Bookings" }] } },
    { "id": "bookings", "type": "queue", "data": { "label": "Bookings queue" } },
    { "id": "end", "type": "hangup" }
  ],
  "edges": [
    { "source": "start", "target": "menu" },
    { "source": "menu", "target": "bookings", "sourceHandle": "1" },
    { "source": "bookings", "target": "end" }
  ]
}
```

Every create and update runs the graph validator before persistence; a malformed graph returns `422 IVR_FLOW_GRAPH_INVALID` and stores nothing. The full node catalog, per-type `data` payloads, and validator codes are in [Build and ship your first IVR flow](/guides/build-ivr-flow) — this page does not repeat them.

## 2. Visual canvas mode

Open **Voice → IVR Builder**. Drag a node from the left palette onto the canvas (or click the palette entry to drop it at the current insert point), drag edges from a source handle to a target node, and click any node to edit its `data` block in the right-hand config panel. A **Menu** node exposes one handle per option key plus `default`; a **Speech Input** node exposes one per intent plus `nomatch`.

Save stays a draft until you publish — nothing a caller can reach. **Publish** promotes the draft to the live snapshot, and **Assign to Number** from the toolbar (or the number's **Routing** tab) attaches the flow to a DID. [Design IVR flows on the visual builder](/guides/ivr-visual-builder) is the full canvas reference.

Side by side, the same two-node branch:

| DSL (API)                                                              | Canvas (dashboard)                                                  |
| ---------------------------------------------------------------------- | ------------------------------------------------------------------- |
| `menu` node with `menuOptions: [{ "key": "1", "label": "Bookings" }]`  | Drag a **Menu** node, type `1` and `Bookings` into the config panel |
| Edge `{ "source": "menu", "target": "bookings", "sourceHandle": "1" }` | Drag from the node's `1` handle onto the **Queue** node             |
| `PUT /api/v1/voice/ivr-flows/:id`, then publish                        | **Save**, then **Publish** in the toolbar                           |

The canvas never stores a different shape: what you see is serialized to the same graph and sent to the same endpoints.

## 3. When to choose the canvas vs the DSL

Both surfaces are tenant-owned authoring choices — pick per change, not per team.

* **Choose the canvas** when routing reads better as a diagram: a multi-level menu, a branch a teammate should review at a glance, the speech-grammar editor for a Speech Input node, or per-node funnel analytics that tell you what to fix next. It is also the only place with a guided validator panel, an in-product simulator, and one-click rollback.
* **Choose the DSL** when the definition belongs in a repo: flows reviewed in pull requests, templates rendered across many tenants or brands, a CI gate that PUTs a candidate and treats `422` as a failed check, or an infrastructure-as-code pipeline that ships IVR alongside the rest of your config.

Teams mix both per flow over its lifetime. A support lead edits the holiday branch on the canvas; your pipeline re-renders the same flow from a template at deploy time. Because the graph is the contract either way, keep the DSL as the source of truth in version control and use the canvas for review and iteration — the [visual-builder reference](/guides/ivr-visual-builder) and the [DSL guide](/guides/build-ivr-flow) cover the same flow lifecycle from each side.

## 4. Editor behaviors on the canvas

Small behaviors that change how you work:

* **Snap-to-tree.** Dropped nodes land at the current insert point and **Auto-layout** re-sorts the tree into a readable vertical flow; **Fit view** re-frames a long canvas. You arrange for a review, the layout button restores the tree.
* **Hover hints.** Palette entries carry a one-line description (`Entry point`, `Collect digits`, `Press 1, 2, 3...`); toolbar buttons show tooltips for **Validate**, **Auto-layout**, **Fit view**, **History**, and the unsaved-changes indicator.
* **Validator feedback.** **Validate** (and every publish) lists each issue in a panel — "Fix each highlighted node before publishing." Click an issue and the canvas focuses the offending node. A second **Start** node is refused outright; disconnected nodes, dead-end branches, and cycles are flagged, and backend-confirmed loops highlight the cycle edges in red. Dismiss the panel and the same issue set stays dismissed — a different validator response re-opens it.
* **Selection safety.** Delete/Backspace removes the selected node, but the keys are suspended while autosave is in an error state so a failed save never costs you a node.

## 5. Simulate a call before you publish

Before attaching a DID, step a scripted caller through the draft with **Simulate** in the toolbar (or `POST /api/v1/voice/ivr-flows/:id/simulate`). Add turns — digits, speech, or an intent — and the walk reports the node reached at each step, the handle it took, the verb categories emitted, and the termination reason. Interactive nodes pause for input; digit buffers at a DTMF input node resolve per your `maxDigits` / terminator settings.

Run three walks per change: the happy path, an unmatched option (falls through `default`), and the silent caller (no input). The canvas dialog and the API consume the same graph — canvas operators and CI pipelines test the identical routing before it can ring a phone.

## 6. Version history and rollback

Every publish appends a version. Open **History** in the toolbar to list each published snapshot with its publish time and node count, preview a snapshot's nodes, and confirm a revert. A revert is a publish: the prior snapshot becomes the new head and the new draft in one action, and the attached DID keeps answering on the reverted definition — no manual re-edit, no re-attach. The equivalent API path (`GET /api/v1/voice/ivr-flows/:id/versions`, `POST .../versions/:version/revert`) is covered in the [DSL guide](/guides/build-ivr-flow#8-version-and-roll-back).

## See also

* [Build and ship your first IVR flow](/guides/build-ivr-flow) — the full DSL/API contract side.
* [Design IVR flows on the visual builder](/guides/ivr-visual-builder) — the canvas reference page.
* [IVR intents](/voice/ivr-intents) — speech menus and per-tenant intent buckets.
