ARHMSGH
v2 · REST · JSON

ARHMSGH Developer API

Sell data bundles, airtime, result checkers, AFA registrations and utility bills from your own app — charged to your ARHMSGH wallet at API pricing. The catalogue endpoints (/api/v2/packages, /api/v2/results-checker/types, /api/v2/afa/pricing) return the exact price you will be charged.

Base URLhttps://arhmsgh.com
Authorization: kf_live_your_api_key_here

Ghana networks only. This API serves MTN, Telecel and AT (AirtelTigo) Ghana numbers in the 0XXXXXXXXX format. International numbers and other networks are not supported.

1

Authentication

Every request carries your API key in the Authorization header. There is no OAuth flow and no request signing.

Header
Authorization: kf_live_your_api_key_here

A Bearer prefix is also accepted, so Authorization: Bearer <key> works too.

Standard key

kf_live_your_api_key_here

Data, airtime, result checkers, AFA, wallet and order status. Charged to your main wallet.

Commission Services key

kf_cs_live_your_commission_key_here

Utility bills and commission earnings. Pay at face value and earn a share of the fee.

Getting a key

  1. 1Log in and open Dashboard → Developer API. Any account can request a key — customer, agent or dealer — but it only works once an admin has enabled your account as an API user.
  2. 2Generate a Standard or Commission Services key.
  3. 3Copy it immediately. It is shown once and stored only as a hash — it cannot be shown again.
  4. 4New keys wait for admin approval. Until then, requests return 403 "API key pending admin approval".

Call the API from your server only.

The API allows requests from any origin, so a call from browser JavaScript will succeed — and hand your key to anyone who opens DevTools. Keep the key in an environment variable on your backend. Use arhmsgh.com, never www.arhmsgh.com: the www host redirects, and the redirect drops your Authorization header.

2

Response Format

Every response is JSON with the same envelope. Check success first, then read data or error.

Success
{
  "success": true,
  "data": { ... },
  "meta": {
    "timestamp": "2026-09-12T10:30:00.000Z",
    "version": "v2"
  }
}
Error
{
  "success": false,
  "error": {
    "code": 402,
    "message": "Insufficient wallet balance. Top up your wallet and retry."
  }
}

The HTTP status always matches error.code, so you can branch on either.

3

Rate Limits

Limits are per API key, per endpoint group, over a sliding 60-second window. Exceeding one returns 429 with a Retry-After header.

GroupEndpointsRequests / min
Purchasedata/purchase, airtime/purchase, results-checker/purchase, afa/register20
Bulkdata/bulk10
Balancewallet/balance, packages, catalogues60
Statusorders/{reference}60
Billersutilities/billers30
Lookuputilities/lookup10
Payutilities/pay6
Bill ordersutilities/orders/{reference}30
Commissioncommission/balance, commission/transactions30

These are defaults and may be adjusted. Bill lookups and payments are deliberately tight: a lookup is a paid provider call, and a payment cannot be recalled.

4

Data Bundles

Standard key. Orders are charged to your main wallet at your role pricing.

List packages

GET/api/v2/packages

Every available bundle with your role-specific price. Call this first — it is the only way to know which network/size pairs exist.

Query: ?network=MTN

cURL
curl -X GET "https://arhmsgh.com/api/v2/packages?network=MTN" \
  -H "Authorization: $ARHMS_API_KEY"

Buy a bundle

POST/api/v2/data/purchase

Charges your wallet atomically. reference is your idempotency key — sending the same one twice returns the existing order without charging again.

cURL
curl -X POST https://arhmsgh.com/api/v2/data/purchase \
  -H "Authorization: $ARHMS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"network":"MTN","volume_gb":5,"recipient":"0551617309","reference":"order_001"}'

Buy up to 100

POST/api/v2/data/bulk

Every order is validated and priced before the wallet is touched, so a bad entry costs nothing.

cURL
curl -X POST https://arhmsgh.com/api/v2/data/bulk \
  -H "Authorization: $ARHMS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"orders":[{"network":"MTN","volume_gb":5,"recipient":"0551617309","reference":"b_001"},{"network":"Telecel","volume_gb":2,"recipient":"0201234567","reference":"b_002"}]}'
5

Airtime

Standard key. MTN, Telecel and AT, priced with your ordinary role fee.

Send airtime

POST/api/v2/airtime/purchase

Networks: MTN, Telecel, AT. Priced with your ordinary role fee, same as the dashboard. Set use_exact_amount to charge the fee on top instead of taking it out of the amount. The provider will not send less than GHS 1.00, and by default the fee comes OUT of amount — so send at least GHS 1.06, or set use_exact_amount and send 1.00.

cURL
curl -X POST https://arhmsgh.com/api/v2/airtime/purchase \
  -H "Authorization: $ARHMS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"network":"MTN","amount":10,"recipient":"0551617309","reference":"air_001"}'
6

Results Checker

Standard key. WAEC and BECE PINs, sold from stock we hold and returned in the response.

Checker catalogue

GET/api/v2/results-checker/types

WAEC and BECE checkers with your role price, bulk tiers and live stock. Vouchers are sold from stock we hold, so available: 0 means a purchase will fail rather than back-order.

