Top up your wallet and SMS units

Your account has two balances: SMS units, which every message spends, and a KES wallet for fees. You can fund either one over the API or from the M-Pesa menu, and move money from the wallet into units whenever you like.

SMS units are sending credit: one unit per SMS part, bought at your account's rate. The wallet holds KES for one-time and service fees such as sender ID registrations, shortcode deposits and USSD fees, and it can be converted into units at any time. Both balances come back from GET /api/v1/units/balance.

Token scope

With an mbs_ API token, every call on this page needs the team:wallet:topup scope; a token without it gets a 403. See Authentication. Signed-in portal users are not affected.

Which route to use

  • Automated top-ups: keep the wallet funded, then buy units from it when your balance runs low. One API call, no payer involved. See wallet to SMS units.
  • Buy units directly: send an M-Pesa prompt for units, or pay the paybill with your plain account number.
  • Fund the wallet: send an M-Pesa prompt for the wallet, or pay the paybill with wl- in front of your account number.

Wallet to SMS units

POST/api/v1/units/purchase

Converts wallet KES into SMS units at your account's rate, in one step and immediately. This is the call to automate: check your unit balance on a schedule and buy more when it drops below your threshold.

Parameters

FieldTypeRequiredDescription
unitsintegerYesHow many SMS units to buy. The cost is units × your SMS rate, taken from the wallet.
# Paste your token once (it starts with mbs_):
export MOBILESASA_TOKEN="mbs_your_token_here"

curl -X POST https://api.mobilesasa.com/api/v1/units/purchase \
  -H "Authorization: Bearer $MOBILESASA_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "units": 10000
  }'
Response: 200
{
  "success": true,
  "data": {
    "units": 10000,
    "cost_kes": 4000.00,
    "sms_rate": 0.40,
    "sms_balance": 25430,
    "wallet_balance": 1200.00
  }
}

sms_balance and wallet_balance are the new balances after the purchase, so you do not need a second call to check them.

Response: 402, the wallet does not cover it
{
  "success": false,
  "error": "Insufficient balance to complete this action."
}

A simple top-up loop

Every few minutes, read sms_balance from GET /api/v1/units/balance. When it falls below your floor, call POST /api/v1/units/purchase. If that returns 402, fund the wallet with an M-Pesa prompt (below) and try again once the deposit lands.

Buy SMS units with M-Pesa

POST/api/v1/units/stk-push

Sends an M-Pesa PIN prompt to the payer. When they approve it, the payment becomes SMS units at your rate: the amount divided by your SMS rate, rounded down.

Parameters

FieldTypeRequiredDescription
phonestringYesThe Safaricom number to charge. The payer gets the PIN prompt on this phone. Any accepted format.
amountnumberYesKES amount, 1 to 250,000 (the M-Pesa per-transaction ceiling).
# Paste your token once (it starts with mbs_):
export MOBILESASA_TOKEN="mbs_your_token_here"

curl -X POST https://api.mobilesasa.com/api/v1/units/stk-push \
  -H "Authorization: Bearer $MOBILESASA_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "phone": "0712345678",
    "amount": 5000
  }'
Response: 200
{
  "success": true,
  "data": { "message": "STK push sent. Check your phone to complete payment." }
}

Fund the wallet with M-Pesa

POST/api/v1/wallet/stk-push

Same request and response as the units prompt; the payment lands in the KES wallet instead.

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

curl -X POST https://api.mobilesasa.com/api/v1/wallet/stk-push \
  -H "Authorization: Bearer $MOBILESASA_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "phone": "0712345678",
    "amount": 5000
  }'

Credits are asynchronous

A 200 from either prompt means the PIN prompt was sent, not that money moved. The balance is credited once the payment is confirmed with Safaricom, usually within seconds of the payer entering their PIN. Confirm it on the ledgers (below).

Pay from the M-Pesa menu (paybill)

Anyone can top up from their phone, no API involved. The account reference decides which balance the money goes to:

Paybill 4078003

FieldTypeRequiredDescription
Account: your account numberSMS unitsNoYour team's local_account_no, exactly as GET /api/v1/units/balance returns it. The payment becomes SMS units at your rate.
Account: wl- + your account numberKES walletNoFor example wl- followed by your account number. The payment goes to the wallet.

Check the account reference

A plain account number buys SMS units, not wallet credit. If you meant to fund the wallet for fees, put wl- in front. Either way the money stays on your account, and you can always convert wallet to units with POST /api/v1/units/purchase.

Confirming a top-up

Each balance has its own ledger. Match on the M-Pesa receipt number:

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

curl "https://api.mobilesasa.com/api/v1/units/wallet/ledger?page=1&page_size=10" \
  -H "Authorization: Bearer $MOBILESASA_TOKEN"
Response: 200
{
  "success": true,
  "data": [
    {
      "uuid": "f302…",
      "entry_type": "deposit",
      "amount": 5000.00,
      "description": "M-Pesa deposit",
      "mpesa_receipt": "SGH8KL2M9Q",
      "created_at": "2026-07-11T14:03:51Z"
    }
  ],
  "meta": { "page": 1, "page_size": 10, "total": 1 }
}