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: GET /v1/scripts/schema ( 2A, A2 ): block types, groups, exits and which need approval, generated from the engine's code. Build the block library from it. POST /v1/scripts/validate ( 2A, A3 ): the same checks as a push. Run it on every save and show the issues on the blocks ( §5.9 ). Conventions in this page: Default is what the engine uses when the field is absent. Leave a field out rather than sending the default. A setting marked req must be present, or the check reports an error. ⚠ next to a block = a risky block: a version with it should be approved before it runs ( Part 1 §1.6 ). 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.} 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 Any string that is unique in the flow ( menu , pay_1 , b7 ). The id is what exits point to, what the timeline names, and what per-block stats count by ( 2B, D9 ). Keep an id when the block is edited, so stats stay comparable across versions. Make a new id only for a new block. 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 x maps each exit name to the id of the next block. An exit that is missing or empty ends the call with the system goodbye ("Asante kwa kupiga simu. Kwaheri." / "Thank you for calling. Goodbye."). The check warns EXIT_UNCONNECTED for each one, and the timeline records EXIT_UNCONNECTED when a call ends there. A warning is not an error: sometimes ending there is what the author wants. Loops are allowed. An exit may point back to an earlier block or to the block itself ("press 0 to repeat", back to the menu). A loop is stopped only when it goes 25 blocks in a row without the caller pressing a key, speaking or recording ( LOOP_GUARD ), so a caller going round a menu is never cut off. Exit names a block doesn't have get the warning EXIT_UNKNOWN ; they're ignored. Not warned when unwired: later (optional), and exits the block can't take as set up ( notUnderstood on a keys-only menu; Schedule's notUnderstood without ask , declined without confirmation). 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 {} the call-out person's values (OUTGOING vars ) {name} , {amount} {input.} Collect number {input.account} {answer.} Ask question {answer.problem} {poll.} Poll ( saveAs , else the block id) {poll.rating} = 4 {choice.label} · {choice.index} · {choice.} 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..outcome} · {action..} App action that waits (the data of its result) {action.pay.receipt} {ai.} AI Agent fields {ai.date} {schedule.when} · {schedule.at} Schedule call "kesho saa tatu asubuhi" · 2026-11-14T09:00 {in.} inside a SHARED flow: what the Transfer block passed {in.account} {queue.position} Connect, while waiting 3 How they're read aloud: money variables are read with thousands separators: 45000 → "45,000". Numbers in {input.*} and {poll.*} are read digit by digit, in words. Sensitive values (Collect with sensitive ) are usable in the call but shown as •••• in the timeline, webhooks and exports. 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) Keys always work. A Menu, Poll or Pick also listens for speech when it has a speech setting; Collect when speech: true ; Ask question always listens. The caller can press a key or start talking during the prompt: the prompt stops (barge-in). After all retries, the block leaves through its give-up exit ( noInput , notUnderstood , failed or silence ). later exit (Menu, Collect, Poll, Pick): when it is wired, words like "baadaye", "niko busy", "sina muda", "later", "call me back" take it instead of a retry. Wire it to a Schedule call block. When it isn't wired, those words are treated like any other answer. 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 : words : the caller's words are compared with each option's words (exact or close). smart : words first; if none match, the AI picks the option the caller meant from labels and words ("nataka kuongea na mtu wa malipo", "I want to talk to someone about payments"). Below minConfidence → not understood. Needs the client app's AI feature. Without it, the push is refused; if AI is taken away later, calls fall back to words . 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.} . 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.} . 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.} , 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.} 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: 5 minutes to 30 days ahead. A time too soon is moved up to the earliest allowed minute. Quiet hours move the time, and the read-back says so. A new booking for the same caller and flow replaces the old one; at most 3 waiting per caller. Times are understood by the engine's rules first ("kesho saa tatu asubuhi", "baada ya saa moja", "tomorrow at 9"). Other wordings ("tarehe 30") use the AI, only when the client app has AI . 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: Signed requests carry X-Voice-Timestamp , X-Voice-Signature (hex HMAC-SHA256 of timestamp.body ), X-Voice-Idempotency-Key and X-Voice-Call-Id . JSON bodies get a meta object ( callId , callOutId , block ). 2xx = success . Any other status, a redirect (never followed), a reply over 64 KB, a blocked address or a network error = failure . No reply in time = timeout . A top-level say in the reply becomes {reply.say} . A top-level items array feeds Pick from list. callback mode: the body must contain {callback_url} (check CALLBACK_URL ). The business POSTs the result there before wr.giveUpSec . "result" or "status" set to failure , failed , error , declined or cancelled → failure ; anything else → success . Secrets ( auth.token , auth.password , auth.value ) are encrypted when the version is pushed and come back as "(hidden)" from D6 . Push "(hidden)" back unchanged to keep the stored secret of the same block id in the latest version; send a new value to replace it. A "(hidden)" on a block that had no secret is refused ( REQUEST_AUTH ). 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 : Action that doesn't wait: next . A bad parameter or unknown action → failed . Action that waits: each registered outcome ( paid , failed_pin …) · failed · timeout (the action's timeoutSec , default 120) · each wr.options[].exit . Variables : {action..outcome} and each key of the result's data as {action..} . 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.} 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: 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. AI-voice notice : a call that will play generated voice starts with one line, "Habari, unazungumza na huduma ya kidijitali ya {business}." ("Hello, you're speaking with {business}'s digital service."), worded per account. Skipped when the call plays only uploaded audio. Recording notice : once per call, before the first recorded part, when the account records. "Sorry" and goodbye : if something breaks during a call, the caller hears "Samahani, kuna tatizo kwa sasa…" ("Sorry, there's a problem right now…") and the call ends ( SYSTEM_ERROR ). The caller never hears silence. Retry lines : "Chaguo hilo si sahihi." ("That option is not valid.") for a wrong key; "Samahani, sijaelewa. Unaweza kusema: …" ("Sorry, I did not understand. You can say: …") with the option labels; "Namba uliyoingiza si sahihi." ("The number you entered is not valid.") in Collect; "Samahani, sikusikia vizuri. Tafadhali sema tena." ("Sorry, I did not hear that well. Please say it again.") in Ask question. 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: texts : the fixed texts that will be made into audio; vars : the declared call-out values (for the call-out upload screen); riskyBlocks : the blocks that need approval ( 2B, D7 ).