cURL
curl -X GET "https://arhmsgh.com/api/v2/results-checker/types" \
  -H "Authorization: $ARHMS_API_KEY"

Buy checkers

POST/api/v2/results-checker/purchase

Settles immediately — the PINs are in the response, no polling. Up to 50 per request. Out of stock returns 409 and your wallet is untouched.

cURL
curl -X POST https://arhmsgh.com/api/v2/results-checker/purchase \
  -H "Authorization: $ARHMS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"type_id":"a3f1c2d4-5e6f-7081-92a3-b4c5d6e7f809","quantity":2,"reference":"rc_001"}'
7

AFA Registration

Standard key. An agent files each registration by hand, so it settles in hours rather than seconds.

AFA price & fields

GET/api/v2/afa/pricing

What a registration costs you, plus the accepted ID types with their formats and the valid regions. Build your form from this rather than hardcoding the lists.

cURL
curl -X GET "https://arhmsgh.com/api/v2/afa/pricing" \
  -H "Authorization: $ARHMS_API_KEY"

Register AFA

POST/api/v2/afa/register

MTN AFA 30-day registration. Charges your wallet and files the application — there is no upstream API, an agent completes it by hand, so status stays pending until then. Applicant must be 18+ and the id_number must match the id_type format.

cURL
curl -X POST https://arhmsgh.com/api/v2/afa/register \
  -H "Authorization: $ARHMS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"full_name":"Kwame Mensah","phone":"0551617309","id_type":"Ghana Card","id_number":"GHA-123456789-0","date_of_birth":"1996-04-12","region":"Ashanti","location":"Kumasi","reference":"afa_001"}'
8

Account & Orders

Standard key. Your wallet balance, and the status of any order you have placed.

Wallet balance

GET/api/v2/wallet/balance

Your spending balance in GHS. Top up from the dashboard wallet page.

cURL
curl -X GET "https://arhmsgh.com/api/v2/wallet/balance" \
  -H "Authorization: $ARHMS_API_KEY"

Order status

GET/api/v2/orders/order_001

Data, airtime, AFA and result checker orders — the type field says which you got. Bill payments have their own endpoint on the Commission API tab. Status flows pending → processing → completed | failed; a completed checker order returns its PINs again.

cURL
curl -X GET "https://arhmsgh.com/api/v2/orders/order_001" \
  -H "Authorization: $ARHMS_API_KEY"
9

Utility Bills

Commission Services key. Pay bills at face value and earn a share of the commission. A Standard key here returns 403.

Biller catalogue

GET/api/v2/utilities/billers

Every biller, including ones an admin has switched off, so your picker can grey them out instead of guessing. Read min_amount / max_amount from here rather than hardcoding them.

cURL
curl -X GET "https://arhmsgh.com/api/v2/utilities/billers" \
  -H "Authorization: $ARHMS_COMMISSION_KEY"

Verify an account

GET/api/v2/utilities/lookup

Resolves a smartcard, IUC or meter number to the customer name. Show it back to your user before charging — a mistyped digit belongs to somebody else. ECG answers a phone number with a list of meters in meters; every other biller fills account_name instead. 404 = no such account, 502 = provider unreachable, retry.

Query: ?biller=dstv&account=7041234567

cURL
curl -X GET "https://arhmsgh.com/api/v2/utilities/lookup?biller=dstv&account=7041234567" \
  -H "Authorization: $ARHMS_COMMISSION_KEY"

Pay a bill

POST/api/v2/utilities/pay

Billers: ecg, ghana_water, dstv, gotv, startimes. ECG and Ghana Water need phone; Ghana Water also needs email for its receipt. For ECG, account is the specific meter from lookup. The account is re-verified server-side before any money moves.

cURL
curl -X POST https://arhmsgh.com/api/v2/utilities/pay \
  -H "Authorization: $ARHMS_COMMISSION_KEY" \
  -H "Content-Type: application/json" \
  -d '{"biller":"dstv","account":"7041234567","amount":65,"reference":"bill_dstv_7041234567_01"}'

Bill status

GET/api/v2/utilities/orders/UTIL-DSTV-3f9a2b1c4d5e6f70

Poll with the reference from the /pay RESPONSE — ours, not the one you sent. commission_earned stays null until the order completes.

cURL
curl -X GET "https://arhmsgh.com/api/v2/utilities/orders/UTIL-DSTV-3f9a2b1c4d5e6f70" \
  -H "Authorization: $ARHMS_COMMISSION_KEY"

Commission balance

GET/api/v2/commission/balance

What you have earned. Separate from your spending wallet.

cURL
curl -X GET "https://arhmsgh.com/api/v2/commission/balance" \
  -H "Authorization: $ARHMS_COMMISSION_KEY"

Earnings statement

GET/api/v2/commission/transactions

One row per bill payment that paid a commission. Paged — pass ?page= and ?limit= (max 100).

cURL
curl -X GET "https://arhmsgh.com/api/v2/commission/transactions" \
  -H "Authorization: $ARHMS_COMMISSION_KEY"
10

Webhooks

