Part 5 — Call flow reference

← Contents · Previous: Part 4 — Reference · Next: Part 6 — Building the screens

Everything inside a flow document: its parts, messages, variables, the 18 blocks with every setting and exit, the Waiting Room, the AI Agent, and every check code. It's written for the developer of a flow editor. What the editor does with the API (push, approve, put on a number) is in Part 2B. How to build the editor screen is in Part 6 §6.1.

Two endpoints keep an editor honest:

Conventions in this page:


5.1 The flow document

{
  "direction": "OUTGOING",
  "settings": { "language": "sw", "voice": { "sw": "marin", "en": "cedar" }, "speed": 1.0, "record": "off" },
  "vars": [ { "name": "name", "type": "text" }, { "name": "amount", "type": "money" } ],
  "start": { "out": "hello" },
  "blocks": {
    "hello": { "type": "say", "msg": { "src": "text", "text": "Habari {name}. Deni lako ni shilingi {amount}." },
               "x": { "next": "menu" } },
    "menu":  { "type": "menu", "msg": { "src": "text", "text": "Bonyeza moja kulipa sasa, mbili kuongea na mhasibu." },
               "options": [ { "k": "1", "id": "pay", "label": "Lipa", "words": ["lipa", "kulipa"] },
                            { "k": "2", "id": "staff", "label": "Mhasibu", "words": ["mhasibu", "mtu"] } ],
               "speech": { "match": "words" },
               "x": { "pay": "pay", "staff": "desk", "noInput": "bye", "notUnderstood": "bye" } },
    "pay":   { "type": "app_action", "action": "pay", "params": { "amount": "{amount}" }, "countAs": "pay_started",
               "x": { "paid": "thanks", "failed": "bye", "timeout": "bye" } },
    "desk":  { "type": "connect", "department": "c09c1657-…", "x": { "noAnswer": "bye" } },
    "thanks": { "type": "goodbye", "msg": { "src": "text", "text": "Asante, malipo yamepokelewa." } },
    "bye":   { "type": "goodbye" }
  }
}

(The Swahili reads: "Hello {name}. Your debt is {amount} shillings." · "Press one to pay now, two to talk to the accountant." · "Thank you, the payment was received.")

Field Type Required Description
direction string Yes INCOMING (a number plays it), OUTGOING (call-outs, quick calls, scheduled calls play it) or SHARED (only other flows run it, through Transfer to script). Fixed for the flow: every version has the same one
start object Yes The first block: {"call": id} for INCOMING, {"out": id} for OUTGOING, {"in": id} for SHARED
blocks object Yes Block id → block. At most 60 blocks
vars array No OUTGOING only: the values each person on a call-out brings ({name, type}; type is text (default) or money). Not allowed in INCOMING flows (VARS_IN_INCOMING)
inputs string[] No SHARED only: what a Transfer to script block must pass in; used inside as {in.<name>}
settings.language string No sw or en: the language the flow's fixed texts are made into audio in when the version is pushed. During a call, see §5.2
settings.voice string or object No The voice for every text: "marin", or one per language {"sw": "marin", "en": "cedar"}
settings.speed number or object No 0.7–1.5 (rounded to 0.05), or one per language. Default 1.0
settings.record string No "off": no live recording for calls on this flow, even if the account records. Absent: the account decides

Block ids

Every block's shape

"pay": {
  "type": "app_action",            // required: one of the 18 types
  "…settings…": "…",               // the block's own settings (§5.5)
  "before": { "src": "text", "text": "Mazungumzo haya yanarekodiwa." },   // optional announcement (§5.4)
  "countAs": "pay_started",        // optional outcome tag (§5.4)
  "voice": "cedar", "speed": 1.1,  // optional: this block's voice and speed
  "wr": { … },                     // Waiting Room, on Connect / Send request / App action (§5.6)
  "x": { "paid": "thanks", "failed": "bye", "timeout": "bye" }   // exit name → block id
}

Exits


5.2 Messages

Every place a flow speaks (msg, before, ask, wr.start, wr.updates, thanks) takes a message: one part, or a list of parts played one after another.

