USSD codes & extensions

Two ways onto the dial pad: apply for your own dedicated code, or take an extension on a shared code (*657*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.

Short-term rentals

An extension does not have to be a monthly commitment. Pass rental_days (1–90) on the request and the extension becomes a fixed-term rental: you pay a pro-rata share of the monthly fee upfront (monthly × days ÷ 30, rounded up, VAT inclusive), it goes live on approval, and it simply expires at the end of the term — no auto-renewal, no further charges. Built for POCs, demos and one-day events; a one-day rental of a 4-digit extension costs a thirtieth of the month.

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:

Shared codes listing (the uuid is your ussd_code_id)
{
  "success": true,
  "data": [
    { "uuid": "6a77e0b2-…", "code": "657", "networks": ["safaricom", "airtel", "telkom"] }
  ]
}
# Paste your token once (it starts with mbs_):
export MOBILESASA_TOKEN="mbs_your_token_here"

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 *657*45# for extension 45. 1–2 digit extensions cost more than longer ones.
networksstring[]YesNetworks the extension must answer on.
# 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/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).

# 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/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.

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

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

Renewing before it expires

An extension carries an end_date. It renews itself from your KES wallet a month at a time, and you are warned three days ahead — but you can renew early, for as many months as you like, and see the price first.

GET/api/v1/ussd/extensions/{uuid}/renewal-quote?months=3
POST/api/v1/ussd/extensions/{uuid}/renew
Quote
{
  "months": 3,
  "monthly_fee": 5000,
  "total": 15000,
  "wallet_balance": 8000,
  "shortfall": 7000,
  "sufficient": false,
  "new_end_date": "2026-11-21"
}

The quote is the honest one: it says what you have, what it costs, and what is missing, before anything is charged. If the wallet is short, top it up (M-Pesa or card — see Top up via M-Pesa) and post the renewal. Renewing never shortens an expiry: months are added to whichever is later, today or the current end date.

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

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