Skip to Content
Errors & codes

Errors & codes

Response shapes

OperationJSON shape
Create (async or sync)status, response_code, response_message, data
Statusstate, code, message, payload
Liststate, result.items, result.page_info
Wallet balancestate, result (prepaid balance fields)
Gift cards — catalog / quote / availabilitystatus, code, message, data
Gift cards — purchase / order statusstatus, response_code, response_message, data
Bill validate (verify customer)state, code, message, result
Bill plans catalogstatus, description, data
Bill statusstate, 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)

FieldLocationValues / format
Outcome wordTop-level statussuccess, failed, or pending
Outcome codeTop-level response_code00 success, 01 failed, 02 pending
Human messageTop-level response_messagee.g. Successful, Failed, Pending
Idempotency echodata.request_idSame value you sent as request_ref (or client_request_id)
Sync timestampdata.created_atRFC 3339 on sync create only, e.g. 2026-05-17T10:30:00Z
Async createdataNo 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_message is 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 creates
  • token, units — prepaid electricity on sync bill create (and on status payload)
  • kct1, kct2 — optional 20-digit Key Change Tokens on prepaid electricity when issued for the meter; omitted on most purchases
  • payment_spent_on — optional on electricity purchase data and status payload when the payment cleared meter debt instead of issuing a token (e.g. debt); omitted otherwise. Response-only — do not send on the request
  • merchant_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:

FieldMeaning
statesuccess, failed, or pending
code00, 01, or 02
messageHuman-readable outcome
payloadTransaction 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 styleAlso accepted
merchantmerchant_code
msisdncustomer_msisdn (meter, smartcard, or betting ID for bills)
request_refclient_request_id (create only)
plan_codeplan (bills verify and purchase)

Bill verify (optional)

POST /v1/transactions/verify uses the Subthingy envelope — state, code, message, result:

FieldLocationValues / format
Outcome wordTop-level stateok on success
Outcome codeTop-level code00 on success
Human messageTop-level messagee.g. customer verified
Customer detailsresultproduct, 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.

FieldLocationValues / format
Outcome wordTop-level statussuccess, failed, or pending
Outcome codeTop-level response_code00, 01, or 02
Idempotency echodata.request_idSame value sent on POST /giftcards/orders
Card detailsdata.cardsPresent on success; omitted while pending
Provider refdata.reference_codeUpstream 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:

codestateMeaning
00successSuccessful
01failedFailed
02pendingStill 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

StatusWhen
200Success (including async create with pending body)
400Invalid JSON, validation error, rejected meter/smartcard/betting ID on bill verify, or missing request_ref / request_id / client_request_id on status GET
401Missing or invalid API key / secret
403Credential cannot access this resource
404Merchant or transaction not found
422Duplicate request reference or missing required field
500Server error — retry with backoff
502Bill verify could not be completed — retry with backoff
503Temporary overload (database semaphore) — retry with backoff

Health check

GET /v1/healthz

No authentication required.

Last updated on