llms.txt
Prompt-friendly specification for Large Language Models.
Are you an LLM crawler? Download the raw specs directly at https://app.kdadatasub.name.ng/llms.txt (concise) and https://app.kdadatasub.name.ng/llms-full.txt (complete).
llms.txt for KDA API
The KDA API is an enterprise-grade Virtual Top-Up (VTU) and bill payment infrastructure. This page is the styled human-readable version — machines should fetch the raw text spec above.
Base URLs
https://app.kdadatasub.name.nghttps://app.kdadatasub.name.ng/api/services
https://app.kdadatasub.name.ng/api/profileHTTPS only, JSON in/out. Every request carries Authorization: Bearer <API_KEY>. There is no separate sandbox host — the key prefix routes the environment.
Authentication
Test Key
Prefix: kds_test_ — sandbox simulator, simulated success, no debit. Use during development.
Live Key
Prefix: kds_live_ — production networks, real wallet debit.
Using your key
curl -X GET https://app.kdadatasub.name.ng/api/profile/balance \
-H "Authorization: Bearer kds_test_YOUR_API_KEY"const response = await fetch("https://app.kdadatasub.name.ng/api/profile/balance", {
headers: { "Authorization": "Bearer kds_test_YOUR_API_KEY" }
});
const data = await response.json();import requests
response = requests.get(
"https://app.kdadatasub.name.ng/api/profile/balance",
headers={"Authorization": "Bearer kds_test_YOUR_API_KEY"},
)
print(response.json())<?php
$ch = curl_init("https://app.kdadatasub.name.ng/api/profile/balance");
curl_setopt_array($ch, [
CURLOPT_HTTPHEADER => ["Authorization: Bearer kds_test_YOUR_API_KEY"],
CURLOPT_RETURNTRANSFER => true,
]);
$response = curl_exec($ch);
curl_close($ch);Scopes
One scope per key — create two keys if you need both:
- Services scope →
/api/services/*(plans, purchases, verify, test). - Profile scope →
/api/profile/*(balance, transactions, withdraw, test). - Wrong scope →
403 FORBIDDENwithkeyStatus: "FORBIDDEN". GET /api/services/testandGET /api/profile/testaccept either scope.
Key status
| keyStatus | Meaning |
|---|---|
INVALID | Key does not exist, is malformed, or failed verification. |
NOT_FOUND | Key passed verification but no matching record exists. |
DISABLED | Key exists but is disabled. Re-enable it in the Developer Console. |
FORBIDDEN | Key is valid but lacks the required scope for this endpoint. |
Missing/invalid/disabled key → 401 UNAUTHORIZED with keyStatus. No header at all → 401 without it.
Rate limits
| Key type | Limit | Window |
|---|---|---|
| LIVE | 10,000 requests | 1 hour |
| SANDBOX | 500 requests | 1 hour |
Exhausted → 429 RATE_LIMITED with retryAfterMs plus Retry-After / X-Retry-After headers. Wait before retrying.
- Never expose your live key in client-side code, mobile apps, or public repos.
- Store keys in environment variables or a secrets manager and rotate periodically.
Quickstart
Verify your key
curl -X GET https://app.kdadatasub.name.ng/api/services/test \
-H "Authorization: Bearer kds_test_YOUR_API_KEY"Expect "environment": "TEST" and check key.scope matches the endpoints you plan to call.
Fetch plans
curl -X GET https://app.kdadatasub.name.ng/api/services/data/plans \
-H "Authorization: Bearer kds_test_YOUR_API_KEY"Pick a kds-* id — the -v1- / -v2- / -v3- segment selects the provider route. ?id= fetches one plan; ?type= filters data/tv categories.
Purchase
curl -X POST https://app.kdadatasub.name.ng/api/services/data \
-H "Authorization: Bearer kds_test_YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"planId": "kds-data-mtn-sme-v1-003",
"number": "08012345678",
"idempotencyKey": "550e8400-e29b-41d4-a716-446655440000",
"isPorted": false
}'New UUID per intent; reuse the same key only for retries. Test keys return simulated success.
Go live
Create a kds_live_* key, fund your wallet, re-call /test (expect "LIVE"), then run one low-value live purchase and reconcile balance, transaction history, and the PURCHASE_SUCCESS webhook. See Going live.
Service endpoints
Data
GET /api/services/data/plans + POST /api/services/data
Airtime
GET /api/services/airtime/plans + POST /api/services/airtime
Tv / Cable
GET /api/services/tv/plans + POST /api/services/tv
Electricity
GET /api/services/electricity/plans + POST /api/services/electricity
Exam PINs
GET /api/services/exam/plans + POST /api/services/exam
Data Cards
GET /api/services/data-card/plans + POST /api/services/data-card
Recharge Cards
GET /api/services/recharge-card/plans + POST /api/services/recharge-card
Verify TV
POST /api/services/verify/tv
Verify Meter
POST /api/services/verify/meter (alias /verify/bill)
Data/tv without filters group as [{ type, plans[] }] with a count total; filtered/single returns flat rows with type per row.
Purchase payloads
Data — POST /api/services/data
Prop
Type
Airtime — POST /api/services/airtime
Prop
Type
TV — POST /api/services/tv (verify first)
Prop
Type
Verify: POST /api/services/verify/tv with { smartCardNumber, providerId } → { valid, name }.
Electricity — POST /api/services/electricity (verify first)
Prop
Type
Purchase meterType is UPPERCASE (PREPAID / POSTPAID), but verify meterType is lowercase (prepaid / postpaid). Verify via POST /api/services/verify/meter with { discoSlug, meterNumber, meterType }. Response carries electricityToken (prepaid) plus conditional electricityUnits.
Exam — POST /api/services/exam
Prop
Type
Response pin may join PIN and serial with <=>.
Data / Recharge cards — POST /api/services/data-card, POST /api/services/recharge-card
Prop
Type
pin / serial return comma-separated lists matching quantity.
Account endpoints
Check Balance
GET /api/profile/balance
Transaction History
GET /api/profile/transactions
Withdraw Funds
POST /api/profile/withdraw
Verify API Key
GET /api/profile/test
Prop
Type
Withdraw body: { amount (>= 1000), bankCode, accountNumber (10-digit), pin (4-digit), narration? }. Requires Allow Withdrawals on the key, else 412. Accepted requests return PENDING and settle via WITHDRAWAL_SUCCESS / WITHDRAWAL_FAILED.
Pricing and routes
Data / TV / Exam / Cards
salePrice — final ₦ debit.
Airtime / Recharge
salePercentage — % off face value (3 → ₦100 costs ₦97).
Electricity
saleFee — flat ₦ fee added per transaction.
-v1- primary, -v2- secondary, -v3- aggregator (rolling out for Airtime, TV, and Electricity). v2 is usually cheaper for Data/Airtime; v1 for Electricity/NECO. Re-fetch plans — never cache prices long-term. See Plan versions.
Reliability
Safe retries: idempotencyKey is required on every purchase. Duplicate → 409 CONFLICT with the original transactionId. No double charge.
Pending: most purchases settle in under 3 seconds, but vendor delays can leave PENDING. Never retry with a new key — wait for the webhook or query transaction history.
Webhooks: subscribe per category (Purchase → PURCHASE_SUCCESS / PURCHASE_FAILED; Withdrawal → WITHDRAWAL_SUCCESS / WITHDRAWAL_FAILED). Verify the X-Webhook-Signature HMAC-SHA256 against the raw body before parsing, reject skew over 5 minutes, respond 200 in under 10 seconds. No automatic retries. See Webhooks.
Errors: 400 VALIDATION_ERROR (+ details), 401, 402 INSUFFICIENT_BALANCE, 403, 404, 405, 409 CONFLICT, 412 PRECONDITION_FAILED, 422 TRANSACTION_FAILED (refunded), 429 RATE_LIMITED, 500. See Error handling.
Guide index
Authentication
Scopes, keyStatus, rotation.
Error Handling
Codes and recovery snippets.
Idempotency
Duplicate protection semantics.
Webhooks
HMAC verification and payloads.
Going Live
Sandbox-to-production checklist.
Plan Versions
v1 vs v2 vs v3 per service.
Network Prefixes
Prefixes and ported numbers.
Supported Banks
Bank codes for withdrawals.
The canonical machine specs live at https://app.kdadatasub.name.ng/llms.txt and https://app.kdadatasub.name.ng/llms-full.txt. They are validated against app/api/services/[...path]/schemas.ts by pnpm llms:check — update them whenever endpoints or fields change.