{ "src": "text", "text": "Habari {name}", "lang": "sw", "voice": "marin", "speed": 1.0 }
{ "src": "upload", "media": "7f1c…" }
[ { "src": "upload", "media": "7f1c…" }, { "src": "text", "text": "Deni lako ni {amount}." } ]

("Hello {name}" · "Your debt is {amount}.")

Field Description
src text (the engine speaks it), or record / upload / library (an audio file, by media id). The three audio sources play the same way; the name only says where the file came from, for the editor
text Up to 500 characters. May contain {variables} (§5.3)
media The id of an audio file of this account (2B, E1). It must be READY when the version is pushed
lang sw or en: the language of this text. Absent: the call's current language
voice, speed This message's voice and speed. Absent: the block's, then the flow's settings, then the engine's default

When audio is made. Fixed texts (no {) are turned into audio when the version is pushed, so calls play them at once. Texts with variables are made during the call the first time each exact text is needed, then reused. Changing the voice or speed makes new audio.

The call's language ({lang}) starts as the account's defaultLanguage. It changes when the caller picks a menu option with lang, or when an AI Agent with AUTO language hears the caller's language. Engine messages ("Samahani…", the goodbye, Pick's "Bonyeza…") follow it. For a flow written in a language other than the account's default, put lang on each text, or start with a menu option that sets the language.

Voices: marin · coral · shimmer · sage · cedar · ash · echo · ballad · verse.


5.3 Variables

{name} in any text, URL, parameter or body is filled during the call. A variable with no value becomes empty, and the timeline records VARIABLE_MISSING. The check refuses names it can't know (UNKNOWN_VARIABLE).

Always there

Variable Value
{caller} The other person's number: the caller (incoming) or the person called (outgoing). E.164
{number} The business's number on this call
{callId} The call's id
{date} · {time} Now, in the account's time zone: 13/11/2026 · 14:05
{lang} The call's language: sw or en
{key} The last menu key pressed
{heard} The last thing the caller said (any Ask block)
{answeredBy} Outgoing calls: person or machine (answering-machine detection). Detection runs only for flows that use this variable

Made by blocks

Variable Made by Example
{<var>} the call-out person's values (OUTGOING vars) {name}, {amount}
{input.<saveAs>} Collect number {input.account}
{answer.<saveAs>} Ask question {answer.problem}
{poll.<saveAs>} Poll (saveAs, else the block id) {poll.rating} = 4
{choice.label} · {choice.index} · {choice.<field>} Pick from list {choice.id}, {choice.price}
{reply.code} · {reply.say} · each map target Send request {reply.code} = 200; "map": {"data.balance": "balance"} → {balance}
{action.<block>.outcome} · {action.<block>.<key>} App action that waits (the data of its result) {action.pay.receipt}
{ai.<field>} AI Agent fields {ai.date}
{schedule.when} · {schedule.at} Schedule call "kesho saa tatu asubuhi" · 2026-11-14T09:00
{in.<input>} inside a SHARED flow: what the Transfer block passed {in.account}
{queue.position} Connect, while waiting 3

How they're read aloud:


5.4 Settings any block can have

Field Description
before Announcement: a message played once when the block starts, before its own prompt. Not repeated when the block asks again after a wrong key ("Mazungumzo haya yanarekodiwa", "This call is recorded")
countAs Outcome tag: when a call reaches the block, the tag is added to the call (booked, promised, wants_callback). Reports and call-out stats count calls by tag. Some are added automatically: opted_out (Opt out), callback_scheduled (Schedule call), App action outcomes the client app mapped with countAs
voice, speed The block's voice and speed, for its texts that don't set their own

