Overview
One API. Four Nigerian networks.
The SaukiMart API is a small, focused REST interface for automating data purchases across MTN, Airtel, Glo, and 9Mobile. The request format is identical across all four networks - no per-network adapters, no separate contracts.
Fetch the live plan catalog to get current prices and codes.
Submit a purchase with a phone number, a plan code, and an idempotency key.
On success, your wallet is debited and the data is delivered immediately.
Optionally configure a webhook to receive delivery events in real time.
Base URLhttps://saukimart.comFormatJSON onlyStandardRESTRate limit90 requests per minute (default)PaymentsPre-funded wallet - live keys debit on deliveryQuickstart
Ship your first data purchase in minutes.
Follow these four steps to go from zero to a working integration.
Step 1 - Create or sign in to your SaukiMart account
If you already use the SaukiMart app, sign in with the same phone number and PIN. If not, registration takes under a minute. Once signed in, navigate to the developer section of your dashboard to activate API access.
Step 2 - Get your API key
After activating developer access, go to the Keys tab and copy your active key. Live keys start with sm_live_ and test keys start with sm_test_. Store your key server-side - never in client-side code or public repositories.
Step 3 - Fetch the plan catalog
Call GET /api/v1/data-plans to retrieve the current list of available plans across all four networks, with your developer price applied to each one.
Step 4 - Make a purchase
Send a POST to /api/v1/purchase-data with the recipient phone number, a plan code from Step 3, and a unique idempotency key.
# 1. Fetch available data plans
curl -X GET "https://saukimart.com/api/v1/data-plans" \
-H "x-api-key: sm_live_XXXX_...your_key_here"
# 2. Purchase data
curl -X POST "https://saukimart.com/api/v1/purchase-data" \
-H "Content-Type: application/json" \
-H "x-api-key: sm_live_XXXX_...your_key_here" \
-d '{
"phoneNumber": "08012345678",
"planCode": 23,
"network": 1,
"idempotencyKey": "your-unique-request-id"
}'Authentication
Send your API key with every request.
All protected endpoints require your API key in the x-api-key request header. Live keys start with sm_live_ and test keys start with sm_test_. Store your key server-side - never in client-side code or public repositories.
Key Formats
Live key: sm_live_<prefix>_<secret> debits your wallet balance on success.
Test key: sm_test_<prefix>_<secret> returns sandbox responses only (no wallet charge or actual data delivery).
Available Scopes
read:plans - Allows fetching the data plans catalog.write:purchases - Allows submitting data purchases.read:transactions - Allows querying past transaction logs.
You can restrict each key to specific scopes, IP addresses, and webhook URLs from the developer dashboard. Keep your API key private. If a key is ever compromised, rotate it immediately from the dashboard.
Plan Catalog
Fetch current plans before every purchase flow.
Plan prices and availability change frequently as networks adjust their offers. Always pull the live catalog dynamically rather than hardcoding plan codes or prices in your application. Cache the response briefly in your own system if needed, but treat it as short-lived data.
read:plans{
"success": true,
"discountPercent": 8,
"docs": {
"purchaseEndpoint": "/api/v1/purchase-data",
"transactionsEndpoint": "/api/v1/transactions",
"openApiEndpoint": "/api/v1/openapi",
"authHeader": "x-api-key: sm_live_... or sm_test_..."
},
"plans": [
{
"code": 23,
"network": "MTN",
"dataSize": "1GB",
"validity": "30 days",
"appPrice": 350,
"developerPrice": 322
}
]
}Field Guide
| Field | Description |
|---|---|
| successboolean | Indicates if the request was successfully processed. |
| discountPercentnumber | Your account-level discount percentage applied to retail prices. |
| plans[].codenumber | The unique plan code to pass to POST /api/v1/purchase-data (e.g. 23). |
| plans[].networkstring | Mobile network operator (MTN, AIRTEL, GLO, or 9MOBILE). |
| plans[].dataSizestring | Volume of data allocated to the plan (e.g. 1GB, 750MB). |
| plans[].validitystring | Duration of the plan validity (e.g. 30 days, 7 days). |
| plans[].appPricenumber | Standard retail price shown on the public user interface. |
| plans[].developerPricenumber | Discounted agent price debited from your developer wallet. |
Integration Tips
- Treat plan catalog details as dynamic. Avoid hardcoding prices or codes.
- Cache catalog responses for 5-15 minutes to reduce API latency.
- Always verify the plan code against the live catalog before initiating a purchase.
Data Plans
Curated live catalog preview.
These rows are pulled from the public plan catalog so developers can inspect current availability before signing in. Account-specific developer pricing is confirmed inside the developer dashboard.
AIRTEL
1 plansGLO
1 plans9Mobile
1 plansPlan Efficiency
Helping your users choose the best plan.
The catalog can return daily, weekly, monthly, and promotional bundles across four networks. Keep your interface flexible - new plan types and promotional bundles are added periodically.
Price per GB
Divide developerPrice by the parsed data size to calculate cost per gigabyte and rank plans from cheapest to most expensive. This is the most useful metric for high-volume or budget-conscious users.
Validity-aware selection
For users who make regular purchases, prefer longer-validity plans that stretch their budget. For urgent or one-off needs, surface smaller daily or weekly bundles at the top of your UI.
Purchase Data
Submit a data purchase for any supported network.
Submits a purchase transaction, locks the required balance, dispatches to the delivery carrier, and debits your wallet on successful delivery. On a sandbox request, the response is identical in structure but no wallet debit or real carrier delivery occurs.
write:purchases{
"phoneNumber": "08012345678",
"planCode": 23,
"network": 1,
"idempotencyKey": "unique-request-id-123"
}{
"success": true,
"status": "success",
"transactionId": "uuid",
"idempotencyKey": "unique-request-id-123",
"sandbox": false,
"deducted": true,
"data": {
"planCode": 23,
"network": "MTN",
"dataSize": "1GB",
"validity": "30 days",
"phoneNumber": "08012345678",
"amount": 322,
"deliveryReference": "AMG-123456",
"sandbox": false
},
"newBalance": 4328
}Field Guide
| Field | Description |
|---|---|
| phoneNumberstringRequired | The 11-digit recipient phone number in Nigeria (e.g. 08012345678). |
| planCodenumberRequired | The unique plan code retrieved from the data plans catalog (e.g. 23). |
| idempotencyKeystringRequired | A unique key (preferably UUIDv4) generated for this purchase to prevent double-charging on network retries. |
| successboolean | Indicates if the request was successfully processed. |
| statusstring | Execution status: success, failed, or sandbox. |
| transactionIdstring | Unique SaukiMart ledger ID for the transaction. |
| sandboxboolean | true if processed in test mode (sm_test keys or test number). |
| deductedboolean | Indicates if your developer wallet balance was charged. |
| data.deliveryReferencestring | Carrier reference code returned by the telco operator. |
| newBalancenumber | Your remaining developer wallet balance after processing. |
Integration Tips
- Idempotency is mandatory. Always send a unique idempotencyKey. If a request times out, retry with the exact same key.
- For sandbox testing, use the phone number 08012345678 or a test key (sm_test_...). The API will simulate a successful purchase with sandbox: true and deducted: false.
Transactions
Query your full API request history.
Use this endpoint to audit past requests, confirm carrier delivery statuses, retrieve receipts, and reconcile your ledger. Results are scoped to the authenticated API key.
read:transactions{
"success": true,
"version": "2026-04-17",
"transactions": [
{
"id": "uuid",
"transactionId": "uuid",
"status": "success",
"endpoint": "/api/v1/purchase-data",
"phoneNumber": "08012345678",
"planCode": 23,
"idempotencyKey": "unique-request-id-123",
"receipt": {
"deliveryReference": "AMG-123456"
}
}
]
}Field Guide
| Field | Description |
|---|---|
| successboolean | Indicates if the request was successfully processed. |
| versionstring | The active developer API version string. |
| transactions[]array | List of transaction objects initiated under this key. |
| transactions[].statusstring | Transaction status (success, failed, pending, or sandbox). |
| transactions[].endpointstring | The route path processed (e.g. /api/v1/purchase-data). |
| transactions[].phoneNumberstring | The recipient mobile phone number. |
| transactions[].planCodenumber | The purchased data plan code (e.g. 23). |
| transactions[].idempotencyKeystring | The associated idempotency key generated by your system. |
| transactions[].receiptobject | Nested receipt payload containing telecommunication carrier details. |
Integration Tips
- Reconcile transactions. Run automated checks in your backend against this endpoint to verify all purchases on your system match SaukiMart ledger records.
- Status pending/processing means carrier hand-off is incomplete. Webhooks will dispatch once resolved.
Test Mode
Test your integration safely before going live.
There are two ways to run sandbox requests: use a test key (sm_test_...) or use the sandbox phone number 08012345678 with a live key. Both methods return a full success-shaped response so your code can parse receipts, handle balances, and validate your integration without touching real funds.
{
"success": true,
"status": "sandbox",
"transactionId": "uuid",
"sandbox": true,
"deducted": false,
"data": {
"planCode": 23,
"network": "MTN",
"phoneNumber": "08012345678",
"deliveryReference": "SANDBOX-NO-DELIVERY"
},
"newBalance": 4650
}deducted: false confirms no wallet charge occurred
delivered: false confirms no real delivery was made
status: sandbox lets you branch sandbox vs live logic
The response structure matches live receipts for parser testing
OpenAPI Specification
Download the full API specification.
Returns the complete OpenAPI 3.1 specification for the SaukiMart developer API. Import this JSON file into Postman, Insomnia, Swagger UI, or any client SDK generator to get a fully documented client automatically. This is a public endpoint - no authentication key required.
public{
"openapi": "3.1.0",
"info": {
"title": "SaukiMart Developer API",
"version": "2026-04-17"
}
}Field Guide
| Field | Description |
|---|---|
| openapistring | The OpenAPI document specification version (e.g. 3.1.0). |
| infoobject | Metadata description about the API catalog. |
| info.titlestring | The title of the developer catalog. |
| info.versionstring | The documentation release version. |
Integration Tips
- Use tools like openapi-generator-cli to generate client libraries for Go, Python, Java, PHP, Node.js and more.
- Import directly into Postman or Insomnia to quickly set up a sandbox collection.
Idempotency
Send idempotencyKey on every purchase request.
Networks are unreliable. A request may time out after being received but before a response is returned, leaving you uncertain whether the purchase went through. Generate a unique key per purchase attempt and reuse the same key if you retry the exact same request.
{
"phoneNumber": "08012345678",
"planCode": 23,
"network": 1,
"idempotencyKey": "unique-request-id-123"
}Errors
All errors return JSON with an actionable error code.
Client-side errors use 4xx status codes, while server-side errors use 5xx. Every error response includes a machine-readable code, a human-readable description, and optionally a details object.
{
"success": false,
"version": "2026-04-17",
"requestId": "req_82a3bc9f",
"error": {
"code": "insufficient_balance",
"message": "Wallet balance is too low for this purchase.",
"details": {
"required": 322,
"available": 150
}
}
}Error Reference Table
| HTTP Status | Error Code | Explanation & Recovery Strategy |
|---|---|---|
| 400 | invalid_phone_number | The phoneNumber is not exactly 11 digits or has an invalid prefix. Fix formatting (e.g. 08012345678) before retrying. |
| 400 | invalid_plan_code | The planCode format is invalid (must be networkId-planId). Check format before retrying. |
| 400 | insufficient_balance | Your developer wallet balance is below the plan price. Fund your wallet to resume purchases. |
| 400 | unsupported_field | Request payload contains callbackUrl, which is deprecated. Set webhooks via dashboard instead. |
| 400 | delivery_failed | The telecom network operator rejected the data transfer. Confirm the recipient number is active. |
| 401 | invalid_api_key | The x-api-key header is missing, malformed, inactive, or has been revoked. Re-verify key credentials. |
| 403 | phone_verification_required | Your SaukiMart account must have verified phone credentials. Complete verification via App. |
| 404 | plan_not_found | No active plan matches the given code. Fetch GET /api/v1/data-plans to refresh your cached catalog. |
| 408 | timeout | Delivery carrier timed out. Inspect GET /api/v1/transactions or retry using the exact same idempotencyKey. |
| 409 | balance_lock_failed | Simultaneous transaction contention. Safe to retry the request with the same idempotencyKey. |
| 429 | rate_limit_exceeded | Your key exceeded its per-minute rate limit. Apply exponential backoff and retry later. |
| 429 | monthly_quota_exceeded | The API key has exhausted its monthly request quota. Upgrade your key quota limit in the dashboard. |
| 500 | server_error | An internal database or ledger error occurred. Retry with backoff or contact administrator. |
Retry Behaviour
Retry safely without creating duplicates or missed events.
For API request retries, always reuse the same idempotencyKey on a retry of the same purchase. Apply exponential backoff and query /api/v1/transactions before creating new order records. For webhook delivery, SaukiMart will retry failed requests with exponential backoff.
Register Webhook
Receive real-time delivery events for your purchases.
Instead of polling /api/v1/transactions for updates, configure a webhook URL. SaukiMart will make an HTTP POST request to your endpoint every time a transaction status updates.
1. Register URL
Sign in, go to the Webhooks tab, select your API key, and enter your public HTTPS callback URL.
2. Define Secret
Optionally define a custom Signing Secret. This secret token will be sent with every webhook request for verification.
3. Receive Events
Your callback URL is active immediately. SaukiMart will start posting JSON payloads to it when events trigger.
{
"event": "developer.purchase.completed",
"transactionId": "uuid",
"status": "success",
"planCode": 23,
"phoneNumber": "08012345678",
"amount": 322
}Manage Webhooks
Update URLs and secrets without downtime.
Webhook configuration is scoped per API key. You can update the callback URL, set or rotate the signing secret, or assign webhooks to a different environment key at any time from the dashboard. Changes take effect instantly.
HTTPS Only
Localhost or unencrypted HTTP callback URLs are rejected. Secure HTTPS connections are mandatory for receiving webhooks.
Failures & Retries
If your server returns anything other than 2xx (or times out), SaukiMart registers a failure. Verify webhook deliveries inside the Webhooks tab.
Event Reference
Webhook event types and schemas.
| Event Name | Trigger Condition |
|---|---|
developer.purchase.completed | Fired when a data purchase is successfully confirmed and delivered to the recipient. |
developer.purchase.failed | Fired when a purchase attempt fails due to provider errors or network rejection. |
developer.purchase.sandbox | Fired when a purchase is successfully processed under a sandbox/test key. |
Verify Webhooks
Secure your endpoint by validating incoming requests.
When a signing secret is configured for your webhook, SaukiMart sends it as a plain-text token in the X-SaukiMart-Webhook-Secret header. Confirm this header matches your secret exactly before trusting the request.
HTTP Header Check
| Header Name | Description |
|---|---|
X-SaukiMart-Webhook-Secret | Contains the plain-text secret token you configured in the developer dashboard settings. |
X-SaukiMart-Event | The event type trigger (e.g. |
X-SaukiMart-Version | The API catalog version matching the payload format. |
app.post('/webhook', (req, res) => {
const secretHeader = req.headers['x-saukimart-webhook-secret'];
if (secretHeader !== process.env.SAUKIMART_WEBHOOK_SECRET) {
return res.status(401).json({ error: 'Unauthorized - invalid webhook secret token' });
}
const { event, transactionId, status } = req.body;
// Process payload...
res.sendStatus(200);
});API Terminal
Run live or sandbox requests directly in your browser.
The API Terminal lets you paste a key, select an endpoint, edit the request body, and inspect the response - all without leaving the docs page. It is useful for quick validation only; do not store your live API key in the browser long term.
08012345678 as the recipient phone number. You will receive a complete success response with zero wallet debit and zero data delivery.{
"success": true,
"docs": {
"purchaseEndpoint": "/api/v1/purchase-data",
"transactionsEndpoint": "/api/v1/transactions"
},
"plans": [
{
"code": 23,
"network": "MTN",
"dataSize": "1GB",
"developerPrice": 322
}
]
}