# Overview Source: https://simconnectng.com/docs One API for airtime, data and more across Nigeria, through hosted SIMs and the channels you connect. SimConnectNG takes one request, sends it through the healthiest channel on the right network, moves to another channel if that one stumbles, and tells you the final state through the response and a signed webhook. You pay a flat fee per delivered sale: it is held on your wallet when a request starts and settled or released when it ends. Everything is plain JSON over HTTPS, with amounts in naira. Every response has the same shape: `success`, `data`, `message` and `code`. ## Start here - [Quickstart](/docs/quickstart): a test key, a test sale and a webhook, in a few minutes. - [Integrate with a coding agent](/docs/agents): a prompt you can paste into your agent, and the files it should read. ## How it works - [Test mode](/docs/concepts/test-mode): build against test keys that never touch live money. - [Authentication](/docs/concepts/authentication): API keys, how to keep them safe and how to rotate them. - [Idempotency](/docs/concepts/idempotency): repeat a request safely, never charge twice. - [Wallet](/docs/concepts/wallet): holds, settlement and the ledger. - [Webhooks](/docs/concepts/webhooks): events, signatures and retries. - [Errors](/docs/concepts/errors): the error shape and every code. ## API reference The base URL is `https://api.simconnectng.com/api/v1`. Every endpoint is listed under **API reference** in the menu, each with one request, one response and one error you can copy. ## For agents Every page is also available as plain markdown: add `.md` to its address. The full index is at [/llms.txt](/llms.txt), and every page in one file is at [/llms-full.txt](/llms-full.txt). --- # Quickstart Source: https://simconnectng.com/docs/quickstart A test key, a test sale and a signed webhook, in a few minutes. Everything here runs in [test mode](/docs/concepts/test-mode): the same responses, statuses and webhooks as live, against a test wallet and a simulated network. The code you write here is the code you ship. ## 1. Get a test key Create an account, verify your email, then open **Developers > API keys** in the dashboard and create a key in **Test** mode. A test key starts with `scn_test_`. It is shown once, so put it straight into your environment. ```bash export SIMCONNECT_KEY="scn_test_..." # the key you just copied ``` ## 2. Fund the test wallet In the dashboard, switch to **Test** and open **Wallet**, then use **Top up test wallet**. Each sale holds its flat fee on the wallet, the same in test and live. A sale the wallet cannot cover is refused with `402 WALLET_INSUFFICIENT_FUNDS`. ## 3. Send airtime One request, with an `Idempotency-Key` so it is safe to repeat. `MTN-VTU` is MTN's airtime product; [List plans](/docs/api/list-data-plans) has every product code. ```bash curl https://api.simconnectng.com/api/v1/airtime \ -H "Authorization: Bearer $SIMCONNECT_KEY" \ -H "Idempotency-Key: order_1042" \ -H "Content-Type: application/json" \ -d '{ "product_code": "MTN-VTU", "amount": 500, "phone_number": "08012345678", "client_reference": "order_1042" }' ``` The call stays open until the sale is final and answers with the result: ```json { "success": true, "data": { "reference": "txn_0192f4c1a7e84b6f9d2c3e5a7b9d1f30", "client_reference": "order_1042", "mode": "test", "status": "successful", "product_code": "MTN-VTU", "network": "mtn", "destination": "+234********78", "amount": 500.00, "price": 2.00, "completed_at": "2026-10-06T12:04:03Z" }, "message": "Transaction successful", "code": "OK" } ``` `amount` is the airtime sent and `price` the fee your wallet paid. Keep the `reference`: webhooks and [Get a transaction](/docs/api/get-transaction) use it. If your request times out, send it again with the same `Idempotency-Key` and body; you will get the same answer, never a second charge. To get an answer at once and the result by webhook, add `?wait=0`: the call answers `202` at once, while the sale is on its way. In test mode the last four digits of the phone number choose the outcome: `0000` fails, `3333` stays `pending` before it is `successful`, `4444` stays `pending` before it fails, anything else is `successful`. [Test mode](/docs/concepts/test-mode) lists them all. ## 4. Take the webhook Add an endpoint under **Developers > Webhooks**. When the sale is final you receive: ```json { "id": "0192f4c2-8d1e-7a3b-9c4f-5e6a7b8c9d0e", "type": "transaction.successful", "mode": "test", "occurred_at": "2026-10-06T12:04:03Z", "data": { "reference": "txn_0192f4c1a7e84b6f9d2c3e5a7b9d1f30", "client_reference": "order_1042", "status": "successful", "amount": 500.00, "price": 2.00 } } ``` Check the `SimConnect-Signature` header before you trust it; [Webhooks](/docs/concepts/webhooks) shows how in a few lines, and lists the full body. ## 5. Go live Create a live key (it starts with `scn_live_`), fund your live wallet, and swap the key. Nothing else changes. --- # Integrate with a coding agent Source: https://simconnectng.com/docs/agents The prompt to paste into your coding agent, and the files it should read. Every page of these docs is plain markdown at its own address, so a coding agent can read exactly what you read. Paste this into your agent and it has what it needs. ```text Integrate SimConnectNG into this project to sell airtime and data. Read these first: - https://simconnectng.com/llms.txt (index of every docs page) - https://simconnectng.com/docs/quickstart.md - https://simconnectng.com/docs/concepts/idempotency.md - https://simconnectng.com/docs/concepts/webhooks.md Rules: - Read the API key from the SIMCONNECT_KEY environment variable. Never hardcode it. - The base URL is https://api.simconnectng.com/api/v1. Every response is { success, data, message, code }; branch on code. - Send an Idempotency-Key with every sale (POST /api/v1/airtime, POST /api/v1/data), derived from our own order id. - Name what to sell with product_code (such as MTN-VTU for airtime or MTN-SHARE-1GB-30D for data) and the number with phone_number. - Treat the transaction status as the truth: successful or failed is final; anything else, such as pending, is still on its way. - Lists are data.items; follow data.page.next_cursor with the cursor query parameter. - Verify the SimConnect-Signature header on every webhook before using it. - Amounts are naira, numbers with at most two decimals. - Use test mode (a scn_test_ key) until I say otherwise. ``` ## What the agent should end up with - A small client that sends airtime and data with an idempotency key. - A webhook handler that checks the signature and updates your order by `client_reference`. - A test that sends to a number ending in `0000` and handles the failure. ## Files for agents | File | What it holds | | --- | --- | | `/llms.txt` | Every docs page, one line each, with its markdown address | | `/llms-full.txt` | Every docs page in one file | | `/docs/.md` | Any single page as markdown | --- # Changelog Source: https://simconnectng.com/docs/changelog Every change to the API, newest first. ## Preview The API is in preview. Endpoints and fields on these pages may still change before launch, and every change will be listed here with its date. ## 6 October 2026 The docs and code samples now follow the backend's API: - The base URL is `https://api.simconnectng.com/api/v1`. Plans are at `/api/v1/pricing/plans`, the wallet at `/api/v1/wallet/balance` and networks at `/api/v1/catalog/networks`, each keyed by `code`. - Sales name what to sell with `product_code` (such as `MTN-VTU` or `MTN-SHARE-1GB-30D`) and the number with `phone_number` (`phone` still works). `?wait=0` answers `202` at once. - A finished sale is `successful` or `failed`, with `completed_at` and a masked `destination`. - Every response is `{ success, data, message, code }`. Lists put their rows in `data.items` and the next page in `data.page.next_cursor`, read with `cursor`; the default page is 25. - You pay a flat fee per delivered sale, shown as `charges` on each plan and `price` on each sale. - Error codes now match the backend, among them `AUTH_REQUIRED`, `PERMISSION_DENIED` (IP allowlist), `IDEMPOTENCY_IN_PROGRESS` and `IDEMPOTENCY_FINGERPRINT_MISMATCH`. A plan that cannot be sold is `400 VALIDATION_FAILED` on `product_code`, and a sale with no line to take it fails with `ROUTING_NO_CANDIDATES`. - Webhooks: the body is `{ id, type, mode, occurred_at, data }` with a `SimConnect-Event-Id` header. Events are `transaction.successful`, `transaction.failed`, `transaction.pending`, `transaction.refunded`, `wallet.funded`, `wallet.low_balance` and `webhook.ping`. - Test numbers ending `1111`, `2222` and `4444` join `0000` and `3333`. - Keys can require signed requests, with `SimConnect-Timestamp` and `SimConnect-Signature`. --- # Test mode Source: https://simconnectng.com/docs/concepts/test-mode Build against test keys that never touch live money or live networks. A test key (`scn_test_...`) talks to a test wallet and a simulated network. Responses, statuses, fees, timing and webhooks match live, so nothing in your code changes when you go live except the key. ## Choosing an outcome In test mode the last four digits of the phone number decide what happens: | Ends in | Outcome | | --- | --- | | `0000` | Permanent failure: `failed`, with a failure code | | `1111` | Temporary failure: tried elsewhere, and with nowhere else to go, `failed` | | `2222` | Unknown outcome: `pending` while we check its status | | `3333` | `pending` first, then `successful` | | `4444` | `pending` first, then `failed` | | anything else | `successful` | ## What stays separate Test and live never mix. A test key cannot see live transactions, spend the live wallet or reach a live line, and the dashboard shows each mode on its own. --- # Authentication Source: https://simconnectng.com/docs/concepts/authentication API keys, how to keep them safe, and how to rotate them without downtime. Send your key as a bearer token on every request: ```bash curl https://api.simconnectng.com/api/v1/wallet/balance \ -H "Authorization: Bearer $SIMCONNECT_KEY" ``` ## Keys - Test keys start with `scn_test_`, live keys with `scn_live_`. The key decides the mode: there is no separate test address. - A key is shown once, when you create it. Store it in your environment or secret manager. - Keep keys out of client-side code, mobile apps and version control. A request with no key, or a key that does not exist, is refused with `401 AUTH_REQUIRED`. ## Rotating a key Create a new key, deploy it, then revoke the old one. Both work during the overlap, so there is no downtime. ## Locking a key down Each key can carry an IP allowlist. Requests from any other address are refused with `403 PERMISSION_DENIED`, "Client address is not allowed for this key". ## Signing requests A key can also require signed requests. Its signing secret is shown once, with the key. Each request then carries two more headers: | Header | Value | | --- | --- | | `SimConnect-Timestamp` | The current unix time in seconds | | `SimConnect-Signature` | Lower case hex HMAC SHA-256 of `.`, keyed with the signing secret | ```js import crypto from "node:crypto"; const body = JSON.stringify({ product_code: "MTN-VTU", amount: 500, phone_number: "08012345678" }); const timestamp = Math.floor(Date.now() / 1000).toString(); const signature = crypto.createHmac("sha256", process.env.SIMCONNECT_SIGNING_SECRET).update(`${timestamp}.${body}`).digest("hex"); // Send body exactly as signed, with SimConnect-Timestamp: timestamp and SimConnect-Signature: signature. ``` Sign the exact bytes you send; a `GET` signs an empty body. A timestamp more than five minutes from our clock, or a signature that does not match, is refused with `401 AUTH_REQUIRED`, "Request signature missing or invalid". The IP allowlist is checked first. --- # Idempotency Source: https://simconnectng.com/docs/concepts/idempotency Repeat a request safely. The same key always gives the same answer and never a second charge. Every sale (`POST /api/v1/airtime` and `POST /api/v1/data`) needs an `Idempotency-Key` header: any string up to 128 characters, unique to that sale. Your own order id works well. Without it the request is refused with `400 IDEMPOTENCY_KEY_MISSING`. ```bash -H "Idempotency-Key: order_1042" ``` ## What happens on a repeat | You send | You get | | --- | --- | | The same key and the same body, after the first finished | The stored answer, with the same status code and an `Idempotent-Replayed: true` header | | The same key while the first is still running | `409 IDEMPOTENCY_IN_PROGRESS` | | The same key with a different body | `422 IDEMPOTENCY_FINGERPRINT_MISMATCH` | "The same body" means the same bytes. So if a request times out, send it again with the same key and body. You will never be charged twice. Refusals (`4xx`) are stored and replayed too; after a `5xx` the key is free to use again. ## Your own reference You can also send `client_reference` in the body: 1 to 128 characters, unique per mode. It comes back on the transaction and in every webhook, and is the easiest way to match a sale to your order. A second sale with the same `client_reference` is refused with `409 CONFLICT`, and the message names the first sale's reference. --- # Wallet Source: https://simconnectng.com/docs/concepts/wallet Holds, settlement and the ledger behind every sale. Every sale's fee is paid from your wallet, and every movement is a ledger entry you can audit. ## What a sale costs You pay SimConnectNG a flat fee for each delivered sale, set per plan or per data type and shown as `charges` on [List plans](/docs/api/list-data-plans). It is not a share of the face value. The airtime or data itself goes out from your lines, paid by their own balance. ## The life of a sale 1. **Hold.** When we accept a request, its fee is held on your wallet. It leaves your available balance but is not spent. 2. **Settle.** When the sale is `successful`, the hold is settled: the fee is spent. 3. **Release.** When the sale fails, the hold is released in full, automatically. A sale whose outcome is not known yet keeps its hold until the network answers. You are never charged for an unknown outcome, and never twice. On the transaction, `price` is the fee. ## Balances | Field | Meaning | | --- | --- | | `balance` | Everything in the wallet | | `held` | Fees waiting on sales in flight | | `available` | `balance` minus `held`: what new sales can use | Read them with [Get wallet](/docs/api/get-wallet). Amounts are naira, numbers with two decimals. ## Funding Fund by bank transfer or card from the dashboard. A top-up is credited only once the payment is confirmed, appears in your ledger straight away, and sends a `wallet.funded` webhook. --- # Webhooks Source: https://simconnectng.com/docs/concepts/webhooks Events, signatures and retries. Add an endpoint under **Developers > Webhooks**. We send a `POST` with a JSON body for each event. ## Events | Event | When | | --- | --- | | `transaction.successful` | A sale delivered and its fee was settled | | `transaction.failed` | A sale failed and its hold was released | | `transaction.pending` | A sale is waiting on the network's answer | | `transaction.refunded` | We refunded a sale | | `wallet.funded` | A top-up was credited | | `wallet.low_balance` | The wallet dropped below your alert level | | `webhook.ping` | A test you sent from the dashboard | ## The body ```json { "id": "0192f4c2-8d1e-7a3b-9c4f-5e6a7b8c9d0e", "type": "transaction.successful", "mode": "live", "occurred_at": "2026-10-06T12:04:03Z", "data": { "reference": "txn_0192f4c1a7e84b6f9d2c3e5a7b9d1f30", "client_reference": "order_1042", "status": "successful", "product_code": "MTN-VTU", "network": "mtn", "service": "airtime", "destination": "+234********78", "amount": 500.00, "price": 2.00, "completed_at": "2026-10-06T12:04:03Z" } } ``` A failed sale adds `failure_code`. `wallet.funded` carries `funding_id`, `amount`, `balance` and `gateway`; `wallet.low_balance` carries `balance` and `threshold`. Amounts are naira, numbers with two decimals. ## Checking the signature Every request carries a `SimConnect-Signature` header: `t=,v1=`. The signature is HMAC SHA-256 of `.` with your endpoint secret, in hex. ```js import crypto from "node:crypto"; function verify(rawBody, header, secret) { const { t, v1 } = Object.fromEntries(header.split(",").map((p) => p.split("="))); const expected = crypto.createHmac("sha256", secret).update(`${t}.${rawBody}`).digest("hex"); const fresh = Math.abs(Date.now() / 1000 - Number(t)) < 300; return fresh && expected.length === v1.length && crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(v1)); } ``` Refuse anything older than five minutes, so a captured request cannot be replayed. ## Retries Answer with any `2xx` within ten seconds to confirm. Anything else, or no answer, is retried from 30 seconds later, doubling each time, for up to 8 attempts. Each event has an `id`, also sent as the `SimConnect-Event-Id` header, so handle it once even if it arrives twice. Failed deliveries can be replayed from the dashboard. --- # Errors Source: https://simconnectng.com/docs/concepts/errors The error shape and every code you can get back. Every response has the same four fields, errors included: ```json { "success": false, "data": null, "message": "Insufficient wallet balance", "code": "WALLET_INSUFFICIENT_FUNDS" } ``` On success `code` is always `OK`. Branch on `code`, not on `message`: messages may be reworded, codes never change. A validation error names each field in `data.errors`: ```json { "success": false, "data": { "errors": [{ "field": "amount", "message": "must be an amount in naira with at most 2 decimal places" }] }, "message": "must be an amount in naira with at most 2 decimal places", "code": "VALIDATION_FAILED" } ``` ## Codes | Status | Code | Meaning | | --- | --- | --- | | 400 | `VALIDATION_FAILED` | A field is missing or malformed, or the `product_code` cannot be sold right now; `data.errors` names the field | | 400 | `IDEMPOTENCY_KEY_MISSING` | A sale without an `Idempotency-Key` header | | 401 | `AUTH_REQUIRED` | No key, a key that does not exist, or a missing or invalid request signature | | 402 | `WALLET_INSUFFICIENT_FUNDS` | The available balance cannot cover the sale's fee | | 403 | `PERMISSION_DENIED` | The key has an IP allowlist and this address is not on it, or the key may not do this | | 404 | `NOT_FOUND` | No such transaction or resource | | 409 | `IDEMPOTENCY_IN_PROGRESS` | The same idempotency key is still running | | 409 | `CONFLICT` | The `client_reference` is already used; `message` names that sale's reference | | 413 | `REQUEST_TOO_LARGE` | The body is too large | | 422 | `IDEMPOTENCY_FINGERPRINT_MISMATCH` | The same idempotency key with a different body | | 429 | `RATE_LIMITED` | Too many requests; wait for `Retry-After` | | 500 | `INTERNAL_ERROR` | Something broke on our side; safe to repeat with the same `Idempotency-Key` | | 503 | `SERVICE_UNAVAILABLE` | We are briefly unavailable; repeat with the same `Idempotency-Key` | ## Failed sales A failed sale is not an error: it answers `200` with `status` `failed`, a `failure_code` and a `failure_message`, and its hold is released. | Failure code | Meaning | | --- | --- | | `ROUTING_NO_CANDIDATES` | No line or channel could take the sale right now | | `PROVIDER_REJECTED` | The sale was refused for good, such as an invalid number; it is not tried again | | `RETRIES_EXHAUSTED` | Every line or channel tried failed for a passing reason | | `PROVIDER_FAILED` | The line or channel reported the sale failed | --- # Rate limits Source: https://simconnectng.com/docs/concepts/rate-limits How limits are applied, and what to do when you hit one. Limits apply per key, 600 requests a minute, and per account across all its keys, 3,000 a minute. Every response carries: | Header | Meaning | | --- | --- | | `X-RateLimit-Limit` | Requests allowed in the current window | | `X-RateLimit-Remaining` | Requests left in the window | | `Retry-After` | On a `429`, seconds to wait before trying again | When you get `429 RATE_LIMITED`, wait for `Retry-After` and repeat the request with the same `Idempotency-Key`. If your volume needs a higher limit, [talk to us](/contact). --- # Send airtime Source: https://simconnectng.com/docs/api/send-airtime Top up any Nigerian number on MTN, Airtel, Glo or 9mobile. Routes the top-up through a line that can send it on the network and answers with the final state. ## Body | Field | Type | Required | Notes | | --- | --- | --- | --- | | `product_code` | string | yes | The network's airtime product: `MTN-VTU`, `AIRTEL-VTU`, `GLO-VTU` or `9MOBILE-VTU`. See [List plans](/docs/api/list-data-plans) | | `amount` | number | yes | Naira, at most two decimals, within the product's `min_amount` and `max_amount` | | `phone_number` | string | yes | Nigerian number, `08012345678` or `+2348012345678`. `phone` is accepted as an alias | | `client_reference` | string | no | Your own reference, 1 to 128 characters, unique per mode | Send an `Idempotency-Key` header with every request. You can also send `network` (such as `mtn`) on its own instead of `product_code`, and the network's airtime product is used. ## Waiting for the result By default the call stays open until the sale is final, up to a limit set by us, and answers `200`. If the sale is still on its way when the limit is reached, it answers `202` with the sale as it stands (`status` not final yet). Add `?wait=0` to get `202` at once and follow the sale by webhook or [Get a transaction](/docs/api/get-transaction). ## Request ```bash curl https://api.simconnectng.com/api/v1/airtime \ -H "Authorization: Bearer $SIMCONNECT_KEY" \ -H "Idempotency-Key: order_1042" \ -H "Content-Type: application/json" \ -d '{ "product_code": "MTN-VTU", "amount": 500, "phone_number": "08012345678", "client_reference": "order_1042" }' ``` ## Response ```json { "success": true, "data": { "id": "0192f4c1-a7e8-7b6f-9d2c-3e5a7b9d1f30", "reference": "txn_0192f4c1a7e84b6f9d2c3e5a7b9d1f30", "client_reference": "order_1042", "mode": "live", "status": "successful", "product_code": "MTN-VTU", "network": "mtn", "service": "airtime", "destination": "+234********78", "amount": 500.00, "price": 2.00, "failure_code": null, "failure_message": null, "created_at": "2026-10-06T12:04:01Z", "completed_at": "2026-10-06T12:04:03Z" }, "message": "Transaction successful", "code": "OK" } ``` A final `status` is `successful` or `failed`. Any other, such as `pending`, finishes later and you hear about it by webhook. `amount` is the airtime sent; `price` is the flat fee your wallet pays for the sale. `destination` is always masked. The figures shown here are placeholders. ## Error ```json { "success": false, "data": null, "message": "Insufficient wallet balance", "code": "WALLET_INSUFFICIENT_FUNDS" } ``` --- # List plans Source: https://simconnectng.com/docs/api/list-data-plans The plans on sale for each network, with their face value and the flat fee per sale. Returns every plan on sale, or one network's plans with `?network=`. Each plan has the `product_code` you send to [Buy data](/docs/api/buy-data) or [Send airtime](/docs/api/send-airtime). ## Query | Field | Type | Required | Notes | | --- | --- | --- | --- | | `network` | string | no | `mtn`, `airtel`, `glo` or `9mobile` | | `service` | string | no | `data` or `airtime` | ## How a sale is charged You pay SimConnectNG a flat fee for each sale, set per plan or per data type, and the same in test and live. That fee is the plan's `charges`, and it is what your wallet is held and charged; a failed sale costs nothing. `amount` is the plan's face value, shown as a guide for your own prices; the data or airtime itself goes out from your lines. What you charge your customers is yours to set. ## Request ```bash curl "https://api.simconnectng.com/api/v1/pricing/plans?network=mtn&service=data" \ -H "Authorization: Bearer $SIMCONNECT_KEY" ``` ## Response `groups` holds the plans by data type and validity, each with its fee. `items` is the same plans as one flat list, with face values. The sample is trimmed to one data type. ```json { "success": true, "data": { "mode": "live", "groups": [ { "code": "data_share", "name": "Datashare", "network": "mtn", "service": "data", "data_type_charges": 10.00, "validity_groups": [ { "label": "30 days", "validity_days": 30, "plans": [ { "product_code": "MTN-SHARE-1GB-30D", "name": "Datashare 1GB, monthly", "kind": "fixed_denomination", "validity_days": 30, "amount": 350.00, "charges": 10.00, "charges_source": "data_type" }, { "product_code": "MTN-SHARE-20GB-30D", "name": "Datashare 20GB, monthly", "kind": "fixed_denomination", "validity_days": 30, "amount": 7000.00, "charges": 30.00, "charges_source": "plan" } ] } ] } ], "items": [ { "product_code": "MTN-SHARE-1GB-30D", "plan_name": "Datashare 1GB, monthly", "network": "mtn", "service": "data", "validity_days": 30, "amount": 350.00 }, { "product_code": "MTN-SHARE-20GB-30D", "plan_name": "Datashare 20GB, monthly", "network": "mtn", "service": "data", "validity_days": 30, "amount": 7000.00 } ] }, "message": "OK", "code": "OK" } ``` `charges_source` says whether the fee is the plan's own (`plan`) or its data type's (`data_type`); a plan's own fee wins. A plan with no `charges` cannot be sold from lines yet. Airtime products (`?service=airtime`) carry `min_amount` and `max_amount` instead of a fixed face value. The figures shown here are placeholders. For the plan list alone, without fees, use `GET /api/v1/catalog/products?network=mtn&service=data`. It answers `data.items`, each with `code` (the product code), `name`, `network`, `service`, `kind` and `validity_days`. ## Error ```json { "success": false, "data": null, "message": "API key required", "code": "AUTH_REQUIRED" } ``` --- # Buy data Source: https://simconnectng.com/docs/api/buy-data Send a data plan to any Nigerian number. Sends the plan through a line or channel that can send it on its network and answers with the final state. ## Body | Field | Type | Required | Notes | | --- | --- | --- | --- | | `product_code` | string | yes | A plan's `product_code` from [List plans](/docs/api/list-data-plans), such as `MTN-SHARE-1GB-30D` | | `phone_number` | string | yes | Nigerian number, `08012345678` or `+2348012345678`. `phone` is accepted as an alias | | `client_reference` | string | no | Your own reference, 1 to 128 characters, unique per mode | Send an `Idempotency-Key` header with every request. Like airtime, the call waits for the final state; add `?wait=0` to get `202` at once. ## Request ```bash curl https://api.simconnectng.com/api/v1/data \ -H "Authorization: Bearer $SIMCONNECT_KEY" \ -H "Idempotency-Key: order_1043" \ -H "Content-Type: application/json" \ -d '{ "product_code": "MTN-SHARE-1GB-30D", "phone_number": "08012345678", "client_reference": "order_1043" }' ``` ## Response ```json { "success": true, "data": { "id": "0192f4c3-b5d2-7e8a-9f1c-6b7d8e9a0f21", "reference": "txn_0192f4c3b5d24e8a9f1c6b7d8e9a0f21", "client_reference": "order_1043", "mode": "live", "status": "successful", "product_code": "MTN-SHARE-1GB-30D", "network": "mtn", "service": "data", "destination": "+234********78", "amount": 350.00, "price": 10.00, "failure_code": null, "failure_message": null, "created_at": "2026-10-06T12:06:38Z", "completed_at": "2026-10-06T12:06:41Z" }, "message": "Transaction successful", "code": "OK" } ``` `amount` is the plan's face value; `price` is the flat fee your wallet pays for the sale. The figures shown here are placeholders. ## Error A plan that cannot be sold right now is refused before anything is held: ```json { "success": false, "data": { "errors": [{ "field": "product_code", "message": "This plan is not sellable from lines: no charges are set for it" }] }, "message": "This plan is not sellable from lines: no charges are set for it", "code": "VALIDATION_FAILED" } ``` --- # Get a transaction Source: https://simconnectng.com/docs/api/get-transaction One sale, its current state and its full timeline. Returns the sale with every state it moved through, from `created` to `successful` or `failed`, each with a time and a reason. ## Request ```bash curl https://api.simconnectng.com/api/v1/transactions/txn_0192f4c1a7e84b6f9d2c3e5a7b9d1f30 \ -H "Authorization: Bearer $SIMCONNECT_KEY" ``` ## Response ```json { "success": true, "data": { "reference": "txn_0192f4c1a7e84b6f9d2c3e5a7b9d1f30", "client_reference": "order_1042", "status": "successful", "product_code": "MTN-VTU", "network": "mtn", "destination": "+234********78", "amount": 500.00, "price": 2.00, "attempt_count": 1, "created_at": "2026-10-06T12:04:01Z", "completed_at": "2026-10-06T12:04:03Z", "timeline": [ { "from_status": null, "to_status": "created", "reason": "accepted", "actor_type": "api_key", "candidate_kind": null, "provider": null, "provider_reference": null, "created_at": "2026-10-06T12:04:01.120Z" }, { "from_status": "created", "to_status": "queued", "reason": "hold placed", "actor_type": "system", "candidate_kind": null, "provider": null, "provider_reference": null, "created_at": "2026-10-06T12:04:01.131Z" }, { "from_status": "queued", "to_status": "processing", "reason": "executor started", "actor_type": "system", "candidate_kind": null, "provider": null, "provider_reference": null, "created_at": "2026-10-06T12:04:01.348Z" }, { "from_status": "processing", "to_status": "successful", "reason": "success", "actor_type": "system", "candidate_kind": "sim", "provider": "simgateway", "provider_reference": "8F2K41", "created_at": "2026-10-06T12:04:03.902Z" } ] }, "message": "OK", "code": "OK" } ``` The sale also carries the other fields of [Send airtime](/docs/api/send-airtime) (`id`, `mode`, `service`, `failure_code`, `failure_message`). `status` can also be `refunded`, when we refund a sale, and the timeline then gains that step. The figures shown here are placeholders. ## Error ```json { "success": false, "data": null, "message": "Transaction not found", "code": "NOT_FOUND" } ``` --- # List transactions Source: https://simconnectng.com/docs/api/list-transactions Your sales, newest first, with filters and pages. Returns up to `limit` sales, newest first. When `data.page.has_more` is true, pass `data.page.next_cursor` as `cursor` to get the next page. ## Query | Field | Type | Required | Notes | | --- | --- | --- | --- | | `status` | string | no | `successful`, `failed`, `pending` or `refunded` (also `created`, `queued` and `processing` for a sale just accepted) | | `network` | string | no | `mtn`, `airtel`, `glo` or `9mobile` | | `service` | string | no | `airtime` or `data` | | `destination` | string | no | An exact number, in `+234` form | | `from`, `to` | string | no | RFC 3339 times, both inclusive | | `search` | string | no | An exact reference, client reference or number | | `limit` | number | no | Up to 100, default 25 | | `cursor` | string | no | `next_cursor` from the previous page | ## Request ```bash curl "https://api.simconnectng.com/api/v1/transactions?status=failed&limit=25" \ -H "Authorization: Bearer $SIMCONNECT_KEY" ``` ## Response ```json { "success": true, "data": { "items": [ { "reference": "txn_0192f3e8c4a17b2d9e5f3a6c8b0d2e47", "client_reference": "order_1038", "status": "failed", "product_code": "GLO-VTU", "network": "glo", "destination": "+234********21", "amount": 200.00, "price": 2.00, "failure_code": "PROVIDER_REJECTED", "created_at": "2026-10-06T11:58:12Z", "completed_at": "2026-10-06T11:58:14Z" } ], "page": { "next_cursor": "eyJ0IjoiMjAyNi0xMC0wNlQxMTo1ODoxMloifQ", "has_more": true } }, "message": "OK", "code": "OK" } ``` Each item carries the same fields as [Get a transaction](/docs/api/get-transaction), without the timeline. A failed sale's `price` is never charged: its hold is released. The figures shown here are placeholders. ## Error ```json { "success": false, "data": { "errors": [{ "field": "status", "message": "must be one of created, queued, processing, pending, successful, failed, reversed, refunded" }] }, "message": "must be one of created, queued, processing, pending, successful, failed, reversed, refunded", "code": "VALIDATION_FAILED" } ``` --- # Get wallet Source: https://simconnectng.com/docs/api/get-wallet Your balance, what is held, and what is available. Returns the wallet for the mode of the key you call with. ## Request ```bash curl https://api.simconnectng.com/api/v1/wallet/balance \ -H "Authorization: Bearer $SIMCONNECT_KEY" ``` ## Response ```json { "success": true, "data": { "id": "0192e1a0-4b7c-7d2e-8f9a-1b2c3d4e5f60", "mode": "live", "currency": "NGN", "balance": 24850.00, "held": 12.00, "available": 24838.00, "low_balance_threshold": 5000.00, "updated_at": "2026-10-06T12:04:03Z" }, "message": "OK", "code": "OK" } ``` `held` is the fees of sales still on their way. `low_balance_threshold` is your alert level: when `balance` drops below it you get a `wallet.low_balance` [webhook](/docs/concepts/webhooks). The figures shown here are placeholders. ## Error ```json { "success": false, "data": null, "message": "Invalid API key", "code": "AUTH_REQUIRED" } ``` --- # List channels Source: https://simconnectng.com/docs/api/list-channels Every line you sell through, with its credit and health. Returns each channel your sales go out through, with credit, status and success rate over the last 24 hours. A live key lists your lines; a test key lists the test channel. Pages work as in [List transactions](/docs/api/list-transactions): `limit` (up to 100, default 25) and `cursor`. ## Request ```bash curl https://api.simconnectng.com/api/v1/channels \ -H "Authorization: Bearer $SIMCONNECT_KEY" ``` ## Response ```json { "success": true, "data": { "items": [ { "id": "0192d7b2-6c1a-7f3e-9b4d-2a5c8e1f7b30", "kind": "line", "network": "mtn", "status": "active", "balance": 32050.00, "success_rate": 0.98 }, { "id": "0192d7b2-9e4f-7a1c-8d2b-5f6a3c9e0d41", "kind": "line", "network": "glo", "status": "low_credit", "balance": 3900.00, "success_rate": 0.95 } ], "page": { "next_cursor": null, "has_more": false } }, "message": "OK", "code": "OK" } ``` A line's `status` is `active`, `low_credit`, `disabled`, `blocked` or `reconnect` (its network session ended and it needs connecting again). Lines low on credit step aside from routing on their own. `success_rate` runs from 0 to 1. In test mode the one channel has `id` `mock`, a `network` and `balance` of `null`, and serves every network. The figures shown here are placeholders. ## Error ```json { "success": false, "data": null, "message": "API key required", "code": "AUTH_REQUIRED" } ``` --- # List networks Source: https://simconnectng.com/docs/api/list-networks The networks you can sell on right now. Returns each network on sale, by `code`. Only networks on sale are listed. For the services, call `GET /api/v1/catalog/services`; for one network's plans, see [List plans](/docs/api/list-data-plans). ## Request ```bash curl https://api.simconnectng.com/api/v1/catalog/networks \ -H "Authorization: Bearer $SIMCONNECT_KEY" ``` ## Response ```json { "success": true, "data": { "items": [ { "code": "mtn", "name": "MTN" }, { "code": "airtel", "name": "Airtel" }, { "code": "glo", "name": "Glo" }, { "code": "9mobile", "name": "9mobile" } ] }, "message": "OK", "code": "OK" } ``` ## Error ```json { "success": false, "data": null, "message": "API key required", "code": "AUTH_REQUIRED" } ```