Errors & codes
Response shapes
| Operation | JSON shape |
|---|---|
| Create (async or sync) | status, response_code, response_message, data |
| Status | state, code, message, payload |
| List | state, result.items, result.page_info |
| Wallet balance | state, result (prepaid balance fields) |
| Gift cards — catalog / quote / availability | status, code, message, data |
| Gift cards — purchase / order status | status, response_code, response_message, data |
| Bill validate (verify customer) | state, code, message, result |
| Bill plans catalog | status, description, data |
| Bill status | state, code, message, payload |
Field conventions (airtime, data, electricity, cable, betting)
These rules apply to every transaction product on the external API.
Create (sync or async)
| Field | Location | Values / format |
|---|---|---|
| Outcome word | Top-level status | success, failed, or pending |
| Outcome code | Top-level response_code | 00 success, 01 failed, 02 pending |
| Human message | Top-level response_message | e.g. Successful, Failed, Pending |
| Idempotency echo | data.request_id | Same value you sent as request_ref (or client_request_id) |
| Sync timestamp | data.created_at | RFC 3339 on sync create only, e.g. 2026-05-17T10:30:00Z |
| Async create | data | No created_at on the immediate pending response |
Optional data fields you may see:
telco_message— common on sync airtime and data. On a failed bill,telco_messageis the biller reason when one was returned (for example a meter that cannot be paid online, or an amount below the minimum)plan— plan code echoed on data and bill createstoken,units— prepaid electricity on sync bill create (and on statuspayload)kct1,kct2— optional 20-digit Key Change Tokens on prepaid electricity when issued for the meter; omitted on most purchasespayment_spent_on— optional on electricity purchasedataand statuspayloadwhen the payment cleared meter debt instead of issuing a token (e.g.debt); omitted otherwise. Response-only — do not send on the requestmerchant_name— on some status payloads
Bill products (ELECTRICITY, CABLE, BETTING) are always sync on create — expect response_code 00 or 01, not pending 02. Airtime and data on POST /v1/transactions without a /sync route are async (02 pending until polled).
Telco data.network in responses is typically uppercase (MTN, GLO, …) even when the request used lowercase.
Status poll (Subthingy envelope)
Status uses a different JSON shape from create — state, code, message, payload:
| Field | Meaning |
|---|---|
state | success, failed, or pending |
code | 00, 01, or 02 |
message | Human-readable outcome |
payload | Transaction details (same fields as create data, plus extras) |
payload.created_at and payload.updated_at use YYYY-MM-DD HH:MM:SS.
Bill extras on success: prepaid electricity may include payload.token, payload.units, and optionally payload.kct1 / payload.kct2 (Key Change Tokens — load on the meter before the recharge token when present). When payment cleared meter debt instead of issuing a token, payload.payment_spent_on may be present (e.g. debt) with no token.
Request field aliases
Transaction create and bill verify accept either naming style:
| Subthingy style | Also accepted |
|---|---|
merchant | merchant_code |
msisdn | customer_msisdn (meter, smartcard, or betting ID for bills) |
request_ref | client_request_id (create only) |
plan_code | plan (bills verify and purchase) |
Bill verify (optional)
POST /v1/transactions/verify uses the Subthingy envelope — state, code, message, result:
| Field | Location | Values / format |
|---|---|---|
| Outcome word | Top-level state | ok on success |
| Outcome code | Top-level code | 00 on success |
| Human message | Top-level message | e.g. customer verified |
| Customer details | result | product, network, msisdn, customer_name, plus product-specific fields (electricity: customer_address, min_amount, max_amount, meter_number, arrears, account_type, meter_type, district, business_unit, district_reference, tariff when the disco returns them) |
No request_ref on verify. See Bill payments — Verify customer.
On rejected meters, smartcards, or betting IDs, HTTP is 400 with status error, integer code 400, and message set to the rejection reason (for example that the meter is not a valid Ibadan Electric prepaid meter). That is a customer-data problem, not a gateway failure — do not treat it as 502. Verify outages are 502 with a generic message.
On dedicated sync routes (/transactions/airtime/sync, /transactions/data/sync), product is set by the route.
Gift cards
Gift card routes use the same authentication and response envelopes as the rest of the Subthingy API.
Catalog, quote, availability
Uses status, code (HTTP-style integer, e.g. 200), message, and data.
Purchase and order status
Uses the same envelope as transaction create: status, response_code, response_message, and data.
| Field | Location | Values / format |
|---|---|---|
| Outcome word | Top-level status | success, failed, or pending |
| Outcome code | Top-level response_code | 00, 01, or 02 |
| Idempotency echo | data.request_id | Same value sent on POST /giftcards/orders |
| Card details | data.cards | Present on success; omitted while pending |
| Provider ref | data.reference_code | Upstream order reference when available |
GET /giftcards/orders/{request_id} returns the same envelope as purchase — poll until response_code is 00 or 01.
Transaction codes
On status, outcomes map to code / state:
code | state | Meaning |
|---|---|---|
00 | success | Successful |
01 | failed | Failed |
02 | pending | Still processing |
Failed example (status):
{
"state": "failed",
"code": "01",
"message": "Failed",
"payload": {
"request_id": "subthingy-req-20260517-001",
"internal_reference": "019262ab-7c4d-7000-8000-000000000001"
}
}On async create, pending is returned immediately with HTTP 200 (not 202):
{
"status": "pending",
"response_code": "02",
"response_message": "Pending",
"data": { }
}HTTP status codes
| Status | When |
|---|---|
200 | Success (including async create with pending body) |
400 | Invalid JSON, validation error, rejected meter/smartcard/betting ID on bill verify, or missing request_ref / request_id / client_request_id on status GET |
401 | Missing or invalid API key / secret |
403 | Credential cannot access this resource |
404 | Merchant or transaction not found |
422 | Duplicate request reference or missing required field |
500 | Server error — retry with backoff |
502 | Bill verify could not be completed — retry with backoff |
503 | Temporary overload (database semaphore) — retry with backoff |
Health check
GET /v1/healthzNo authentication required.