Build on Danmalama
The Danmalama API lets you sell airtime, data, electricity, and cable TV subscriptions directly from your own platform, using the same wallet, pricing, and provider connections that power the Danmalama app. Every endpoint below is documented against the live, production API — not a simplified or hypothetical version of it.
https://api.danmalama.com.ng/api
This API is account-based: you integrate as a Danmalama user, using your own wallet balance to fund purchases you make on behalf of your customers. There is no separate "merchant" account type — any Danmalama account can generate an API key and start integrating immediately.
If your own system already integrates against another VTU reseller API, you don't have to rewrite it to reach Danmalama — two compatibility surfaces, MSORG and ADEX, accept those APIs' exact request/response shapes and auth conventions, internally calling the same purchase engine as the native API above. See the Compatibility APIs section before you start — there's one auth detail (a transaction PIN neither source API has a concept of) that's easy to miss and will 401 every request until you know about it.
Sandbox
Pick a shape and an endpoint, then send a real request straight from your browser to
api.danmalama.com.ng. This isn't a mockup — it's a live call
against the production API, using your own credentials. Purchase endpoints spend real
wallet balance.
Your key/token is used only for the request you send, never logged, and never sent anywhere but Danmalama's own API — kept in this tab's session storage at most, cleared when you close it.
Authentication
Every request to the native API is authenticated with an API key, sent in the
X-API-Key header. (MSORG and ADEX use a different convention —
see Compatibility APIs.)
Getting a key
API keys are generated from inside the Danmalama app, not through this API — open the app, go to Settings → API Access, and tap Enable. The raw key is shown exactly once. If you lose it, generate a new one (this immediately invalidates the old one — there's no overlap period), or disable API access entirely from the same screen.
X-API-Key: dmk_live_3f9a1b2c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e
Keys are prefixed dmk_live_ followed by 48 hex characters (57
characters total). Danmalama stores only a one-way hash of your key — support cannot
look up or recover a lost key for you, and neither can you retrieve it again from the
app after the first time it's shown.
There is currently no way to scope a key to "purchases only." A key authenticates as your full Danmalama account — the same as being logged into the app — and can call any endpoint your account can reach, including managing your own API access, changing your PIN, and generating a wallet funding account. Treat it exactly like a password: never embed it in a mobile app, browser extension, or any client your end users can inspect. Keep it server-side only, in your own backend. The same rule applies to the MSORG/ADEX tokens below — they're built directly from this key.
Requests & Responses
Send request bodies as JSON with Content-Type: application/json. Every native-API response, success or failure, is wrapped in the same envelope. (MSORG and ADEX use their own, different envelopes — documented in each compatibility section.)
Success
{
"success": true,
"data": { ... }
}Error
{
"success": false,
"error": {
"code": "INSUFFICIENT_BALANCE",
"message": "Insufficient wallet balance.",
"details": null
}
}error.details is present only for some errors — most carry just a code and a human-readable message. See Errors for the one case where details has a fixed, parseable shape.
Errors
Every error uses one of these codes. The HTTP status is always derived from the code — never inspect the status alone without checking error.code, since 400 covers three distinct codes.
| Code | Status | Meaning |
|---|---|---|
VALIDATION_ERROR | 400 | Request body failed validation, or a business rule rejected it (e.g. amount below minimum). |
INSUFFICIENT_BALANCE | 400 | Your wallet balance is lower than the purchase amount. |
UNAUTHORIZED | 401 | Missing or invalid X-API-Key. |
FORBIDDEN | 403 | Your account is frozen, or you don't own the resource you're trying to access. |
NOT_FOUND | 404 | The resource (transaction, voucher, route) doesn't exist. |
CONFLICT | 409 | The request conflicts with existing state — a duplicate in-flight purchase, a voucher you already redeemed, a transaction already refunded. |
RATE_LIMITED | 429 | Reserved for account-level limits (see Rate Limits for how the general per-IP limit differs). |
PIN_LOCKED | 429 | Too many failed transaction PIN attempts — temporarily locked. |
PROVIDER_ERROR | 502 | The upstream network/disco/cable provider declined or errored. Your wallet is automatically refunded before this is returned. |
INTERNAL_ERROR | 500 | Unexpected server error. Safe to retry. |
A purchase can return 200 with the transaction's status field set to processing rather than successful — this happens when the upstream provider doesn't respond in time, not when it declines. Your wallet is still debited. Poll Get a transaction until the status resolves to successful, failed, or refunded. A prompt decline is never a 200 — it comes back as PROVIDER_ERROR with the debit already reversed. The same underlying behavior applies to purchases made through MSORG/ADEX — only the response envelope differs.
When a request body fails schema validation (missing field, wrong type, out-of-range value), error.details has this specific shape:
{
"success": false,
"error": {
"code": "VALIDATION_ERROR",
"message": "Invalid request",
"details": {
"formErrors": [],
"fieldErrors": {
"phone": ["Enter an 11-digit phone number starting with 0"],
"amount": ["Number must be greater than 0"]
}
}
}
}Rate Limits
Two limits apply on the native API, tracked separately:
The stricter 15/minute limit applies to every purchase endpoint (airtime, data,
electricity, cable), meter/smartcard validation, refund requests, and every voucher
endpoint. Everything else (listing plans, discos, providers, your account details,
transaction history) uses the general 120/minute limit. The same 15/minute limiter also
protects the equivalent MSORG and ADEX purchase/validation endpoints — it's shared
infrastructure, not a separate allowance per entry point. ADEX's token-exchange endpoint
(POST /compat/adex/api/user) uses a separate, stricter limiter: 20
requests per 15 minutes per IP, the same one that protects native login.
Unlike every other error on this API, a rate-limit rejection is a plain text body —
Too many requests, please try again later. — not the
{success,error} envelope (or MSORG/ADEX's {status,message,code}
envelope). Detect it by status code, not by parsing the body as JSON. Standard rate-limit headers are included on every response:
RateLimit-Limit: 120 RateLimit-Policy: 120;w=60 RateLimit-Remaining: 0 RateLimit-Reset: 27 Retry-After: 27
Compatibility APIs
Two shim layers sit in front of the same purchase engine as the native API, shaped to match two competitor VTU reseller APIs exactly — so an already-integrated client can switch to Danmalama by changing only its base URL and token, no code changes. Both cover data, airtime, electricity, and cable only (plus each source's own account-check and transaction-lookup endpoints). Neither source API's result-checker/exam-PIN, bulk SMS, or printable recharge/data-card endpoints have a Danmalama equivalent, so those are deliberately not shimmed.
MSORG
MSORG matches the request/response field names, route paths, and auth header convention of a widely-used VTU reseller API. If you already have code talking to that API, point it at the base URL below with a Danmalama token and it should work with no other changes.
https://api.danmalama.com.ng/compat/msorg
The source API has no transaction-PIN concept, but every Danmalama purchase requires one regardless of entry point. So the PIN travels folded into the token itself:
dmk_live_xxxxx:1234 (your API key, a colon, your 4-digit transaction PIN)
Sent as Authorization: Token dmk_live_xxxxx:1234 on every request.
Build this once from Settings → API Access (for the key) and your own
PIN, then paste the whole string wherever your existing code already
sends its token. Forgetting the :1234 suffix — or sending just
the raw API key, the way you would to the native API's X-API-Key
header — is the single most common cause of a 401 here.
Network IDs
The source API's own documented requests use small numeric network ids. These are real, fixed conventions in the shipped code — MSORG accepts either form:
| Numeric id | Network | Name form (also accepted) |
|---|---|---|
1 | MTN | MTN |
2 | Airtel | AIRTEL |
3 | Glo | GLO |
4 | 9mobile | 9MOBILE |
Meter type
| Value | Meaning |
|---|---|
prepaid or 1 | Prepaid meter |
postpaid or 2 | Postpaid meter |
The source API's own documentation has no saved example responses at all — only its requests are documented. Every response shape shown in the MSORG sections below is Danmalama's own reasonable design for this shim, not a guarantee that it matches what that real API actually returns byte-for-byte. If your existing integration strictly parses specific response fields, verify against what you actually get back before relying on it in production.
plan, disconame/disco_name, and
cablename/cableplan values must come from
Danmalama's own live catalog — List data plans,
List discos, and List providers on
the native API (your MSORG API key works there too — same account, just with the
X-API-Key header instead of Authorization: Token).
MSORG does not accept the source API's own numeric catalog ids for plans, discos, or
cable billers/items — only the network id (1–4) and meter-type mapping above are genuinely
portable conventions between the two APIs.
Error envelope
MSORG errors use a shape shared with ADEX — { status, message, code } —
distinct from both the native API's {success,error{...}} envelope
and from MSORG's own success shape:
{
"status": "error",
"message": "mobile_number and plan are required.",
"code": "VALIDATION_ERROR"
}
Note that status is a boolean true on some
successful MSORG responses (account lookup, listings, validation) and a string
"success" on others (purchase endpoints, mirroring the underlying
transaction status like "processing" or "failed") — and a
different string, "error", on failure. Don't write a single
=== true check across every MSORG endpoint; check the HTTP status
code first, then branch on whichever form of status that specific
endpoint actually documents below.
ADEX
ADEX matches the request/response field names and auth flow of another widely-used VTU reseller API, including its two-step auth exchange.
https://api.danmalama.com.ng/compat/adex
The source API has no transaction-PIN concept either, but Danmalama requires one on every purchase. Its auth flow is HTTP Basic → bearer token, so the PIN is folded into the Basic auth password field instead of the token:
Step 1 — exchange for a token. POST /compat/adex/api/user with
HTTP Basic auth, where:
<your account password>:<your 4-digit PIN> — password, a colon, then the PINThis returns { status, AccessToken, balance, username } — AccessToken is
already the same <api-key>:<pin> composite token MSORG uses, ready
to use as-is.
Step 2 — use it. Every other ADEX call sends
Authorization: Token <AccessToken>, exactly like MSORG.
Requesting a new token this way rotates your account's API key — any previously issued key (native, MSORG, or ADEX) stops working immediately. Don't call this more than once per credential change, and if you do, update every other integration you have running against the old key.
Get a token
curl -X POST https://api.danmalama.com.ng/compat/adex/api/user \ -u "you@example.com:your_account_password:1234"
const creds = btoa("you@example.com:your_account_password:1234");
const res = await fetch("https://api.danmalama.com.ng/compat/adex/api/user", {
method: "POST",
headers: { Authorization: `Basic ${creds}` }
});
const { AccessToken } = await res.json();
// AccessToken is "<api-key>:<pin>" -- use it as Authorization: Token <AccessToken>
// on every other ADEX call below.Response
{
"status": "success",
"AccessToken": "dmk_live_your_key_here:1234",
"balance": "15230.00",
"username": "you@example.com"
}Network IDs & meter type
ADEX shares the exact same network-id and meter-type conventions as MSORG (same underlying helper in Danmalama's code):
| Numeric id | Network |
|---|---|
1 | MTN |
2 | Airtel |
3 | Glo |
4 | 9mobile |
meter_type: prepaid or 1; postpaid or 2.
Unlike MSORG, the ADEX source API's own documentation did publish real example responses, and ADEX
mirrors them closely — field names like oldbal/newbal,
wallet_vending, and system come straight from those
documented examples. You can rely on these shapes with more confidence than MSORG's —
though they're still Danmalama's own implementation of that shim, not a live proxy to
that other API, so treat them as accurate-as-documented rather than contractually guaranteed
forever.
Exactly the same constraint as MSORG: data_plan, disco,
and cablename/cableplan must be real Danmalama ids from
List data plans, List discos, and
List providers — ADEX does not accept the source API's own catalog ids.
Only the network id (1–4) and meter-type mapping above are genuinely portable.
Error envelope
Same shape as MSORG (shared code): { status: "error", message, code }. ADEX's success responses are more consistent than MSORG's — every purchase and validation endpoint documented below uses the string "success", never a bare boolean.
{
"status": "error",
"message": "phone and data_plan are required.",
"code": "VALIDATION_ERROR"
}Returns your account details, including your current wallet balance. Shown in three shapes below — Danmalama's own API, and the two drop-in compatibility modes.
X-API-Key: dmk_live_...There is no separate "check balance" endpoint — use this one, or read the balance field returned by any purchase.
curl https://api.danmalama.com.ng/api/auth/me \ -H "X-API-Key: dmk_live_your_key_here"
const res = await fetch("https://api.danmalama.com.ng/api/auth/me", {
headers: { "X-API-Key": process.env.DANMALAMA_API_KEY }
});
const { data } = await res.json();
console.log(data.user.walletBalance);Response
{
"success": true,
"data": {
"user": {
"id": "68f1a2b3c4d5e6f7a8b9c0d1",
"name": "Adiahn Danmalama",
"email": "you@example.com",
"phone": "08031234567",
"avatarUrl": null,
"walletBalance": 15230,
"bonusBalance": 0,
"gafiaBankName": "Wema Bank",
"gafiaAccountNumber": "8123456789",
"role": "customer",
"hasPin": true,
"profileComplete": true,
"referralCode": "DAN-4F2A",
"referralEarnings": 350,
"createdAt": "2026-01-14T09:22:11.000Z"
}
}
}Authorization: Token <api-key>:<pin>GET /api/user/ — exactly as documented, including the trailing slash.
curl https://api.danmalama.com.ng/compat/msorg/api/user/ \ -H "Authorization: Token dmk_live_your_key_here:1234"
const res = await fetch("https://api.danmalama.com.ng/compat/msorg/api/user/", {
headers: { Authorization: "Token dmk_live_your_key_here:1234" }
});
const data = await res.json();
console.log(data.user.wallet_balance);Response
{
"status": true,
"user": {
"id": "68f1a2b3c4d5e6f7a8b9c0d1",
"username": "you@example.com",
"email": "you@example.com",
"full_name": "Adiahn Danmalama",
"wallet_balance": "15230.00"
}
}wallet_balance is a string (fixed to 2 decimal places) here, unlike the native API's numeric walletBalance — parse it before doing arithmetic on it.
email:password:pin
ADEX has no separate "who am I" call — account info (balance, username) comes back
as part of the token-exchange step itself, POST /api/user, fully
documented in the ADEX overview above. Repeated here for
convenience:
curl -X POST https://api.danmalama.com.ng/compat/adex/api/user \ -u "you@example.com:your_account_password:1234"
const creds = btoa("you@example.com:your_account_password:1234");
const res = await fetch("https://api.danmalama.com.ng/compat/adex/api/user", {
method: "POST",
headers: { Authorization: `Basic ${creds}` }
});
const { balance, username } = await res.json();Response
{
"status": "success",
"AccessToken": "dmk_live_your_key_here:1234",
"balance": "15230.00",
"username": "you@example.com"
}This call also rotates your API key — see the callout in the ADEX overview before calling it more than you need to.
Airtime
Direct top-ups for MTN, Airtel, Glo, and 9mobile. Available in three shapes below — Danmalama's own API, and the MSORG/ADEX compatibility modes.
Buys airtime and credits it directly to the recipient's phone number. Capped at ₦5,000 per hour per account (summed across successful purchases in the trailing 60 minutes) on every entry point — they share the same underlying purchase engine and limit.
X-API-Key: dmk_live_...| Field | Type | Description | |
|---|---|---|---|
network | string | required | One of MTN, AIRTEL, GLO, 9MOBILE. |
phone | string | required | 11-digit Nigerian number starting with 0, e.g. 08031234567. |
amount | integer | required | Naira amount, whole number, greater than 0. |
pin | string | required | Your account's 4-digit transaction PIN. |
bypassNetworkCheck | boolean | optional | Default false. The API rejects a number whose prefix doesn't match the given network (likely the wrong network selected) unless this is true — useful for genuinely ported numbers. |
curl -X POST https://api.danmalama.com.ng/api/vending/airtime \
-H "X-API-Key: dmk_live_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"network": "MTN",
"phone": "08031234567",
"amount": 500,
"pin": "1234"
}'const res = await fetch("https://api.danmalama.com.ng/api/vending/airtime", {
method: "POST",
headers: {
"X-API-Key": process.env.DANMALAMA_API_KEY,
"Content-Type": "application/json"
},
body: JSON.stringify({
network: "MTN",
phone: "08031234567",
amount: 500,
pin: "1234"
})
});
const { data } = await res.json();Response
{
"success": true,
"data": {
"transaction": {
"_id": "68f9a1b2c3d4e5f6a7b8c9d0",
"reference": "AIRTIME-3F9A1B2C4D5E",
"type": "airtime",
"provider": "MTN",
"phone": "08031234567",
"amount": 500,
"walletDeducted": 500,
"bonusDeducted": 0,
"status": "successful",
"verificationAttempts": 0,
"createdAt": "2026-09-30T10:12:03.000Z",
"updatedAt": "2026-09-30T10:12:05.000Z"
},
"balance": 14730
}
}Authorization: Token <api-key>:<pin>POST /api/topup/ — no pin field: it travels inside your token (see MSORG overview).
| Field | Type | Description | |
|---|---|---|---|
network | string/number | required | MTN/AIRTEL/GLO/9MOBILE, or the numeric id 1–4. |
mobile_number | string | required | 11-digit Nigerian number starting with 0. |
amount | integer | required | Naira amount, greater than 0. |
Ported_number | boolean | optional | Default false. Same network/phone-prefix bypass as the native API's bypassNetworkCheck. |
curl -X POST https://api.danmalama.com.ng/compat/msorg/api/topup/ \
-H "Authorization: Token dmk_live_your_key_here:1234" \
-H "Content-Type: application/json" \
-d '{
"network": "MTN",
"mobile_number": "08031234567",
"amount": 500
}'const res = await fetch("https://api.danmalama.com.ng/compat/msorg/api/topup/", {
method: "POST",
headers: {
Authorization: "Token dmk_live_your_key_here:1234",
"Content-Type": "application/json"
},
body: JSON.stringify({
network: "MTN",
mobile_number: "08031234567",
amount: 500
})
});
const data = await res.json();Response
{
"status": "success",
"id": "68f9a1b2c3d4e5f6a7b8c9d0",
"network": "MTN",
"mobile_number": "08031234567",
"amount": 500,
"message": "₦500 airtime sent to 08031234567.",
"oldbal": 15230,
"newbal": 14730
}Authorization: Token <AccessToken>POST /api/topup — no trailing slash, no pin field (it's inside the token).
| Field | Type | Description | |
|---|---|---|---|
network | string/number | required | Name or numeric id 1–4. |
phone | string | required | 11-digit Nigerian number starting with 0. |
amount | integer | required | Naira amount, greater than 0. |
request-id | string | optional | Your own idempotency/reference id — echoed back verbatim. Defaults to Danmalama's transaction id if omitted. |
bypass | boolean | optional | Default false. Same network/phone-prefix bypass. |
plan_type | string | optional | Default VTU. Echoed back verbatim — not validated against anything. |
curl -X POST https://api.danmalama.com.ng/compat/adex/api/topup \
-H "Authorization: Token dmk_live_your_key_here:1234" \
-H "Content-Type: application/json" \
-d '{
"network": "MTN",
"phone": "08031234567",
"amount": 500
}'const res = await fetch("https://api.danmalama.com.ng/compat/adex/api/topup", {
method: "POST",
headers: {
Authorization: "Token dmk_live_your_key_here:1234",
"Content-Type": "application/json"
},
body: JSON.stringify({
network: "MTN",
phone: "08031234567",
amount: 500
})
});
const data = await res.json();Response
{
"network": "MTN",
"request-id": "68f9a1b2c3d4e5f6a7b8c9d0",
"amount": 500,
"discount": 0,
"status": "success",
"message": "Successfully purchased MTN VTU ₦500 for 08031234567",
"phone_number": "08031234567",
"oldbal": "15230",
"newbal": 14730,
"system": "API",
"plan_type": "VTU",
"wallet_vending": "wallet"
}oldbal is a string and newbal is a number — an inherited quirk of this shim, not a typo. Coerce both with Number() before doing arithmetic.
Data
Data bundles across all four networks. Plans and prices are account-specific — always fetch the live list rather than hardcoding plan IDs. Buying is available in three shapes below; listing plans is native-only (MSORG/ADEX purchases still require a real Danmalama planId from this list).
Lists every data plan you can buy, with pricing already resolved for your account (including any discount you've been assigned). planId is opaque — pass it straight through to Buy data, don't parse it.
curl https://api.danmalama.com.ng/api/vending/data/plans \ -H "X-API-Key: dmk_live_your_key_here"
Response
{
"success": true,
"data": [
{ "planId": "173", "network": "MTN", "label": "1GB - 30 Days", "price": 550 },
{ "planId": "174", "network": "MTN", "label": "2GB - 30 Days", "price": 900 },
{ "planId": "410", "network": "AIRTEL", "label": "5GB Weekly Plan - 7 Days", "price": 1650 }
]
}These planId values are illustrative, not a fixed contract — always fetch live. This same list is what MSORG's plan field and ADEX's data_plan field must contain.
Buys a data plan and delivers it to the recipient's phone number.
X-API-Key: dmk_live_...| Field | Type | Description | |
|---|---|---|---|
network | string | required | One of MTN, AIRTEL, GLO, 9MOBILE — must match the plan's network. |
phone | string | required | 11-digit Nigerian number starting with 0. |
planId | string | required | From List data plans. |
pin | string | required | Your account's 4-digit transaction PIN. |
bypassNetworkCheck | boolean | optional | Default false. Same network/phone-prefix check as airtime. |
curl -X POST https://api.danmalama.com.ng/api/vending/data \
-H "X-API-Key: dmk_live_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"network": "MTN",
"phone": "08031234567",
"planId": "173",
"pin": "1234"
}'const res = await fetch("https://api.danmalama.com.ng/api/vending/data", {
method: "POST",
headers: {
"X-API-Key": process.env.DANMALAMA_API_KEY,
"Content-Type": "application/json"
},
body: JSON.stringify({
network: "MTN",
phone: "08031234567",
planId: "173",
pin: "1234"
})
});
const { data } = await res.json();Response
{
"success": true,
"data": {
"transaction": {
"_id": "68f9a1b2c3d4e5f6a7b8c9d1",
"reference": "DATA-BD029DE81705",
"type": "data",
"provider": "MTN",
"planId": "173",
"phone": "08031234567",
"amount": 550,
"walletDeducted": 550,
"bonusDeducted": 0,
"status": "successful",
"verificationAttempts": 0,
"createdAt": "2026-09-30T10:14:00.000Z",
"updatedAt": "2026-09-30T10:14:02.000Z"
},
"balance": 14180
}
}Authorization: Token <api-key>:<pin>POST /api/data/. plan must be a real Danmalama planId — not the source API's own plan code.
| Field | Type | Description | |
|---|---|---|---|
network | string/number | required | Name or numeric id 1–4. |
mobile_number | string | required | 11-digit Nigerian number starting with 0. |
plan | string | required | Danmalama planId from List data plans. |
Ported_number | boolean | optional | Default false. |
curl -X POST https://api.danmalama.com.ng/compat/msorg/api/data/ \
-H "Authorization: Token dmk_live_your_key_here:1234" \
-H "Content-Type: application/json" \
-d '{
"network": "MTN",
"mobile_number": "08031234567",
"plan": "173"
}'const res = await fetch("https://api.danmalama.com.ng/compat/msorg/api/data/", {
method: "POST",
headers: {
Authorization: "Token dmk_live_your_key_here:1234",
"Content-Type": "application/json"
},
body: JSON.stringify({
network: "MTN",
mobile_number: "08031234567",
plan: "173"
})
});
const data = await res.json();Response
{
"status": "success",
"id": "68f9a1b2c3d4e5f6a7b8c9d1",
"network": "MTN",
"mobile_number": "08031234567",
"plan": "173",
"amount": 550,
"message": "Data plan delivered to 08031234567.",
"oldbal": 15230,
"newbal": 14680
}MSORG also exposes GET /api/data/ (lists your data-type transactions) and GET /api/data/:id (looks up one by id — doubles as the airtime lookup too). Both are covered under Get a transaction.
Authorization: Token <AccessToken>POST /api/data. data_plan must be a real Danmalama planId — not the source API's own plan code.
| Field | Type | Description | |
|---|---|---|---|
network | string/number | required | Name or numeric id 1–4. |
phone | string | required | 11-digit Nigerian number starting with 0. |
data_plan | string | required | Danmalama planId. |
request-id | string | optional | Echoed back verbatim; defaults to the transaction id. |
bypass | boolean | optional | Default false. |
curl -X POST https://api.danmalama.com.ng/compat/adex/api/data \
-H "Authorization: Token dmk_live_your_key_here:1234" \
-H "Content-Type: application/json" \
-d '{
"network": "MTN",
"phone": "08031234567",
"data_plan": "173"
}'const res = await fetch("https://api.danmalama.com.ng/compat/adex/api/data", {
method: "POST",
headers: {
Authorization: "Token dmk_live_your_key_here:1234",
"Content-Type": "application/json"
},
body: JSON.stringify({
network: "MTN",
phone: "08031234567",
data_plan: "173"
})
});
const data = await res.json();Response
{
"network": "MTN",
"request-id": "68f9a1b2c3d4e5f6a7b8c9d1",
"amount": "550",
"dataplan": "173",
"status": "success",
"message": "Data delivered to 08031234567.",
"response": "Data delivered to 08031234567.",
"phone_number": "08031234567",
"oldbal": "15230",
"newbal": 14680,
"system": "API",
"plan_type": "GIFTING",
"wallet_vending": "wallet"
}amount and oldbal are strings here, newbal is a number — same coerce-before-you-add caution as Buy airtime.
Electricity
Prepaid and postpaid electricity across every major Nigerian disco. Validate/buy are available in three shapes below; listing discos is native-only.
Lists every electricity distribution company (disco) you can buy for.
curl https://api.danmalama.com.ng/api/vending/electricity/discos \ -H "X-API-Key: dmk_live_your_key_here"
Response
{
"success": true,
"data": [
{ "discoId": "ikeja-electric", "name": "Ikeja Electric" },
{ "discoId": "eko-electric", "name": "Eko Electric (EKEDC)" },
{ "discoId": "abuja-electric", "name": "Abuja Electric (AEDC)" }
]
}Treat discoId values as opaque and always fetched live — they're illustrative above, not a fixed contract. This is also the only source of valid disconame/disco_name/disco values for MSORG and ADEX below — neither accepts the source APIs' own disco ids.
Resolves the customer name on a meter number before you charge anyone. This does not debit your wallet on any entry point.
X-API-Key: dmk_live_...| Field | Type | Description | |
|---|---|---|---|
discoId | string | required | From List discos. |
meterNumber | string | required | |
meterType | string | required | prepaid or postpaid. |
amount | integer | required | The amount you intend to purchase — some discos validate differently per amount. |
curl -X POST https://api.danmalama.com.ng/api/vending/electricity/validate-meter \
-H "X-API-Key: dmk_live_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"discoId": "ikeja-electric",
"meterNumber": "04123456789",
"meterType": "prepaid",
"amount": 5000
}'Response
{
"success": true,
"data": {
"customerName": "ADEBAYO O. FASHOLA",
"address": "12 Allen Avenue, Ikeja, Lagos"
}
}Authorization: Token <api-key>:<pin>GET /api/validatemeter — a GET with query-string parameters, not a POST body.
| Query param | Type | Description | |
|---|---|---|---|
disconame | string | required | Danmalama discoId. |
meternumber | string | required | |
mtype | string/number | required | prepaid/1 or postpaid/2. |
amount | integer | optional | Default 1000 if omitted. |
curl "https://api.danmalama.com.ng/compat/msorg/api/validatemeter?disconame=ikeja-electric&meternumber=04123456789&mtype=prepaid&amount=5000" \ -H "Authorization: Token dmk_live_your_key_here:1234"
Response
{
"status": true,
"customer_name": "ADEBAYO O. FASHOLA",
"address": "12 Allen Avenue, Ikeja, Lagos"
}Authorization: Token <AccessToken>GET /api/bill/bill-validation.
| Query param | Type | Description | |
|---|---|---|---|
disco | string | required | Danmalama discoId. |
meter_number | string | required | |
meter_type | string/number | required | prepaid/1 or postpaid/2. |
amount | integer | optional | Default 1000. |
curl "https://api.danmalama.com.ng/compat/adex/api/bill/bill-validation?disco=ikeja-electric&meter_number=04123456789&meter_type=prepaid&amount=5000" \ -H "Authorization: Token dmk_live_your_key_here:1234"
Response
{
"status": "success",
"name": "ADEBAYO O. FASHOLA"
}Field is name, not customer_name — and there's no address at all here, unlike native and MSORG.
Buys electricity credit. Minimum ₦1,000 on every entry point.
X-API-Key: dmk_live_...customerName must be the value returned by Validate meter — don't ask your own users to type it in.
| Field | Type | Description | |
|---|---|---|---|
discoId | string | required | |
meterNumber | string | required | |
meterType | string | required | prepaid or postpaid. |
amount | integer | required | Minimum ₦1,000. |
customerName | string | required | From Validate meter's response. |
pin | string | required | Your account's 4-digit transaction PIN. |
curl -X POST https://api.danmalama.com.ng/api/vending/electricity \
-H "X-API-Key: dmk_live_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"discoId": "ikeja-electric",
"meterNumber": "04123456789",
"meterType": "prepaid",
"amount": 5000,
"customerName": "ADEBAYO O. FASHOLA",
"pin": "1234"
}'Response
{
"success": true,
"data": {
"transaction": {
"_id": "68f9a1b2c3d4e5f6a7b8c9d2",
"reference": "ELECTRICITY-9C1D2E3F4A5B",
"type": "electricity",
"provider": "ikeja-electric",
"phone": "04123456789",
"amount": 5000,
"walletDeducted": 5000,
"bonusDeducted": 0,
"status": "successful",
"providerResponse": { "token": "1234-5678-9012-3456-7890" },
"verificationAttempts": 0,
"createdAt": "2026-09-30T10:20:11.000Z",
"updatedAt": "2026-09-30T10:20:14.000Z"
},
"balance": 9180
}
}The prepaid token (when applicable) is inside providerResponse — its exact shape depends on the disco.
Authorization: Token <api-key>:<pin>POST /api/billpayment/. Unlike the native API, there's no customerName field — this endpoint validates the meter itself internally before purchasing, using whatever name comes back.
| Field | Type | Description | |
|---|---|---|---|
disco_name | string | required | Danmalama discoId. |
meter_number | string | required | |
MeterType | string/number | required | Note the PascalCase field name — prepaid/1 or postpaid/2. |
amount | integer | required | Minimum 1000. |
curl -X POST https://api.danmalama.com.ng/compat/msorg/api/billpayment/ \
-H "Authorization: Token dmk_live_your_key_here:1234" \
-H "Content-Type: application/json" \
-d '{
"disco_name": "ikeja-electric",
"meter_number": "04123456789",
"MeterType": "prepaid",
"amount": 5000
}'Response
{
"status": "success",
"id": "68f9a1b2c3d4e5f6a7b8c9d2",
"disco_name": "ikeja-electric",
"meter_number": "04123456789",
"amount": 5000,
"token": "1234-5678-9012-3456-7890",
"message": "Electricity payment successful.",
"oldbal": 14680,
"newbal": 9680
}token is only present for prepaid meters where the disco returned one — omitted otherwise.
Authorization: Token <AccessToken>POST /api/bill. Also auto-validates the meter internally — no customerName field needed.
| Field | Type | Description | |
|---|---|---|---|
disco | string | required | Danmalama discoId. |
meter_number | string | required | |
meter_type | string/number | required | prepaid/1 or postpaid/2. |
amount | integer | required | Minimum 1000. |
request-id | string | optional | Echoed back; defaults to the transaction id. |
curl -X POST https://api.danmalama.com.ng/compat/adex/api/bill \
-H "Authorization: Token dmk_live_your_key_here:1234" \
-H "Content-Type: application/json" \
-d '{
"disco": "ikeja-electric",
"meter_number": "04123456789",
"meter_type": "prepaid",
"amount": 5000
}'Response
{
"disco_name": "ikeja-electric",
"request-id": "68f9a1b2c3d4e5f6a7b8c9d2",
"amount": 5000,
"charges": 0,
"status": "success",
"message": "Transaction successful ikeja-electric PREPAID ₦5000 to 04123456789",
"meter_number": "04123456789",
"meter_type": "PREPAID",
"oldbal": "14680",
"newbal": 9680,
"system": "API",
"token": "1234-5678-9012-3456-7890",
"wallet_vending": "wallet"
}meter_type is echoed back upper-cased in the response. charges is always 0 — Danmalama doesn't add a separate convenience fee on this shim.
Cable TV
DStv, GOtv, StarTimes, and Showmax subscriptions — new plan changes or straight renewals. Validate/buy are available in three shapes below; listing providers is native-only.
Lists every cable provider and their available plans.
curl https://api.danmalama.com.ng/api/vending/cable/providers \ -H "X-API-Key: dmk_live_your_key_here"
Response
{
"success": true,
"data": [
{
"billerId": "dstv",
"name": "DStv",
"plans": [
{ "itemId": "dstv-compact", "label": "DStv Compact", "amount": 19000 },
{ "itemId": "dstv-premium", "label": "DStv Premium", "amount": 44500 }
]
},
{
"billerId": "gotv",
"name": "GOtv",
"plans": [
{ "itemId": "gotv-max", "label": "GOtv Max", "amount": 8500 }
]
}
]
}These billerId/itemId values are illustrative, not a fixed contract — always fetch live. This is also the only source of valid cablename/cableplan values for MSORG and ADEX below.
Resolves the customer name on a smartcard/IUC number.
X-API-Key: dmk_live_...If the smartcard already has an active subscription, renewalAmount is returned too — use it for a renew purchase instead of guessing the plan price.
| Field | Type | Description | |
|---|---|---|---|
billerId | string | required | From List providers. |
itemId | string | optional | A specific plan, if known. |
smartcardNumber | string | required |
curl -X POST https://api.danmalama.com.ng/api/vending/cable/validate-smartcard \
-H "X-API-Key: dmk_live_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"billerId": "dstv",
"smartcardNumber": "1234567890"
}'Response
{
"success": true,
"data": {
"customerName": "CHIOMA N. OKEKE",
"renewalAmount": 19000
}
}Authorization: Token <api-key>:<pin>GET /api/validateiuc.
| Query param | Type | Description | |
|---|---|---|---|
cablename | string | required | Danmalama billerId. |
smart_card_number | string | required |
curl "https://api.danmalama.com.ng/compat/msorg/api/validateiuc?cablename=dstv&smart_card_number=1234567890" \ -H "Authorization: Token dmk_live_your_key_here:1234"
Response
{
"status": true,
"customer_name": "CHIOMA N. OKEKE",
"renewal_amount": 19000
}Authorization: Token <AccessToken>GET /api/cable/cable-validation.
| Query param | Type | Description | |
|---|---|---|---|
cable | string | required | Danmalama billerId. |
iuc | string | required | Smartcard/IUC number. |
curl "https://api.danmalama.com.ng/compat/adex/api/cable/cable-validation?cable=dstv&iuc=1234567890" \ -H "Authorization: Token dmk_live_your_key_here:1234"
Response
{
"status": "success",
"name": "CHIOMA N. OKEKE"
}No renewal amount here at all — unlike native and MSORG, ADEX's validation response is just the resolved name. If you need the renewal price, call List providers on the native API instead.
Buys or renews a cable subscription. There's no amount field on any entry point — price is always resolved server-side.
X-API-Key: dmk_live_...Price is resolved from the catalog (change) or the freshly re-validated renewal amount (renew) — a client can never supply a price.
| Field | Type | Description | |
|---|---|---|---|
billerId | string | required | |
itemId | string | optional | Required when subscriptionType is change. |
smartcardNumber | string | required | |
customerName | string | required | From Validate smartcard. |
subscriptionType | string | optional | change (default) or renew. |
pin | string | required | Your account's 4-digit transaction PIN. |
curl -X POST https://api.danmalama.com.ng/api/vending/cable \
-H "X-API-Key: dmk_live_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"billerId": "dstv",
"itemId": "dstv-compact",
"smartcardNumber": "1234567890",
"customerName": "CHIOMA N. OKEKE",
"subscriptionType": "change",
"pin": "1234"
}'Response
{
"success": true,
"data": {
"transaction": {
"_id": "68f9a1b2c3d4e5f6a7b8c9d3",
"reference": "CABLE-1A2B3C4D5E6F",
"type": "cable",
"provider": "dstv",
"planId": "dstv-compact",
"phone": "1234567890",
"amount": 19000,
"walletDeducted": 19000,
"bonusDeducted": 0,
"status": "successful",
"verificationAttempts": 0,
"createdAt": "2026-09-30T10:25:40.000Z",
"updatedAt": "2026-09-30T10:25:42.000Z"
},
"balance": 30180
}
}Authorization: Token <api-key>:<pin>POST /api/cablesub/. No customerName field — this endpoint re-validates the smartcard internally before purchasing. subscriptionType is inferred automatically: sending cableplan means change, omitting it means renew.
| Field | Type | Description | |
|---|---|---|---|
cablename | string | required | Danmalama billerId. |
cableplan | string | optional | Danmalama itemId. Omit to renew the existing plan. |
smart_card_number | string | required |
curl -X POST https://api.danmalama.com.ng/compat/msorg/api/cablesub/ \
-H "Authorization: Token dmk_live_your_key_here:1234" \
-H "Content-Type: application/json" \
-d '{
"cablename": "dstv",
"cableplan": "dstv-compact",
"smart_card_number": "1234567890"
}'Response
{
"status": "success",
"id": "68f9a1b2c3d4e5f6a7b8c9d3",
"cablename": "dstv",
"cableplan": "dstv-compact",
"smart_card_number": "1234567890",
"amount": 19000,
"message": "Cable subscription successful.",
"oldbal": 44680,
"newbal": 25680
}Authorization: Token <AccessToken>POST /api/cable. Same auto-validate and cableplan-presence inference as MSORG.
| Field | Type | Description | |
|---|---|---|---|
cablename | string | required | Danmalama billerId. |
cableplan | string | optional | Danmalama itemId. Omit to renew. |
smart_card_number | string | required | |
request-id | string | optional | Echoed back; defaults to the transaction id. |
curl -X POST https://api.danmalama.com.ng/compat/adex/api/cable \
-H "Authorization: Token dmk_live_your_key_here:1234" \
-H "Content-Type: application/json" \
-d '{
"cablename": "dstv",
"cableplan": "dstv-compact",
"smart_card_number": "1234567890"
}'Response
{
"cablename": "dstv",
"cableplan": "dstv-compact",
"amount": 19000,
"status": "success",
"message": "Successfully subscribed dstv for Smart Card 1234567890",
"smart_card_number": "1234567890",
"request-id": "68f9a1b2c3d4e5f6a7b8c9d3",
"oldbal": 44680,
"newbal": 25680,
"system": "API"
}Unlike its own data/airtime/electricity responses, ADEX's cable oldbal here is a number, not a string — a further inconsistency inherited from the shim, worth defensive coercion regardless.
Transactions
Every purchase you make — airtime, data, electricity, cable, and voucher activity — lands here as a single unified record, regardless of whether it was made through the native API, MSORG, or ADEX. Listing is native-only; MSORG additionally exposes its own per-type listing (GET /api/data/ for data-type transactions) with no equivalent for airtime/electricity/cable or a combined "all types" list. Looking up a single transaction by id is documented below.
Lists your transactions, newest first, capped at 500. Filtering and pagination aren't available yet — filter client-side, or fetch a single transaction by id below once you have a reference to look up.
curl https://api.danmalama.com.ng/api/vending/history \ -H "X-API-Key: dmk_live_your_key_here"
Response
{
"success": true,
"data": [
{
"_id": "68f9a1b2c3d4e5f6a7b8c9d1",
"reference": "DATA-BD029DE81705",
"type": "data",
"provider": "MTN",
"planId": "173",
"phone": "08031234567",
"amount": 550,
"status": "successful",
"createdAt": "2026-09-30T10:14:00.000Z"
}
]
}Fetches one transaction by its MongoDB id. Use this to poll a processing purchase until it resolves. ADEX has no transaction-lookup-by-id endpoint in this compatibility layer — the purchase response itself (see Buy airtime / Buy data / etc. above) is the only confirmation an ADEX-shaped client gets back.
X-API-Key: dmk_live_...Use the MongoDB _id (not the reference string). Returns 404 NOT_FOUND both when the id doesn't exist and when it belongs to a different account — deliberate, so a bad request never confirms whether someone else's transaction id exists.
curl https://api.danmalama.com.ng/api/vending/history/68f9a1b2c3d4e5f6a7b8c9d1 \ -H "X-API-Key: dmk_live_your_key_here"
Response
{
"success": true,
"data": {
"_id": "68f9a1b2c3d4e5f6a7b8c9d1",
"reference": "DATA-BD029DE81705",
"type": "data",
"provider": "MTN",
"planId": "173",
"phone": "08031234567",
"amount": 550,
"walletDeducted": 550,
"bonusDeducted": 0,
"status": "successful",
"verificationAttempts": 0,
"createdAt": "2026-09-30T10:14:00.000Z",
"updatedAt": "2026-09-30T10:14:02.000Z"
}
}Authorization: Token <api-key>:<pin>MSORG has no single generic lookup — instead, three type-specific endpoints, matching how the source API documents its own "Query Data/Airtime/Electricity/Cable Transaction" calls. Use the one matching the transaction's type:
| Transaction type | Path |
|---|---|
| Data or Airtime | GET /api/data/:id |
| Electricity | GET /api/billpayment/:id |
| Cable | GET /api/cablesub/:id |
The data endpoint doubles as the airtime lookup too — the source's own docs point both at the same path.
curl https://api.danmalama.com.ng/compat/msorg/api/data/68f9a1b2c3d4e5f6a7b8c9d1 \ -H "Authorization: Token dmk_live_your_key_here:1234"
const res = await fetch(
"https://api.danmalama.com.ng/compat/msorg/api/data/68f9a1b2c3d4e5f6a7b8c9d1",
{ headers: { Authorization: "Token dmk_live_your_key_here:1234" } }
);
const data = await res.json();Response — data / airtime (/api/data/:id)
{
"id": "68f9a1b2c3d4e5f6a7b8c9d1",
"network": "MTN",
"mobile_number": "08031234567",
"plan": "173",
"amount": 550,
"status": "success",
"created_at": "2026-09-30T10:14:00.000Z"
}For an airtime transaction, plan comes back null — airtime purchases have no plan id.
Response — electricity (/api/billpayment/:id)
{
"status": true,
"id": "68f9a1b2c3d4e5f6a7b8c9d2",
"disco_name": "ikeja-electric",
"meter_number": "04123456789",
"amount": 5000,
"status_detail": "successful"
}Response — cable (/api/cablesub/:id)
{
"status": true,
"id": "68f9a1b2c3d4e5f6a7b8c9d3",
"cablename": "dstv",
"smart_card_number": "1234567890",
"amount": 19000,
"status_detail": "successful"
}Note the shapes genuinely differ across the three: data/airtime uses status (the string form) and created_at; electricity/cable use a boolean status plus a separate status_detail for the actual transaction status, and no timestamp at all. This isn't a documentation simplification — it's what the shipped code returns.
Flags a transaction for manual admin review. This does not refund anything itself — it's a request, reviewed by a human. Native API only — there's no MSORG/ADEX equivalent, since neither source API documents a refund-request endpoint.
| Field | Type | Description | |
|---|---|---|---|
transactionId | string | required | |
userNote | string | optional | Up to 500 characters, explaining what went wrong. |
curl -X POST https://api.danmalama.com.ng/api/vending/request-refund \
-H "X-API-Key: dmk_live_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"transactionId": "68f9a1b2c3d4e5f6a7b8c9d1",
"userNote": "Customer says data never landed on their line."
}'Response
{ "success": true, "data": { "ok": true } }| Code | Status | When |
|---|---|---|
NOT_FOUND | 404 | Transaction doesn't exist. |
FORBIDDEN | 403 | The transaction belongs to a different account. |
CONFLICT | 409 | Already refunded. |
Vouchers
Pre-paid, shareable codes redeemable for wallet credit, airtime, data, electricity, or
cable — useful for gifting or batch-distributing value without handing out your API
key. You pay for the full batch (amountPerRedemption × maxRedemptions)
up front when you create one. Native API only — vouchers have no MSORG/ADEX equivalent.
Creates a voucher and debits its full cost from your wallet immediately.
| Field | Type | Description | |
|---|---|---|---|
type | string | required | wallet, airtime, data, electricity, or cable. |
provider | string | required* | Network / discoId / billerId — required for every type except wallet. |
planId | string | optional | Required for data and cable types. |
amount | number | optional | Required for wallet, airtime, and electricity types — ignored for data/cable, whose price comes from the plan. |
maxRedemptions | integer | required | 1–1000. How many times this code can be redeemed in total. |
expiresAt | string | required | ISO date, must be in the future. |
message | string | optional | Up to 200 characters, shown to whoever looks up the code. |
code | string | optional | 4–24 chars, letters/numbers/dashes. Auto-generated (DMV-XXXXXX) if omitted. |
pin | string | required | Your account's 4-digit transaction PIN. |
curl -X POST https://api.danmalama.com.ng/api/vouchers \
-H "X-API-Key: dmk_live_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"type": "data",
"provider": "MTN",
"planId": "173",
"maxRedemptions": 50,
"expiresAt": "2026-12-31T23:59:59.000Z",
"message": "Happy Sallah from Danmalama!",
"pin": "1234"
}'Response
{
"success": true,
"data": {
"voucher": {
"id": "68fa1b2c3d4e5f6a7b8c9d10",
"code": "DMV-7K3M9Q",
"type": "data",
"provider": "MTN",
"planId": "173",
"amountPerRedemption": 550,
"maxRedemptions": 50,
"totalCost": 27500,
"message": "Happy Sallah from Danmalama!",
"expiresAt": "2026-12-31T23:59:59.000Z",
"createdAt": "2026-09-30T10:30:00.000Z",
"redeemedCount": 0,
"remainingRedemptions": 50
},
"balance": 42500
}
}Lists every voucher you've created, newest first.
curl https://api.danmalama.com.ng/api/vouchers \ -H "X-API-Key: dmk_live_your_key_here"
Previews a voucher before redeeming it — never reveals who created it or who else has redeemed it.
curl https://api.danmalama.com.ng/api/vouchers/lookup/DMV-7K3M9Q \ -H "X-API-Key: dmk_live_your_key_here"
Response
{
"success": true,
"data": {
"code": "DMV-7K3M9Q",
"type": "data",
"providerLabel": "1GB - 30 Days",
"amountPerRedemption": 550,
"message": "Happy Sallah from Danmalama!",
"expiresAt": "2026-12-31T23:59:59.000Z",
"expired": false,
"fullyRedeemed": false,
"alreadyRedeemedByYou": false
}
}Redeems a voucher. Behavior depends on whether you pass reference (the recipient's phone number, meter number, or smartcard number):
- With
reference: delivers immediately and returns the outcome. - Without it: claims your one redemption slot now, delivered later via Deliver a claimed voucher. A
wallet-type voucher always delivers immediately —referencedoesn't apply to it.
| Field | Type | Description | |
|---|---|---|---|
code | string | required | |
reference | string | optional | Phone/meter/smartcard number to deliver to. Omit to claim now, deliver later. |
meterType | string | optional | prepaid or postpaid — required when delivering an electricity voucher. |
pin | string | required | Your account's 4-digit transaction PIN. |
curl -X POST https://api.danmalama.com.ng/api/vouchers/redeem \
-H "X-API-Key: dmk_live_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"code": "DMV-7K3M9Q",
"reference": "08031234567",
"pin": "1234"
}'Response
{ "success": true, "data": { "ok": true, "type": "data" } }Deferred claim (no reference) returns { "ok": true, "type": "data", "deferred": true }. A wallet voucher returns { "ok": true, "type": "wallet", "balance": 15730 } — your new balance, since it credits you directly.
| Code | Status | When |
|---|---|---|
NOT_FOUND | 404 | Code doesn't exist. |
VALIDATION_ERROR | 400 | Expired, or fully redeemed. |
CONFLICT | 409 | You've already redeemed this code — one redemption per account. |
PROVIDER_ERROR | 502 | Delivery failed. Your redemption slot is not consumed — retry with corrected details. |
Delivers a voucher you previously claimed without a reference. No PIN needed here — you already proved intent when you claimed it.
| Field | Type | Description | |
|---|---|---|---|
reference | string | required | Phone/meter/smartcard number to deliver to. |
meterType | string | optional | Required for an electricity voucher. |
curl -X POST https://api.danmalama.com.ng/api/vouchers/DMV-7K3M9Q/use \
-H "X-API-Key: dmk_live_your_key_here" \
-H "Content-Type: application/json" \
-d '{ "reference": "08031234567" }'Response
{ "success": true, "data": { "ok": true, "type": "data" } }Lists vouchers you've redeemed (as opposed to List my vouchers, which lists ones you created).
curl https://api.danmalama.com.ng/api/vouchers/redeemed \ -H "X-API-Key: dmk_live_your_key_here"
Response
{
"success": true,
"data": [
{
"code": "DMV-7K3M9Q",
"type": "data",
"providerLabel": "1GB - 30 Days",
"amountPerRedemption": 550,
"message": "Happy Sallah from Danmalama!",
"redeemedAt": "2026-09-30T11:02:00.000Z",
"delivered": true
}
]
}Wallet
Fund your account balance by bank transfer. Native API only.
Generates a dedicated bank account number for funding your wallet by transfer. Any transfer into it credits your wallet automatically (minus a small bank charge) — usually within a minute or two.
| Field | Type | Description | |
|---|---|---|---|
bvn | string | optional* | 11 digits. Only required the first time — if you already have a funding account, this returns it and ignores bvn. |
curl -X POST https://api.danmalama.com.ng/api/wallet/virtual-account \
-H "X-API-Key: dmk_live_your_key_here" \
-H "Content-Type: application/json" \
-d '{ "bvn": "12345678901" }'Response
{
"success": true,
"data": {
"accountNumber": "8123456789",
"bankName": "Wema Bank",
"accountName": "DANMALAMA - ADIAHN DANMALAMA"
}
}Ready to integrate?
Enable API access from Settings in the Danmalama app to get your key.
Danmalama API