The Ask blocks (Menu, Collect, Poll, Pick, Ask question, and Schedule call's questions) also share:

Field Default Description
timeoutSec 8 (Collect 10) Seconds of silence after the prompt before it counts as no input
retries 2 How many times the block asks again after no input, a wrong key or words it didn't understand. 0–3
laterWords — Extra words meaning "not now" for the later exit (below)

5.5 The blocks

Group Blocks
Speak Say
Ask Menu · Collect number ⚠ · Poll · Pick from list · Ask question
Connect Connect to department · Voicemail · Schedule call · AI Agent ⚠
Integrate Send request ⚠ · App action (⚠ when the action is risky)
Logic Condition · Go to · Transfer to script · Return
End Opt out · Goodbye

say

Says a text or plays audio, then goes on.

Setting Type Default Rules
msg message req

Exits: next. Timeline: SAY / PLAY.

"hi": { "type": "say", "msg": { "src": "text", "text": "Karibu PMK Chemicals." }, "x": { "next": "menu" } }

("Welcome to PMK Chemicals.")


menu

Asks the caller to pick an option by key or, with speech on, by voice. Each option is an exit.

Setting Type Default Rules
msg message req Say the keys and words: "Bonyeza moja au sema mauzo…" ("Press one or say sales…")
options array req 1–10 options (below)
speech object absent = keys only {"match": "words" | "smart", "minConfidence": 0.5, "language": "sw", "maxSec": 12}
timeoutSec · retries · laterWords 8 · 2 §5.4

Each option:

Field Description
k The key: 0–9, * or #, unique in the menu. May be empty only when speech is on (a voice-only option)
id The exit name. Default: the key. Letters, digits, _; unique in the menu
label The option's name, read back when the caller isn't understood ("Samahani, sijaelewa. Unaweza kusema: Mauzo au Msaada.", "Sorry, I did not understand. You can say: Sales or Help.")
words Words that pick this option by voice (["mauzo", "kununua", "bei"]). With match: words, an option without words can only be picked by its key (warning OPTION_WITHOUT_WORDS)
lang sw or en: picking this option also switches the call's language (a language menu)

speech.match:

Exits: each option's id · noInput (silence or wrong keys, after retries) · notUnderstood (speech that matched nothing, after retries; if not wired, noInput is used) · later. Flows from before 2026-11-12 used keys instead of options and the exit noKey. Both still run; write options and noInput.

Timeline: KEY (digit, valid) · SPEECH · MATCH (option, by: key / words / smart, confidence) · NO_INPUT · RETRY · LANGUAGE · LATER. Webhook: call.block {type: "menu", option, by}.


collect

⚠ Risky. Collects a number (account, meter, PIN, amount) into {input.<saveAs>}.

Setting Type Default Rules
msg message req "Weka namba ya akaunti, kisha alama ya reli." ("Enter your account number, then the hash key.")
saveAs string req A variable name: [a-zA-Z_][a-zA-Z0-9_]*
minDigits · maxDigits int 1 · 20 1–20, min ≤ max
finishKey string # The key that ends the number
pattern string — A regular expression the digits must match (^07\d{8}$)
sensitive bool false PIN-like: kept encrypted, shown as •••• everywhere; never listens for speech
speech bool false Also accept a spoken number ("sifuri saba moja…", "zero seven one…"). Off by default: spoken account numbers are where speech recognition errs most
timeoutSec · retries · laterWords 10 · 2

A wrong length or pattern → "Namba uliyoingiza si sahihi." ("The number you entered is not valid.") and ask again.

Exits: ok · failed (after retries) · later. Timeline: INPUT (value or ••••, valid, by: key / voice) · NO_INPUT · NOT_UNDERSTOOD · RETRY. Webhook: call.block {type: "collect", saveAs, value} (masked when sensitive).


poll

A question answered with a key from 1 to 5 (rating, yes/no, survey), saved as {poll.<saveAs>}.

Setting Type Default Rules
msg message req Say what each key means
answers int 5 1–5: keys 1..answers are valid
saveAs string the block id
labels array — [{"k": "1", "label": "Mbaya", "words": ["mbaya"]}, …]: names for reports, and words for speech
speech bool or object off true, or {"match": "words" | "smart", "minConfidence"}. A spoken number ("tano", "five") always works
timeoutSec · retries · laterWords 8 · 2

Exits: next (answered) · noInput · later. Timeline: POLL (answer, label, by) · KEY · NO_INPUT · NOT_UNDERSTOOD · RETRY. Webhook: call.block {type: "poll", question, answer, label}.


pick

Reads a list ("Bonyeza moja kwa Arusha. Bonyeza mbili kwa Dodoma…", "Press one for Arusha. Press two for Dodoma…") and lets the caller choose. Sets {choice.*}.

Setting Type Default Rules
source string req The id of a Send request block that waits for its reply (the reply's top-level items array), or "inline"
items array — With source: "inline": [{"id": "aru", "label": "Arusha"}, …]
label string {label} How each item is read: fields of the item, "{name} kwa shilingi {price}" ("{name} for {price} shillings")
msg message — Said once before the list
perPage int 5 1–9 items per page; 0 reads the next page
speech bool or object off The caller can say the item's name, its number, or "zaidi" / "more"
timeoutSec · retries · laterWords 8 · 2

Exits: chosen · empty (no items) · noInput · later. Variables: every plain field of the chosen item as {choice.<field>}, plus {choice.label} and {choice.index}. Timeline: PICK (index, id, label, by) · PICK_MORE · PICK_EMPTY · KEY · NO_INPUT. Webhook: call.block {type: "pick", id, label}.


ask

Asks an open question; the caller's spoken answer becomes text in {answer.<saveAs>} and the call goes on. ("Tuambie tatizo lako.", "Tell us your problem.") Unlike Voicemail, nothing is recorded and the call doesn't end.

Setting Type Default Rules
msg message req
saveAs string req Variable name
maxSec int 20 5–60: the longest answer
language string the call's sw / en: the language to listen for
useMenuWords bool false If the Menu just before was answered by voice in 6 or more words, those words are the answer and the question is skipped
retries int 2 Asked again after silence

Exits: answered · silence. Timeline: SPEECH · NO_INPUT · RETRY · ANSWER_FROM_MENU. Webhook: call.block {type: "ask", saveAs, text}.


connect

Puts the caller in a department's queue and rings its agents in their browser (Part 2D). The caller waits in the Waiting Room until an agent accepts.

Setting Type Default Rules
department string req A department id of this account (not deleted)
record bool account false: don't record the talk with the agent, even if the account records
wr object engine defaults The Waiting Room (§5.6): music, updates, queue position, option keys, give-up time

Agents are rung once the caller has heard everything before the music (the intro, wr.start). Whether members' mobiles ring too is the number's ringMode (2A), not a block setting.

Exits: noAnswer (nobody accepted before wr.giveUpSec; or nobody online and the department says "exit at once") · each wr.options[].exit. After an agent accepts, the flow doesn't go on: the call is with the agent until someone hangs up. Timeline: WAIT_START · QUEUED · RING · RING_TIMEOUT · RING_DECLINED · ACCEPTED · AGENT_JOINED · LIVE_START · NOBODY_ONLINE · WAIT_UPDATE · WAIT_OPTION · WAIT_END.


voicemail

A prompt, a beep, then records the caller's message. Ends the call. The message is stored, transcribed if the account transcribes, and sent as voicemail.received.

Setting Type Default Rules
msg message req "Acha ujumbe baada ya mlio." ("Leave a message after the beep.")
maxSec int 60 5–120, and never above the account's voicemail limit
beep bool true One beep to start, two short beeps warnBeforeEndSec before the end, one long beep when time is up. Only the caller hears them; they're not in the recording
warnBeforeEndSec int 10 (a third of a short maxSec) 0 = no warning
thanks message the system goodbye Said after the message

The caller ends the message with # or by hanging up.

Exits: none (the call ends). Timeline: VOICEMAIL (recorded) · RECORDING_READY (kind: VOICEMAIL) · TRANSCRIPT_READY.


schedule

Books a later call to this caller ("nipigie baadaye", "nikumbushe kesho asubuhi", "call me later", "remind me tomorrow morning"), played by an OUTGOING flow. Needs the client app's CALLOUTS feature. Full design: SCHEDULE-CALL.md.

Setting Type Default Rules
script · version string · int req An OUTGOING flow of this account and a runnable version of it
after string — A fixed delay: 30m, 1h, 1d
at string — A fixed time: 17:00, today 17:00, tomorrow 09:00 (a time already past today means tomorrow)
ask message — The caller says the time ("Tukupigie lini?", "When should we call you?")
confirm bool true with ask, else false Say the time back and wait for yes / no (keys 1 / 2 too)
vars object — {name: template}: values for the later call's flow ({"order": "{input.order}"})
note string — What it's about, shown on the scheduled call
maxTries · retryGapSec int 2 · 300 1–3 tries; at least 300 s apart
msg message engine line Said after booking; may use {schedule.when}
timeoutSec · retries 8 · 2 For the spoken time and the yes / no

Exactly one of after, at, ask.

Rules the engine applies, whatever the flow says:

Exits: scheduled · notUnderstood (the time wasn't understood, after retries) · declined (the caller said no to the time, after retries) · refused (the engine said no; SCHEDULE_REFUSED.reason: TIME_OUT_OF_RANGE, OPTED_OUT, TOO_MANY_PENDING, SCRIPT_NOT_RUNNABLE, FEATURE_OFF, NO_TIME). Timeline: SCHEDULE_HEARD (said, at) · SCHEDULE_CHANGED · CALL_SCHEDULED (at, script) · SCHEDULE_REFUSED (reason). Webhook: call.scheduled {callId, calloutId, at, to, scriptRef, said}.


ai_agent

⚠ Risky. A spoken conversation with an AI, in stages, with its own knowledge, fields and tools. Needs the client app's AI feature. Its seven exits lead to any block. Full settings: §5.7.

Exits: goal (done) · human (wants a person: usually a Connect) · vm (leave a message) · cb (call me back) · nohelp (the caller ended it) · time (maxMin reached) · err (AI failed, didn't start in 20 s, or the client app lost AI).


request

⚠ Risky. Calls the business's own HTTPS endpoint and maps the JSON reply into variables.

Setting Type Default Rules
url string req https:// with a public host. {variables} in it are URL-encoded. Private, loopback and cloud-metadata addresses are refused
method string POST GET · POST · PUT · PATCH · DELETE
params object — Query parameters {name: template}
headers object — {name: template}. X-Voice-*, Host and Content-* are set by the engine
body object {"type": "json"} type: json · form · raw · none; value: the body, with variables
auth object {"type": "signature"} signature (signed with the account's request secret) · bearer + token · basic + username + password · apikey + header + value · none
map object — Reply JSON path → variable: {"data.balance": "balance", "items[0].name": "first"}
timeoutSec int 10 1–15
mode string reply reply: wait for the answer · continue: send and go on at once (queued, retried for up to 24 h) · callback: the business answers later at {callback_url}
wr object — The Waiting Room for reply / callback when the answer takes more than about a second

What the engine sends and expects:

Exits: continue mode: next. Other modes: success · failure · timeout · each wr.options[].exit. Timeline: REQUEST_SENT · REQUEST (status, latencyMs, outcome, saved) · REQUEST_QUEUED · REQUEST_CALLBACK · REPLY_NOT_JSON · WAIT_*. Webhook: request.failed (a continue request that failed for 24 h); call.block with late: true when a callback arrives after the caller left.


app_action

Asks the client app to do one of the actions it registered (Part 2E): send an SMS, take a payment, create a ticket. The engine has no built-in SMS or payment.

Setting Type Default Rules
action string req A name the client app registered
params object the action's defaults {name: template}. Every required parameter must be given; values are type-checked during the call (money, number, phone, choice)
msg message — Said before waiting ("Tafadhali thibitisha malipo kwenye simu yako.", "Please confirm the payment on your phone.")
wr object — The Waiting Room while it waits

Exits:

Variables: {action.<block>.outcome} and each key of the result's data as {action.<block>.<key>}. Timeline: ACTION_REQUESTED · ACTION_RESULT (outcome, late) · ERROR (ACTION_UNKNOWN, ACTION_PARAMS). The block is ⚠ when the action is registered as risky.


condition

Branches on a test. Instant: the caller hears nothing.

test.kind Other fields Exits
hours — open · closed (the account's opening hours, in its time zone)
calledRecently days (default 7) yes · no: an outgoing call from this account reached this caller in the last days
variable var, op, value yes · no
contactExists — yes · no. Reserved: nothing fills it yet, so it is always no. Don't offer it in the editor

op for variable (value may contain {variables}; text compares ignore case):

op Yes when
equals (default) · notEquals the value is / isn't value
gt · lt greater / less than value (as numbers; 45,000 works)
in one of a comma list: "value": "M-Pesa, Tigo Pesa"
matches matches a regular expression
exists · empty has a value / has none (no value needed)

Answered by a machine (outgoing): {"kind": "variable", "var": "answeredBy", "op": "equals", "value": "machine"}.

Timeline: CONDITION (test, result).


goto

Jumps to another block.

Setting Type Default Rules
target string req The block to jump to (to is accepted too)
maxLoops int 0 = no limit After this many jumps from this block in one call → tooMany

Exits: tooMany. Most editors draw target as a dashed line, not as an exit port. Timeline: LIMIT (GOTO_LOOPS).


transfer

Runs a SHARED flow like a function: its Return blocks come back here, through the exit with the same name.

Setting Type Default Rules
script · version string · int req A SHARED flow and a runnable version (pinned: changing the shared flow changes nothing here until the pin moves)
inputs object — {name: template}: every input the shared flow declares

Exits: the names the shared flow's Return blocks use (done, failed…). Variables set inside stay visible after. At most 5 transfers deep. Timeline: TRANSFER_IN · TRANSFER_OUT (exit).

return

SHARED flows only: goes back to the Transfer block.

Setting Type Rules
exit string req. [a-zA-Z][a-zA-Z0-9_]{0,30}

Exits: none.


optout

Adds the caller to the account's do-not-call list (2E): call-outs never call them again.

Setting Type Default
msg message "Umeondolewa kwenye orodha. Hutapigiwa tena." / "You have been removed from the list. We will not call again."

Exits: next. Adds the tag opted_out. Timeline: OPTED_OUT.

goodbye

Ends the call.

Setting Type Default
msg message "Asante kwa kupiga simu. Kwaheri." / "Thank you for calling. Goodbye."

Exits: none.


5.6 The Waiting Room

wr on Connect, Send request and App action: what the caller hears and can do while the block is busy.

"wr": {
  "start": { "src": "text", "text": "Tafadhali subiri, tunakuunganisha. Bonyeza tisa kuacha ujumbe." },
  "music": "hold_default",
  "updates": [ { "src": "text", "text": "Bado tunakutafutia mhudumu." } ],
  "updateEverySec": 30,
  "queuePosition": true,
  "options": [ { "key": "9", "exit": "vm" } ],
  "optionsAfterSec": 20,
  "giveUpSec": 180
}

("Please wait, we're connecting you. Press nine to leave a message." · "We're still finding you an agent.")

Field Default Description
start — Played once when waiting starts
music hold_default An audio id of the account, looped
updates — Messages played in turn, one every updateEverySec
updateEverySec 30
queuePosition false Connect only: "Wewe ni wa 3 kwenye foleni." ("You are number 3 in the queue.") with each update
options — [{key, exit}]: a key the caller can press to leave through that exit (voicemail, call me back, back to the menu). Key 0–9, *, #. Each exit becomes an exit of the block
optionsAfterSec 0 Keys work only after this many seconds
giveUpSec 300 10–1800, and never above the account's longest wait. Then noAnswer (Connect), timeout (Send request, App action)

The Waiting Room says only what the author writes. Announce the option keys in start or updates; options with neither get the warning WAIT_OPTIONS_UNANNOUNCED.


5.7 The AI Agent

"assistant": {
  "type": "ai_agent",
  "agent": {
    "goal": "Book an appointment",
    "persona": "Mhudumu mpole wa Kliniki ya Afya Bora",
    "guard": "Never promise prices that are not in the knowledge base",
    "voice": "marin", "maxMin": 5,
    "language": { "mode": "AUTO", "default": "sw" },
    "kb": { "items": [ { "q": "Saa za kazi", "a": "Jumatatu hadi Jumamosi, saa mbili asubuhi hadi saa kumi jioni." } ],
            "docs": ["4b1e…"], "site": "https://example.co.tz/huduma" },
    "fields": [ { "name": "patient_name", "desc": "Jina la mgonjwa" }, { "name": "date", "desc": "Siku ya miadi" } ],
    "tools": [ { "type": "request", "name": "book_slot", "req": "book" },
               { "type": "app action", "name": "send_confirmation", "action": "send_sms", "params": { "text": "Miadi yako ni {ai.date}" } } ],
    "canSchedule": { "script": "callback", "version": 1 },
    "start": "s1",
    "anytime": [ { "kind": "person", "to": "s5" }, { "kind": "upset", "to": "s5" } ],
    "stages": {
      "s1": { "type": "greet", "say": "Habari! Nikusaidie nini leo?", "leave": [ { "kind": "always", "to": "s2" } ] },
      "s2": { "type": "understand", "leave": [ { "kind": "question", "to": "s3" }, { "kind": "intent", "to": "s4" } ] },
      "s3": { "type": "answer", "leave": [ { "kind": "always", "to": "s2" } ] },
      "s4": { "type": "collect", "collect": ["patient_name", "date"], "leave": [ { "kind": "done", "to": "s6" } ] },
      "s6": { "type": "act", "actions": ["book_slot", "send_confirmation"],
              "leave": [ { "kind": "success", "to": "exit:goal" }, { "kind": "failed", "to": "s5" } ] },
      "s5": { "type": "handover", "ho": {
                "ok":     { "say": "Nakuunganisha na mhudumu sasa hivi.", "to": "exit:human" },
                "busy":   { "say": "Wahudumu wote wako kwenye simu, subiri kidogo.", "to": "exit:human" },
                "none":   { "say": "Tutakupigia ndani ya saa moja.", "to": "exit:cb" },
                "closed": { "say": "Tumefunga sasa, tafadhali acha ujumbe.", "to": "exit:vm" } } }
    }
  },
  "x": { "goal": "bye", "human": "desk", "vm": "vm", "cb": "later", "nohelp": "bye", "time": "bye", "err": "desk" }
}

(The Swahili reads: "A kind assistant of Afya Bora Clinic" · "Working hours" · "Monday to Saturday, 8 a.m. to 4 p.m." · "Patient's name" · "Day of the appointment" · "Your appointment is on {ai.date}" · "Hello! How can I help you today?" · "I'm connecting you to an agent now." · "All agents are on calls, please wait a little." · "We'll call you within an hour." · "We're closed now, please leave a message.")

Field Default Description
goal "help the caller" What the conversation is for
persona · guard — Who the AI is; what it must never do
voice · speed · model engine's The realtime voice and model
maxMin 5 1–30 minutes, then the time exit
language the call's {"mode": "FIXED", "languages": ["sw"]} or {"mode": "AUTO", "default": "sw"} (follows the caller; sets {lang})
kb — Knowledge: items (Q/A), docs (ids from 2B, F1), site (an https page, read when the version is pushed). Used only in answer stages; the AI says it doesn't know rather than invent
fields — [{name, desc}]: details to collect; each becomes {ai.<name>}
tools — [{type, name, …}]. name: lower case, digits, _. Types: request + req (a Send request block of this flow, reused) · app action + action + params · transfer + department
canSchedule — {script, version}: lets the AI book a later call (same rules as Schedule call)
start req The first stage
anytime — Leave-when rows checked in every stage
stages req Stage id → stage

Stage:

Field Description
type greet · understand · answer (from knowledge) · collect · act · confirm · handover · custom
say Said on entering the stage
instruction Extra instruction for this stage
collect collect stages: field names (declared in fields)
actions act stages: tool names
leave [{kind, to}]. to: a stage id, or exit:<goal|human|vm|cb|nohelp|time|err>
ho handover stages: {ok, busy, none, closed}, each {say, to}: chosen from the live state of the department behind the human exit (someone free / all busy / nobody online / closed). Defaults: ok, busy → human; none → cb; closed → vm

Leave-when kinds: always (then) · question (asks a question) · intent (wants to book or act) · done (all fields collected) · success · failed (an act stage's tools) · person (asks for a person) · upset.

After the conversation: the transcript (by speaker), a summary and the collected fields → summary.ready. Timeline: AI_VOICE_DISCLOSURE · AI_AGENT_START · AI_STAGE · AI_HEARD · AI_SAID · AI_TOOL · AI_KNOWLEDGE · AI_FIELD · AI_HANDOVER · AI_EXIT · AI_ERROR · AI_ENDED · AI_USAGE.

Rule of thumb: if the AI needs a result to keep talking (check a balance, book a slot), make it a tool. If the AI's part is finished (transfer, pay, goodbye), use an exit to a block.


5.8 What the engine adds by itself

Not settings: don't add blocks for these.


5.9 Check codes

POST /v1/scripts/validate and a push return errors and warnings, each {block, code, message}. block is the block id, or null for the whole flow. An error stops the push; a warning doesn't. Show each on its block; the message is written to be shown as it is.

Code Level Block / field How the author fixes it
DIRECTION_MISSING error flow Choose INCOMING, OUTGOING or SHARED
DIRECTION_MISMATCH error flow A flow keeps its direction: make a new flow instead
START_MISSING · START_UNKNOWN error flow Connect the start to a block
TOO_MANY_BLOCKS error flow At most 60 blocks: move a part into a SHARED flow
UNKNOWN_BLOCK_TYPE error block Engine doesn't know the type (old editor?): delete the block
VARS_IN_INCOMING error flow Incoming flows have no call-out values: remove vars
RETURN_OUTSIDE_SHARED error flow Return blocks only in SHARED flows
EXIT_TARGET_MISSING error block An exit points to a deleted block: reconnect it
EXIT_UNCONNECTED warning block The call ends there. Connect it, or leave it if that is meant
EXIT_UNKNOWN warning block Not an exit of that block (old option id?): remove it
RETRIES error Ask blocks 0–3
MESSAGE_MISSING · MESSAGE_SOURCE · MESSAGE_TEXT error message Write the text or choose audio
MESSAGE_TOO_LONG error message At most 500 characters: split into two messages
MEDIA_MISSING · MEDIA error message, wr.music Choose an audio file; it must be READY and belong to the account
UNKNOWN_VARIABLE error block The {name} isn't produced by any block or declared: fix the name or add the variable
MENU_KEYS error menu 1–10 options
MENU_KEY error menu Keys 0–9, * or #, not repeated; an option without a key needs speech on
MENU_OPTION error menu Two options with the same id
MENU_KEY_LANG error menu An option's lang is sw or en
OPTION_WITHOUT_WORDS warning menu With speech on, give the option words, or it can only be picked by key
SAVE_AS error collect, ask A variable name: letters, digits, _
DIGITS · PATTERN error collect 1–20 digits, min ≤ max; a valid regular expression
POLL_ANSWERS error poll 1–5
PICK_SOURCE · PICK_PAGE error pick A Send request block that waits, or inline items; 1–9 per page
ASK_LENGTH error ask 5–60 seconds
VOICEMAIL_LENGTH error voicemail 5–120 seconds
DEPARTMENT error connect Choose a department of this account
WAIT_GIVE_UP · WAIT_OPTION error wr 10–1800 s; each option a key and an exit
WAIT_OPTIONS_UNANNOUNCED warning wr Tell callers about the keys in the start or update message
REQUEST_URL · REQUEST_HTTPS error request An https URL with a public host
REQUEST_METHOD · REQUEST_BODY · REQUEST_MODE · REQUEST_TIMEOUT error request A listed value; timeout 1–15 s
REQUEST_AUTH error request Fill the auth fields for the chosen type
CALLBACK_URL error request Callback mode: put {callback_url} in the body
ACTION error app_action An action the client app registered, with all its required parameters
CONDITION_TEST error condition hours, calledRecently, contactExists or variable
GOTO_TARGET error goto Choose the block to jump to
TRANSFER_TARGET error transfer A runnable SHARED flow version; fill its inputs
RETURN_EXIT error return An exit name
SCHEDULE_TARGET error schedule, AI canSchedule A runnable OUTGOING flow version
SCHEDULE_TIME error schedule Exactly one of after / at / ask, in the right form
SCHEDULE_TRIES error schedule 1–3
AGENT_START · AGENT_MOVE · AGENT_TOOL · AGENT_FIELD · AGENT_KB · AI_LANGUAGE error ai_agent First stage set; every move goes to a stage or an exit; tools and fields declared; knowledge docs of the account; site https
FEATURE_OFF error ai_agent, menu/poll/pick with smart The client app has no AI feature: use words matching, or remove the AI Agent

Besides the issues, the check returns:


Revision #1
Created 13 November 2026 18:53:52 by Admin Qbit
Updated 13 November 2026 18:54:14 by Admin Qbit