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.
| Route | Notes |
|---|---|
POST /v1/transactions | product: airtime or data |
POST /v1/transactions/airtime/async | Forces product: airtime |
POST /v1/transactions/data/async | Forces 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
| Field | Required | Description |
|---|---|---|
merchant or merchant_code | Yes | Your merchant profile |
msisdn or customer_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 or client_request_id | Yes | Your unique idempotency key |
plan_code | Data & bills | Data: 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}| Query | Required | Description |
|---|---|---|
request_ref | Yes* | Your idempotency key from the create call (recommended) |
request_id | Alt. | Same value as request_ref |
client_request_id | Alt. | 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.
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.