Skip to Content
TransactionsAirtime Async

Airtime Async

Async: the API stores the transaction, enqueues it, and returns immediately with HTTP 200 and response_code 02 (pending). Poll status until final.

RouteNotes
POST /v1/transactionsproduct: airtime or data
POST /v1/transactions/airtime/asyncForces product: airtime
POST /v1/transactions/data/asyncForces product: data

For sync creates (final outcome in the create response), see Airtime.

Bill products (ELECTRICITY, CABLE, BETTING) are sync on POST /v1/transactions — see Bill payments.

Request body

FieldRequiredDescription
merchant or merchant_codeYesYour merchant profile
msisdn or customer_msisdnYesSubscriber number (234… or 080…)
networkYesmtn, glo, airtel, 9mobile
productYesairtime, data, ELECTRICITY, CABLE, or BETTING
amountYesAmount in Naira (string)
request_ref or client_request_idYesYour unique idempotency key
plan_codeData & billsData: prefixed code from Data plans (e.g. MTN-8). Bills: catalog id from Electricity, Cable TV, or Betting. Legacy tariff/product ids still work for existing clients.

Create returns status, response_code, response_message, and data. Status polling uses Subthingy’s own envelope: state, code, message, and payload (not the same shape as create). See Field conventions.

Buy airtime (async)

curl -X POST "https://api.subthingy.io/v1/transactions" \ -H "Content-Type: application/json" \ -H "X-Merchant-Key: mk_your_key_id" \ -H "X-Merchant-Secret: your_issued_secret" \ -d '{ "merchant": "YOUR_MERCHANT", "msisdn": "2348012345678", "network": "mtn", "product": "airtime", "amount": "100", "request_ref": "subthingy-req-20260517-003" }'

HTTP 200 — pending (poll status):

{ "status": "pending", "response_code": "02", "response_message": "Pending", "data": { "internal_reference": "019262ab-7c4d-7000-8000-000000000001", "msisdn": "2348012345678", "product": "airtime", "request_id": "subthingy-req-20260517-003", "network": "MTN", "amount": "100", "plan": "", "merchant_id": 10 } }

Check status

Use after async creates, or to re-fetch an outcome. Pass the same request_ref you sent on create as a query parameter (not in the URL path).

GET /v1/transactions/status?request_ref={request_ref}
QueryRequiredDescription
request_refYes*Your idempotency key from the create call (recommended)
request_idAlt.Same value as request_ref
client_request_idAlt.Same value — alternate alias for request_ref

*One of request_ref, request_id, or client_request_id is required.

Example — poll after an async airtime create:

curl "https://api.subthingy.io/v1/transactions/status?request_ref=subthingy-req-20260517-003" \ -H "X-Merchant-Key: mk_your_key_id" \ -H "X-Merchant-Secret: your_issued_secret"

If you used sync create (Airtime) and received response_code 00 or 01, you already have the final outcome — status polling is optional.

HTTP 400 — missing query param:

{ "status": "error", "code": 400, "message": "client_request_id, request_id, or request_ref query parameter is required" }

HTTP 200:

{ "state": "success", "code": "00", "message": "Successful", "payload": { "internal_reference": "019262ab-7c4d-7000-8000-000000000001", "msisdn": "2348012345678", "product": "airtime", "request_id": "subthingy-req-20260517-003", "network": "MTN", "amount": "100", "merchant_id": 10, "merchant_name": "YOUR_MERCHANT", "created_at": "2026-05-17 10:30:00", "updated_at": "2026-05-17 10:31:15" } }

payload timestamps use YYYY-MM-DD HH:MM:SS. Optional fields include telco_message and bill-specific token / units. See Field conventions.

statecodeMeaning
success00Completed successfully
failed01Failed
pending02Still processing

List transactions

GET /v1/transactions
QueryDefaultDescription
page1Page number
limit10Page size
msisdn—Filter by subscriber
network—Filter by network name (mtn, glo, …)
status—Filter by status code (integer)
curl "https://api.subthingy.io/v1/transactions?page=1&limit=20" \ -H "X-Merchant-Key: mk_your_key_id" \ -H "X-Merchant-Secret: your_issued_secret"

HTTP 200:

{ "state": "ok", "result": { "items": [], "page_info": { "total_items": 0, "page": 1, "page_size": 20, "total_pages": 0 } } }

Idempotency

Reusing the same request_ref for a merchant returns 422. Query status instead of resubmitting.

Last updated on