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:
| Route | Notes |
|---|---|
POST /v1/transactions/airtime/sync | Airtime only |
POST /v1/transactions/data/sync | Data 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.
| Route | Notes |
|---|---|
POST /v1/transactions | product in body: airtime, data, ELECTRICITY, CABLE, or BETTING |
POST /v1/transactions/airtime/async | Forces product: airtime |
POST /v1/transactions/data/async | Forces product: data |
Bill products (ELECTRICITY, CABLE, BETTING) require plan_code and are documented in Bill payments.
Request body
| Field | Required | Description |
|---|---|---|
merchant | Yes | Your merchant profile |
msisdn | Yes | Subscriber number (234… or 080…) |
network | Yes | mtn, glo, airtel, 9mobile |
product | Yes | airtime, data, ELECTRICITY, CABLE, or BETTING |
amount | Yes | Amount in Naira (string) |
request_ref | Yes | Your unique idempotency key |
plan_code | Data & bills | Data: 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"
}
}state | code | Meaning |
|---|---|---|
success | 00 | Completed successfully |
failed | 01 | Failed |
pending | 02 | Still processing |
List transactions
GET /v1/transactions| Query | Default | Description |
|---|---|---|
page | 1 | Page number |
limit | 10 | Page 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.