Register an HTTPS endpoint and we POST to it when an order settles, so you do not have to poll every order you place.

Register it in your dashboard under Developer API → Webhooks. Each key kind has its own endpoint and its own signing secret, shown once when you save.

EventKeyFires when
data.completedStandardThe supplier delivered the bundle to the recipient.
data.failedStandardThe supplier could not deliver it. Check your wallet — a refund is manual.
airtime.completedStandardThe network confirmed the top-up reached the beneficiary.
airtime.failedStandardThe provider refused or could not deliver it.
afa.completedStandardAn agent filed the AFA registration with MTN.
afa.failedStandardThe registration could not be completed.
utility.completedCommissionThe biller accepted the payment.
utility.failedCommissionThe biller rejected it; nothing was delivered.
utility.refundedCommissionA failed bill payment was credited back to your wallet.
What we send
POST https://your-app.com/webhooks/arhms
X-Arhms-Event: data.completed
X-Arhms-Signature: sha256=<hmac>

{
  "event": "data.completed",
  "reference": "order_001",
  "order_id": "6faea706-…",
  "status": "completed",
  "network": "MTN",
  "size": "1GB",
  "recipient": "0551617309",
  "price": 4.5,
  "sent_at": "2026-09-17T03:31:16.058Z"
}
Verify the signature (Node)
const crypto = require('crypto')

// Hash the RAW body, before any JSON parsing.
const expected = 'sha256=' + crypto
  .createHmac('sha256', process.env.ARHMS_WEBHOOK_SECRET)
  .update(rawBody, 'utf8')
  .digest('hex')

const ok = crypto.timingSafeEqual(
  Buffer.from(expected),
  Buffer.from(req.headers['x-arhms-signature'])
)

Hash the raw body, before any JSON parsing — re-serialising changes the bytes and the signature will not match. Respond 2xx as soon as you have stored the event and do your own work afterwards. We retry three times with backoff on a timeout or 5xx; a 4xx is treated as a permanent rejection and is not retried.

Airtime and bill events are sent the instant the order settles. Data and AFA events are swept within about a minute. A webhook is a convenience, not a guarantee — if your endpoint was unreachable for every retry, the order status is still authoritative atGET /api/v2/orders/{reference}.

11

Networks

Ghana only. Send local numbers in 0XXXXXXXXX form — 233XXXXXXXXX is also accepted and normalised.

MTN

network: "MTN"

024, 025, 053, 054, 055, 059

Telecel

network: "Telecel"

020, 050 (formerly Vodafone)

AT

network: "AT"

026, 027, 056, 057 (AirtelTigo)

Pass the value exactly as shown — the API matches it, not the brand name. International numbers and other networks are rejected. Which bundle sizes exist per network changes, so read /api/v2/packages rather than hardcoding a list.

12

Error Codes

The error message is written to be read — it usually says exactly what to change.

400

Bad Request

A field is missing or malformed — an invalid phone number, an unknown network, an amount outside the biller's range.

Do: Fix the request. The message names the field. Retrying unchanged will fail again.

401

Unauthorized

"Missing Authorization header" or "Invalid API key".

Do: Send the key in the Authorization header, on arhmsgh.com (not www).

402

Payment Required

"Insufficient wallet balance. Top up your wallet and retry."

Do: Top up from the dashboard. Nothing was charged.

403

Forbidden

The key is pending approval or revoked, the account is suspended, the role is not allowed, or a standard key was sent to a Commission endpoint (or vice versa).

Do: Read the message — it says which. Pending keys need admin approval.

404

Not Found

No order with that reference, or no such utility account.

Do: Check the reference. For bills, poll with the reference from the /pay response.

409

Conflict

The reference belongs to a different API key, the same bill payment was sent twice within 30 seconds, or a result checker type is out of stock. (Resending your OWN reference is not an error — it returns the original order.)

Do: Use a fresh reference. The original order is unaffected and nothing was charged.

429

Too Many Requests

"Rate limit exceeded for this endpoint."

Do: Wait for the number of seconds in the Retry-After header, then retry.

500

Internal Server Error

Something failed on our side.

Do: Check the order status before retrying a purchase — it may have gone through.

503

Service Unavailable

The API, v2, or a product (e.g. utility bills) is switched off.

Do: Retry later. This is deliberate, not an outage on your side.

13

Tips & Recommendations

Always send a reference

Your reference is the idempotency key. If a request times out, send it again with the same reference — you get the existing order back instead of being charged twice.

Poll, don't assume

A purchase response means the order was accepted, not delivered. Poll the order status endpoint until it reaches completed or failed.

Read catalogues, don't hardcode

Packages, billers, checker types and AFA fields change. Fetch them and cache for a few minutes rather than baking values into your app.

Verify before you pay a bill

Call utilities/lookup and show the account name to your customer first. A mistyped digit pays someone else, and bill payments cannot be reversed.

Keep the key server-side

Store it in an environment variable, never in frontend code or a git repository. If it leaks, revoke it from the dashboard and generate a new one.

Back off on 429

Honour the Retry-After header. Retrying in a tight loop only extends the window you are locked out for.