USSD menus & nodes
Build a dial-pad menu tree we host and serve for you — no server on your side, sub-second screens, with callback nodes where you do need your own logic.
A menu is the container (header text, invalid-input text, approval state); nodes are the tree — each node is one option a caller can pick, and its children are the next screen. Attach the approved menu to a code or extension with mode=menu.
Create the menu
Parameters
| Field | Type | Required | Description |
|---|---|---|---|
| name | string | Yes | Internal label. |
| header_text | string | Yes | The first line of the root screen — "Welcome to Dukapay". |
| invalid_input_text | string | Yes | Shown when the caller picks an option that doesn't exist. |
| description | string | No | Notes for your team and the reviewers. |
curl -X POST https://api.mobilesasa.com/api/v1/ussd/menus \
-H "Authorization: Bearer $MOBILESASA_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Dukapay main menu",
"header_text": "Welcome to Dukapay",
"invalid_input_text": "Invalid choice. Please try again."
}'Add nodes
Parameters
| Field | Type | Required | Description |
|---|---|---|---|
| option_number | string | Yes | The digit(s) the caller presses to pick this node — "1", "2", "0". |
| label | string | Yes | The text shown next to the option number in the parent screen. |
| type | string | Yes | What happens when picked — see the node types below. |
| display_text | string | No | The screen this node shows: the header of its sub-menu, the final text of a message node, or the prompt of an input node. |
| parent_id | uuid | No | UUID of the parent node. Omit for a root-level option. |
| callback_url | string | No | For callback nodes — where the session is handed to your backend. |
| sort_order | int | No | Display order among siblings. |
Node types
| Field | Type | Required | Description |
|---|---|---|---|
| submenu | branch | No | Shows display_text as a header plus its children as numbered options. |
| message | terminal | No | Shows display_text and ends the session — balances, confirmations, goodbyes. |
| input | collect | No | Prompts with display_text and captures whatever the caller types (an amount, an account number); its child nodes continue the flow. |
| callback | hand-off | No | Forwards the session to your callback_url — from here your backend drives the screens, same contract as callback mode. |
| back | navigation | No | Returns the caller to the previous screen. Convention: option_number "0". |
curl -X POST https://api.mobilesasa.com/api/v1/ussd/menus/4b8e19c3-…/nodes \
-H "Authorization: Bearer $MOBILESASA_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"option_number": "1",
"label": "Check balance",
"type": "callback",
"callback_url": "https://example.com/ussd/balance",
"sort_order": 1
}'Build depth by chaining parent_id: create "2. My account" as a submenu, then create its options with parent_id set to that node's UUID. Nodes can be updated (PUT /ussd/menus/nodes/{uuid}) and deleted (DELETE) while the menu is in draft.
Simulate before submitting
Walk the tree exactly as a phone would — ussd_string is the caller's input path so far ("1", then "1*3", …):
curl -X POST https://api.mobilesasa.com/api/v1/ussd/simulate \
-H "Authorization: Bearer $MOBILESASA_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"service_code": "*384*45#",
"session_id": "sim-001",
"ussd_string": "1",
"network": "safaricom"
}'{
"success": true,
"data": {
"prompt": "My account\n1. Balance\n2. Statements\n0. Back",
"end_session": false
}
}Submit for approval
Menus go through a quick content review before serving live traffic (you're emailed on approval or rejection, with the reason). A pending or approved menu is locked— nodes can't change. To edit a live menu, call revise: it unlocks a draft revision you edit and re-submit, while the approved version keeps serving until the revision replaces it.
curl -X POST https://api.mobilesasa.com/api/v1/ussd/menus/4b8e19c3-…/submit \
-H "Authorization: Bearer $MOBILESASA_TOKEN" \
-H "Content-Type: application/json" \
-d '{}'Test-only whitelist
Restrict a menu to your test numbers while iterating: {"msisdns": ["254712345678"]} — an empty array lifts the restriction.
Menu, survey or callback?
callback nodes so only the dynamic screens hit your server.