Skip to main content
An IVR menu greets a caller, reads out a few options, and routes them by the key they press — “press 1 for sales, press 2 for support.” You author the menu once with this API (or the dashboard), bind it to one of your Wave numbers, and Wave serves it live to every caller — no redeploy. Under the hood a menu compiles to a WaveML <Gather> that plays your greeting and collects one keypress, then runs the branch for that digit. This API is the authoring layer on top of WaveML — you describe the menu as JSON and Wave turns it into the call flow.
This is the single-level menu — one greeting, one set of keypad options, each ending the call or handing it off. Nested sub-menus and a visual flow builder are separate, later features. For full branching logic today, return WaveML from your own endpoint instead.

Authentication & tiers

All /v1/callflows endpoints take an API key with the callflows:read or callflows:write scope, or a dashboard session. You can author and test menus with a sandbox key (sk_sandbox_); serving a menu to a real inbound call needs a production key (sk_live_), a production-tier org, and a bound number (see Bind a number below).

The menu definition

A menu is a JSON definition: a greeting, the keypad branches, and an optional fallback for when the caller presses nothing.

Prompt and actions

A prompt and every branch action is one of these. A branch action is a terminal leaf — it ends the call or hands it off.
dial targets are restricted to Saudi (KSA) phone numbers — a toll-fraud guard, since a <Dial> bridges the caller out to a new leg.

Branch input (your webhook payload)

Give a branch an input object and Wave merges it into the call.input webhook when the caller picks that option — your integration’s hook for what the caller chose.
  • Up to 20 fields; values are string (≤512), number, or boolean.
  • The keys call_id and digits are reserved (Wave sets them) and rejected.

Create a menu

POST /v1/callflows — create a menu by name. Pass "activate": true to make it live immediately.
201 Created returns the menu, including its version history:
A duplicate name returns 409 — use PUT to add a version instead.

List and fetch menus

GET /v1/callflows lists one row per menu (its active or latest version):
GET /v1/callflows/{name} returns the full menu — the active (or latest) version’s definition plus the complete versions history. 404 if the menu doesn’t exist.

Versioning and rollback

Menus are immutable and append-only. Editing never mutates a version in place — it appends a new one.
  • PUT /v1/callflows/{name} with a new definition (and optional activate) adds the next version. 404 if the menu doesn’t exist.
  • POST /v1/callflows/{name}/activate with { "version": N } makes version N live — rollback is just activating an older version. 404 if that version doesn’t exist.
Both return the menu object with the newly-active version.

Bind a number

POST /v1/callflows/{name}/bind-number points one of your Wave numbers at the menu’s active version — inbound calls to that number then run the menu.
cURL
Returns { "bound": true }. Numbers are Wave-provided — see Virtual Numbers.

Retire a menu

DELETE /v1/callflows/{name} soft-retires a menu: it stops serving callers and any bound number is unlinked, but the version history is kept (you can recreate it later). Returns { "retired": true }; 404 if the menu doesn’t exist.

The call.input webhook

When a caller presses a digit, Wave sends a call.input webhook. Its data carries the pressed digits plus that branch’s input object — so your backend learns exactly what the caller chose:
Register an endpoint and verify the signature as described in Webhooks.

Audio prompts

A play prompt or action plays a pre-recorded file instead of TTS — good for a produced greeting, and it sidesteps synthesis latency. Upload the file first, then reference the returned audio_ref. POST /v1/callflows/audio takes a base64-encoded WAV file (8 kHz mono 16-bit PCM recommended — telephony quality) and returns a Wave-hosted audio_ref:
cURL
Use the audio_ref in a play:
Audio upload is rolling out. Until it’s enabled for your account, the upload returns 503 and menus use say (TTS) prompts. An audio_ref must be a file you uploaded — arbitrary URLs are rejected.

Next steps

  • WaveML — the verbs a menu compiles to, and full custom flows.
  • Webhooks — receive and verify call.input.
  • Queues — where an enqueue branch sends the caller.