Skip to content

Charges

Scopes: charges:write to create, charges:read to read. Available in the sandbox.

POST /v1/charges

Ask a specific phone to pay. The payer receives a network prompt on their handset and approves it with their mobile-money PIN. They have 15 minutes.

Headers

Header
Idempotency-KeyRequired. 8–128 characters of A–Z a–z 0–9 - _. Use your own order number. See Idempotency.

Body

FieldType
amount_minorintegerrequiredWhat the payer authorises (gross). Whole shillings: a multiple of 100, TZS 100 – 20,000,000.
railstringrequiredmpesa · airtel · tigo · halopesa · azampesa
msisdnstringrequiredThe payer's number: 0759280775, 759280775 or +255759280775.
currencystringoptionalOnly TZS. Defaults to TZS.
referencestringoptionalYour order number. ≤ 200 chars. Appears on the charge and the event.
descriptionstringoptional≤ 500 chars.
metadataobjectoptionalAnything you want back later. Stored as-is.
bash
curl -X POST "$BASE/v1/charges" \
  -H "Authorization: Bearer $KEY" \
  -H "Idempotency-Key: order-20260916-00417" \
  -H "Content-Type: application/json" \
  -d '{
    "amount_minor": 4650000,
    "rail": "mpesa",
    "msisdn": "0759280775",
    "reference": "ORD-00417",
    "description": "2 × kanga, delivery Kariakoo",
    "metadata": { "cart_id": "c_88a1" }
  }'

Response 201

json
{
  "id": "ch_01HZX4Q7V9M2K3N5P6R8S9T0VW",
  "object": "charge",
  "state": "processing",
  "amount_minor": 4650000,
  "fee_minor": 93000,
  "net_minor": 4557000,
  "currency": "TZS",
  "rail": "mpesa",
  "reference": "ORD-00417",
  "description": "2 × kanga, delivery Kariakoo",
  "metadata": { "cart_id": "c_88a1" },
  "created": "2026-09-16T09:14:02.118Z",
  "expires_at": "2026-09-16T09:29:02.118Z"
}

Idempotency is exact-once, not best-effort. A retry with the same key returns the charge created the first time and does not prompt the payer again. The same key with a different body is refused with IDEMPOTENT_MISMATCH.

Refusals: IDEMPOTENCY_REQUIRED, BAD_AMOUNT, BAD_CURRENCY, BAD_RAIL, BAD_MSISDN, AMOUNT_TOO_SMALL, LEDGER_UNAVAILABLE.

GET /v1/charges/{id}

One charge. A charge belonging to another business is NOT_FOUND, identically to one that never existed.

GET /v1/charges

Query: limit (1–100, default 25), starting_after (the last id of the previous page), state (requires_payment · processing · succeeded · failed · expired).

json
{ "object": "list", "has_more": true, "data": [ { "id": "ch_…", "…": "…" } ] }

Lists are cursor-paginated, newest first. Never assume an offset — a list money is being appended to would skip rows.

States

requires_payment ──► processing ──► succeeded

                          ├──────► failed
                          └──────► expired

processing means we do not yet know, and is never a failure. Fulfil on succeeded — by webhook or by a poll returning it — and never on a 201.

Every code, scope and route on this site is rendered from the API's own source.