Skip to Content
Transactions

Transactions

Sync vs async

Sync (recommended): the API runs vending before responding. You get the final outcome in the create response (response_code 00 or 01 inside status / response_code / data). Use:

RouteNotes
POST /v1/transactions/airtime/syncAirtime only
POST /v1/transactions/data/syncData only (plan_code required)

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 in body: airtime, data, ELECTRICITY, CABLE, or BETTING
POST /v1/transactions/airtime/asyncForces product: airtime
POST /v1/transactions/data/asyncForces product: data

Bill products (ELECTRICITY, CABLE, BETTING) require plan_code and are documented in Bill payments.

Request body

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

Create (sync or async) returns status, response_code, response_message, and data. Status polling uses state, code, message, and payload.

Buy airtime (sync)

curl -X POST "https://api.subthingy.io/v1/transactions/airtime/sync" \ -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": "glo", "product": "airtime", "amount": "200", "request_ref": "subthingy-req-20260517-001" }'

HTTP 200 — successful:

{ "status": "success", "response_code": "00", "response_message": "Successful", "data": { "internal_reference": "019262ab-7c4d-7000-8000-000000000002", "msisdn": "2348012345678", "product": "airtime", "request_id": "subthingy-req-20260517-001", "network": "glo", "amount": "200", "merchant_id": 10, "created_at": "2026-05-17 10:30:00" } }

Buy data (sync)

curl -X POST "https://api.subthingy.io/v1/transactions/data/sync" \ -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": "data", "amount": "500", "request_ref": "subthingy-req-20260517-002", "plan_code": "MTN-8" }'

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.

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

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" } }
statecodeMeaning
success00Completed successfully
failed01Failed
pending02Still processing

List transactions

GET /v1/transactions
QueryDefaultDescription
page1Page number
limit10Page size
msisdnFilter by subscriber
networkFilter by network name (mtn, glo, …)
statusFilter 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