USSD codes & extensions

Two ways onto the dial pad: apply for your own dedicated *XYZ# code, or take an extension on a shared code (*384*45#) and be live in days instead of weeks.

Endpoints live under /api/v1/ with a scoped mbs_ token or JWT. Once a code or extension is approved, you decide how sessions are answered — a hosted menu, a survey, or callback mode against your own backend.

Fees

Approval checks your wallet — and fails if it can't pay

USSD approvals deduct setup and monthly fees from your KES wallet, and unlike sender IDs the deduction is checked at approval time — an approval with an underfunded wallet fails outright. Fund the wallet before your application reaches the front of the queue. Extension pricing varies with digit length (shorter = pricier); dedicated codes are quoted per application.

Option A — extension on a shared code

GET/api/v1/ussd/shared-codes
GET/api/v1/ussd/shared-codes/{uuid}/availability

List the platform's shared codes, then check whether the extension you want is free:

curl "https://api.mobilesasa.com/api/v1/ussd/shared-codes/6a77e0b2-…/availability?extension=45" \
  -H "Authorization: Bearer $MOBILESASA_TOKEN"
POST/api/v1/ussd/extensions

Parameters

FieldTypeRequiredDescription
ussd_code_iduuidYesUUID of the shared code (from the listing above).
extensionstringYesThe digits after the star — customers dial *384*45# for extension 45. 1–2 digit extensions cost more than longer ones.
networksstring[]YesNetworks the extension must answer on.
curl -X POST https://api.mobilesasa.com/api/v1/ussd/extensions \
  -H "Authorization: Bearer $MOBILESASA_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "ussd_code_id": "6a77e0b2-…",
    "extension": "45",
    "networks": [
      "safaricom",
      "airtel"
    ]
  }'

Option B — dedicated code

POST/api/v1/ussd/codes

A dedicated code (*728#) is registered with the networks in your name — expect network paperwork and a longer lead time. Send JSON for a bare application, or multipart/form-data when attaching supporting documents (authorisation letters, business certificate — 10 MB max per file; the file field name becomes the document type, with an optional network_{field} form value scoping it to one network).

curl -X POST https://api.mobilesasa.com/api/v1/ussd/codes \
  -H "Authorization: Bearer $MOBILESASA_TOKEN" \
  -F "code=*728#" \
  -F "networks=safaricom" \
  -F "networks=airtel" \
  -F "[email protected]" \
  -F "network_request_letter=safaricom"

JSON mode works too when there are no documents: {"code": "*728#", "networks": ["safaricom","airtel"]}.

Track the application

GET/api/v1/ussd/codes
GET/api/v1/ussd/codes/{uuid}
GET/api/v1/ussd/extensions
GET/api/v1/ussd/extensions/{uuid}

Both codes and extensions carry an approval_status (pending → approved | rejected, with rejection_reason on rejects — you're also emailed automatically either way) and a runtime status once live.

curl "https://api.mobilesasa.com/api/v1/ussd/extensions/91c2f7aa-…" \
  -H "Authorization: Bearer $MOBILESASA_TOKEN"

Point it somewhere

PUT/api/v1/ussd/codes/{uuid}
PUT/api/v1/ussd/extensions/{uuid}

Parameters

FieldTypeRequiredDescription
modestringNomenu (hosted menu), survey, or callback (your backend drives every screen — see callback mode).
menu_iduuidNoThe approved menu to serve, when mode is menu.
survey_iduuidNoThe survey to run, when mode is survey.
callback_urlstringNoYour HTTPS endpoint, when mode is callback.
curl -X PUT https://api.mobilesasa.com/api/v1/ussd/extensions/91c2f7aa-… \
  -H "Authorization: Bearer $MOBILESASA_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "mode": "callback",
    "callback_url": "https://example.com/ussd"
  }'