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

POST/api/v1/ussd/menus

Parameters

FieldTypeRequiredDescription
namestringYesInternal label.
header_textstringYesThe first line of the root screen, e.g. "Welcome to Dukapay".
invalid_input_textstringYesShown when the caller picks an option that doesn't exist.
descriptionstringNoNotes 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

POST/api/v1/ussd/menus/{uuid}/nodes

Parameters

FieldTypeRequiredDescription
option_numberstringYesThe digit(s) the caller presses to pick this node, e.g. "1", "2", "0".
labelstringYesThe text shown next to the option number in the parent screen.
typestringYesWhat happens when picked. See the node types below.
display_textstringNoThe 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_iduuidNoUUID of the parent node. Omit for a root-level option.
callback_urlstringNoFor callback nodes, where the session is handed to your backend.
sort_orderintNoDisplay order among siblings.

Node types

FieldTypeRequiredDescription
submenubranchNoShows display_text as a header plus its children as numbered options.
messageterminalNoShows display_text and ends the session. Balances, confirmations, goodbyes.
inputcollectNoPrompts with display_text and captures whatever the caller types (an amount, an account number); its child nodes continue the flow.
callbackhand-offNoForwards the session to your callback_url: from here your backend drives the screens, same contract as callback mode.
backnavigationNoReturns 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

POST/api/v1/ussd/simulate

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": "*657*45#",
    "session_id": "sim-001",
    "ussd_string": "1",
    "network": "safaricom"
  }'
Response: 200
{
  "success": true,
  "data": {
    "prompt": "My account\n1. Balance\n2. Statements\n0. Back",
    "end_session": false
  }
}

Submit for approval

POST/api/v1/ussd/menus/{uuid}/submit
POST/api/v1/ussd/menus/{uuid}/revise

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

GET/api/v1/ussd/menus/{uuid}/whitelist
PUT/api/v1/ussd/menus/{uuid}/whitelist

Restrict a menu to your test numbers while iterating: {"msisdns": ["254712345678"]}: an empty array lifts the restriction.

Menu, survey or callback?

Static information trees → hosted menu (this page). Structured Q&A with stored answers → survey. Anything dynamic per caller (balances, payments) → callback mode, or a menu whose leaves are callback nodes so only the dynamic screens hit your server.