Flows & the Studio

Design USSD journeys visually on the canvas. A flow is a graph of typed nodes executed by the runtime.

The Workflow Studio

The Studio is a drag-and-drop canvas where you compose a flow from nodes and wire them together. Each node carries configuration in its inspector and references session variables captured from user input. Use the built-in simulator to step through a flow before you publish.

Add nodes by dragging them from the palette, double-clicking a palette item, or pressing its number key (19). Collapse the palette with the chevron; a slim rail keeps a re-open button in view.

Node reference

A flow graph supports these node types:

NodePurpose
startEntry point where every session begins
menuShow a list of options to the user
inputCapture free-text input into a variable
conditionBranch on variables / expressions
apiCall an external HTTP API
databaseRead or write your project tables
operationCompute values with safe arithmetic
paymentProcess a payment — coming soon
smsSend an SMS
endTerminate the session

The database node

The database node reads and writes your project tables at runtime. Choose an operation (read / write / update / delete), the target table, a filter and the fields to map into session variables. This is the same data your REST API sees.

Model your tables first (see Database), then reference them from database nodes to keep flow logic and stored data in sync.

Variables & interpolation

Every value a flow captures is stored as a session variable. Insert one into any text field with {{name}}. The caller's number is always available as {{msisdn}}.

SourceVariable
The caller's phone number{{msisdn}}
An input node's stored value{{<variable>}}
An API node's saved response{{<saveAs>}}
A project environment variable{{env.<NAME>}}
input node · data
{
  "prompt": "Enter amount (GHS):",
  "variable": "amount"        // captured input → {{amount}}
}

When a variable holds a structured value — an API response saved with saveAs, or a whole database row — reach into it with a path. Use dots for object fields and either dots or brackets for array indices.

path access
{{member.full_name}}        // object field
{{member.address.city}}     // nested field
{{items[0].id}}             // array element (bracket)
{{items.0.id}}              // array element (dotted)

Whole value vs. field

A bare {{member}} renders the whole value as JSON; add a path to pull out a single field. An exact variable name always wins over a path, and an unknown path renders as empty.

The condition node

A condition node holds an ordered list of branches. Each branch compares a variable against a value; the first match routes through a handle named after that branch's id. If none match, the else handle is taken.

condition node · data
{
  "branches": [
    { "id": "b1", "variable": "amount", "operator": "gt", "value": "0" }
  ]
}
// b1  → amount > 0     else → fallthrough
OperatorTrue when
eqvalue equals
nevalue differs
gt / ltnumeric greater / less than
gte / ltenumeric ≥ / ≤
containssubstring is present
empty / notEmptyvalue is blank / set

The API node

The API node calls an external HTTP endpoint, interpolating variables into the URL, headers and body, then saves the response into the variable named by saveAs. It routes through next on success and error on a non-2xx response or timeout.

Headers and the request body are edited as key/value pairs in the inspector. The body is sent as a JSON object on POST requests; every value supports {{variable}} interpolation.

api node · data
{
  "method": "POST",
  "url": "https://api.example.com/transfer",
  "headers": { "Authorization": "Bearer {{env.API_TOKEN}}" },
  "body": { "msisdn": "{{msisdn}}", "amount": "{{amount}}" },
  "timeoutMs": 8000,
  "saveAs": "transfer"         // response → {{transfer}}
}

Environment variables

Reference secrets such as auth tokens and API keys as {{env.NAME}}. They are configured per environment (Development / Staging / Production) so the same flow uses test credentials in staging and live ones in production — see Deployments & environments.
Always wire the errorhandle so a slow or failing API doesn't dead-end the session.

Database: read & write

Pick an operation and a table. A read matches rows with a filterand maps the first row's columns into variables; write / update apply an assignments map.

database node · read
{
  "operation": "read",
  "collection": "prices",
  "filter": { "name": "{{crop}}" },
  "saveAs": "priceRow"          // columns → {{price}}, {{unit}}, …
}
database node · write
{
  "operation": "write",
  "collection": "deposits",
  "assignments": {
    "amount": "{{amount}}",
    "msisdn": "{{msisdn}}"
  },
  "saveAs": "depositId"         // new row id → {{depositId}}
}

For update and delete, set rowIdto the row's id (often a captured variable).

The operation node

The operation node computes new values from existing variables using a safe, whitelisted arithmetic grammar — never eval. It holds an ordered list of assignments, each writing an expression result into a target variable. Assignments run top-to-bottom, so a later one can build on an earlier target.

operation node · data
{
  "assignments": [
    { "target": "new_balance", "expression": "{{balance}} + {{amount}}" },
    { "target": "display",     "expression": "fixed({{new_balance}} / 100, 2)" }
  ]
}

Expressions support the operators + - * / %, parentheses, and the functions min, max, round, abs, floor, ceil and fixed(x, n). Variables are interpolated with {{...}} before evaluation.

Working with money

Store money as pesewas (integer minor units) to avoid floating-point drift, then format for display with fixed({{balance}} / 100, 2). The result of fixed is a formatted string and cannot be used in further arithmetic.
On an invalid expression the node sets {{opStatus}} to error and stops at the first failure — wire the errorhandle so a bad expression doesn't dead-end the session. On success {{opStatus}} is ok.

The payment node

Payments are coming soon

The payment node lets flows collect mobile money. It's being finished and will be documented here when it ships.

Coming soon

The SMS node

The SMS node queues a message (fire-and-forget, so the USSD response stays fast). It defaults to the caller when to is empty.

sms node · data
{
  "to": "{{msisdn}}",
  "message": "Payment of GHS {{amount}} received."
}

Connections, undo & redo

Drag from a source handle to a target to connect two nodes. To remove a connection, hover the edge and click the × at its midpoint, or select it and press Delete. Every edit is captured in an undo history — reverse and replay changes with the toolbar buttons or Ctrl/⌘+Z and Ctrl/⌘+Shift+Z.

Keyboard shortcuts

The canvas has shortcuts for common actions (they never fire while you're typing in a field). Open the keyboard menu in the toolbar for the full list.

ActionShortcut
Add node by palette position1
Add a node from the paletteDouble-click the palette item
Delete the selected node / edgeDelete
UndoCtrl/⌘+Z
RedoCtrl/⌘+Shift+Z
Run the simulatorCtrl/⌘+Enter

Validation & fixing errors

When you publish (or the simulator can't start), the flow is validated. Problems tied to a specific node are listed as clickable items — selecting one jumps to that node and outlines it in red on the canvas so the fix is obvious. The highlight clears as soon as you edit the graph.

New to building? Follow the Market price lookup tutorial to wire these nodes together end to end.