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.
# Paste your token once (it starts with mbs_):
export MOBILESASA_TOKEN="mbs_your_token_here"

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".
# Paste your token once (it starts with mbs_):
export MOBILESASA_TOKEN="mbs_your_token_here"

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", …):

# Paste your token once (it starts with mbs_):
export MOBILESASA_TOKEN="mbs_your_token_here"

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
  }
}

Go live

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

A new menu is offline until you call submit, which puts it live instantly: a menu costs nothing and answers on your own extension, so there is nothing to review. Editing never locks. Nodes on a live menu change in place and callers see the new text on their next dial. revise takes a live menu offline again (callers get a short unavailable message) while you rework it.

# Paste your token once (it starts with mbs_):
export MOBILESASA_TOKEN="mbs_your_token_here"

curl -X POST https://api.mobilesasa.com/api/v1/ussd/menus/4b8e19c3-…/submit \
  -H "Authorization: Bearer $MOBILESASA_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{}'

Start from a template

GET/api/v1/ussd/menu-templates
POST/api/v1/ussd/menus/{uuid}/template

Three starter trees, so a menu never begins empty: information (about us, contacts, opening hours), service_request (report a fault, request a callback, check a ticket) and mobile_money (balance, pay, statement, PIN). Applying a template adds its nodes to an existing menu; you then edit them like any other node. The portal's extension wizard uses the same call.

# Paste your token once (it starts with mbs_):
export MOBILESASA_TOKEN="mbs_your_token_here"

curl -X POST https://api.mobilesasa.com/api/v1/ussd/menus/4b8e19c3-…/template \
  -H "Authorization: Bearer $MOBILESASA_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "template": "service_request"
  }'

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.