Developers
Inkoom API documentation
A JSON REST API for agents, super agents and dealers. Sell data and airtime from your own site, bot or POS — priced at your reseller tier, charged to your Inkoom wallet.
https://inkoomgh.com/api/public/v1
Bearer ink_live_…
120 req / min / key
Quickstart
Three calls get you selling. Everything is JSON over HTTPS.
- Create a key in your dashboard under API Keys (Agent tier or above). It is shown once.
- Fund your Inkoom wallet — every purchase is debited from it at your tier price.
- List products, then POST a purchase with your own unique
reference.
# 1. check your wallet
curl https://inkoomgh.com/api/public/v1/balance -H "Authorization: Bearer ink_live_xxx"
# 2. list MTN bundles at your tier price
curl "https://inkoomgh.com/api/public/v1/products?network=mtn" -H "Authorization: Bearer ink_live_xxx"
# 3. buy one
curl -X POST https://inkoomgh.com/api/public/v1/purchase \
-H "Authorization: Bearer ink_live_xxx" \
-H "Content-Type: application/json" \
-d '{"product_id":"<uuid>","recipient":"0241234567","reference":"order-00123"}'bashAuthentication
Send your key on every request. Either header works — pick one and stick to it. Keys start with ink_live_, are stored hashed on our side, and are shown to you only once at creation.
Authorization: Bearer ink_live_xxxxxxxxxxxxxxxx
# or
x-api-key: ink_live_xxxxxxxxxxxxxxxxhttpA missing, revoked or banned key returns 401 with { "success": false, "error": "Invalid or missing API key", "code": "unauthorized" }. Never expose a key in browser code — call the API from your server.
Conventions
- Every response is
{ "success": boolean, "data": … }or{ "success": false, "error": string, "code": string }. - All amounts are in Ghana Cedis (GHS) as numbers, e.g.
15.5. - Phone numbers accept
0241234567,233241234567or+233241234567— we normalise them. - Product prices are resolved per key: your tier (agent, super agent, dealer) is applied automatically.
Unified Action API (POST /api/v1)
/api/v1 (or /api/public/v1)Inkoom provides a single-endpoint Action API identical to Bundle Portal. Send a JSON body with an action property, authenticated with your x-api-key or Bearer token.
Drop-in Bundle Portal Compatible
If you have an existing integration built for Bundle Portal, you can switch your base URL to https://inkoomgh.com/api/v1 without changing your request payload format!
Supported Actions
| Field | Type | Description |
|---|---|---|
| check_balance | No extra params | Returns spendable wallet, commission balance, and role. |
| verify_number | network, recipient | Checks number eligibility with operator before placing orders. |
| get_bundles | network (optional) | Lists active bundle packages and your tier price. |
| place_order | network, package_size, recipient (or product_id) | Places an order with automatic carrier waterfall. |
| check_status | order_reference or order_id | Polls live fulfillment status of an order. |
| order_airtime | network, recipient, amount | Instant mobile airtime recharge (GHS 1 - 500). |
| get_result_checker_types | No extra params | Lists WAEC and BECE result checker voucher types. |
| order_result_checker | checker_type, quantity (opt) | Instantly purchases WAEC / BECE result checker PIN and Serial. |
| get_transactions | limit, page (optional) | Returns your wallet ledger history. |
| get_special_offers | No extra params | Lists active promo data bundles. |
| share_data | from_phone, to_phone, network, volume_gb | Initiates data volume transfer. |
Example: Place Order via Action API
curl -X POST https://inkoomgh.com/api/v1 \
-H "x-api-key: ink_live_xxx" \
-H "Content-Type: application/json" \
-d '{
"action": "place_order",
"network": "mtn",
"recipient": "0241234567",
"package_size": 5,
"order_id": "client-ref-1001"
}'bash{
"success": true,
"data": {
"order_id": "…uuid…",
"reference": "client-ref-1001",
"recipient": "0241234567",
"network": "mtn",
"package_size": 5,
"amount": 24.00,
"status": "success"
}
}jsonWallet balance
/api/public/v1/balanceReturns your spendable wallet, commission balance and reseller role.
{
"success": true,
"data": {
"currency": "GHS",
"wallet_balance": 482.50,
"commission_balance": 26.10,
"role": "agent"
}
}jsonNetworks
/api/public/v1/networksThe active networks you can sell on. Use code to filter products.
{
"success": true,
"data": [
{ "id": "…uuid…", "code": "mtn", "name": "MTN Ghana" },
{ "id": "…uuid…", "code": "telecel", "name": "Telecel Ghana" },
{ "id": "…uuid…", "code": "at", "name": "AT (iShare & BigTime)" }
]
}jsonProducts (bundles)
/api/public/v1/products?network=mtn&service=dataCache this list and refresh it hourly — id values are stable, prices can change when the operator updates the catalogue.
| Field | Type | Description |
|---|---|---|
| network | query, optional | Network code: mtn, telecel, at. |
| service | query, optional | data | airtime | afa | result_checker | combo. |
{
"success": true,
"data": [
{
"id": "b1f0…-…-…",
"name": "5GB",
"service": "data",
"network": "mtn",
"volume_mb": 5120,
"validity_days": 90,
"price": 24.00
}
]
}jsonprice is what your wallet is debited — mark it up however you like.
Place an order
/api/public/v1/purchase| Field | Type | Description |
|---|---|---|
| product_id | string (uuid), required | From GET /products. |
| recipient | string, required | Ghana MSISDN receiving the bundle. |
| reference | string, required | Your unique order id, 8–128 chars. Makes the call idempotent. |
curl -X POST https://inkoomgh.com/api/public/v1/purchase \
-H "Authorization: Bearer ink_live_xxx" \
-H "Content-Type: application/json" \
-d '{
"product_id": "b1f0…-…-…",
"recipient": "0241234567",
"reference": "shop-2026-00123"
}'bash{
"success": true,
"data": {
"order_id": "…uuid…",
"reference": "shop-2026-00123",
"recipient": "0241234567",
"amount": 24.00,
"status": "success",
"reason": null
}
}jsonOrder lifecycle
success— delivered. Wallet already debited.processing— accepted by an upstream carrier partner, not yet confirmed. Poll the order endpoint; do not re-send.failed— every route failed. Your wallet is refunded automatically andreasonexplains why.
Behind one call we run a supplier waterfall: if the first carrier partner rejects or times out, we fall through to the next one before failing.
Airtime top-up
/api/v1/airtime (or /api/public/v1/airtime)Top up any MTN, Telecel or AT Ghana number with instant airtime between GHS 1 and GHS 500.
| Field | Type | Description |
|---|---|---|
| recipient | string, required | 10-digit Ghana phone number. |
| network | string, required | mtn | telecel | at. |
| amount | number, required | Recharge amount in GHS (1 to 500). |
| order_id | string, optional | Idempotency key for this recharge. |
curl -X POST https://inkoomgh.com/api/v1/airtime \
-H "x-api-key: ink_live_xxx" \
-H "Content-Type: application/json" \
-d '{
"recipient": "0541234567",
"network": "mtn",
"amount": 10.00
}'bash{
"success": true,
"data": {
"order_id": "…uuid…",
"reference": "ink_air_…",
"recipient": "0541234567",
"network": "mtn",
"amount": 10,
"status": "completed"
}
}jsonResult Checkers
/api/v1/checkers (or action: order_result_checker)Purchase WAEC WASSCE and BECE result checker voucher cards. The response delivers PIN and Serial numbers immediately upon wallet debit.
| Field | Type | Description |
|---|---|---|
| checker_type | string, required | waec | bece | novdec. |
| quantity | number, optional | Quantity of scratch cards to purchase (default 1). |
| recipient_phone | string, optional | Phone number for SMS voucher notification. |
curl -X POST https://inkoomgh.com/api/v1/checkers \
-H "x-api-key: ink_live_xxx" \
-H "Content-Type: application/json" \
-d '{
"checker_type": "waec",
"quantity": 1
}'bash{
"success": true,
"data": {
"order_id": "…uuid…",
"checker_type": "waec",
"quantity": 1,
"checkers": [
{
"pin": "123456789012",
"serial": "WEC202612345",
"type": "waec"
}
]
}
}jsonWallet Transactions
/api/v1/transactions (or action: get_transactions)Retrieve full ledger audit trail of deposits, order debits, refunds, and commissions.
| Field | Type | Description |
|---|---|---|
| limit | query, optional | Page size, 1 to 100 (default 20). |
| page | query, optional | Page number starting at 1. |
{
"success": true,
"data": {
"transactions": [
{
"id": "…uuid…",
"amount": 24.00,
"type": "debit",
"purpose": "purchase",
"reference": "shop-00123",
"balance_before": 100.00,
"balance_after": 76.00,
"status": "completed",
"created_at": "2026-09-17T12:00:00Z"
}
],
"total": 142,
"page": 1,
"limit": 20
}
}jsonSpecial Offers
/api/v1/special-offers (or action: get_special_offers)Returns active discounted promotional bundles and supplier specials.
Check an order
/api/public/v1/orders/{order_id_or_reference}Accepts either our order_id or your own reference. Poll every 10–20 seconds while the status is processing; most orders settle within a minute.
{
"success": true,
"data": {
"order_id": "…uuid…",
"reference": "shop-2026-00123",
"recipient": "0241234567",
"amount": 24.00,
"status": "processing",
"refunded": false,
"reason": null
}
}jsonIdempotency & retries
reference is your idempotency key. Re-sending a purchase with the same reference returns the original order instead of charging twice — so on a timeout or a 500, retry with the exact same reference.
- Generate the reference before the first attempt and persist it.
- Never reuse a reference for a different recipient or product.
- Alternatively send the header
Idempotency-Keyinstead of the body field.
Rate limits
120 requests per minute per API key. Every response carries the current window state:
x-ratelimit-limit: 120
x-ratelimit-remaining: 118
x-ratelimit-reset: 1767225600httpOver the limit you get 429 with retry_after in seconds. Back off exponentially; dealers can request a higher ceiling.
Error codes
Errors are never thrown as HTML. Switch on code, not on the message text.
| Field | Type | Description |
|---|---|---|
| unauthorized | 401 | API key missing, revoked, or the account is banned. |
| rate_limited | 429 | More than 120 requests in a minute. Check retry_after. |
| bad_request | 400 | Body was not valid JSON. |
| invalid_product | 400 | product_id missing or not a UUID. |
| invalid_recipient | 400 | Recipient is not a valid Ghana MSISDN. |
| invalid_reference | 400 | reference missing or not 8–128 characters. |
| order_rejected | 422 | Business rejection: insufficient balance, blacklisted number, inactive product. |
| not_found | 404 | No order matches that id or reference. |
| server_error | 500 | Unexpected failure. Safe to retry with the same reference. |
{
"success": false,
"error": "Insufficient wallet balance",
"code": "order_rejected"
}jsonCode samples
const API = "https://inkoomgh.com/api/public/v1";
const KEY = process.env.INKOOM_API_KEY;
async function inkoom(path, init = {}) {
const res = await fetch(API + path, {
...init,
headers: {
Authorization: `Bearer ${KEY}`,
"Content-Type": "application/json",
...(init.headers || {}),
},
});
const body = await res.json();
if (!body.success) throw new Error(`${body.code}: ${body.error}`);
return body.data;
}
export async function sellBundle(productId, recipient, reference) {
const order = await inkoom("/purchase", {
method: "POST",
body: JSON.stringify({ product_id: productId, recipient, reference }),
});
if (order.status !== "processing") return order;
// poll until terminal
for (let i = 0; i < 20; i++) {
await new Promise((r) => setTimeout(r, 10_000));
const check = await inkoom(`/orders/${reference}`);
if (check.status !== "processing") return check;
}
return order;
}javascriptGo-live checklist
- Store the API key as a server-side environment variable, never in the client.
- Persist your
referencebefore calling/purchase. - Treat
processingas an open order in your own system, and reconcile by polling/orders/{reference}. - Handle
429with exponential backoff. - Alert yourself when the wallet balance drops below a day of sales.
- Rotate keys from the dashboard if one leaks — revoking is instant.
