Introduction

Build your own data reselling business with the Rema Data API. Our secure, high-performance REST API allows you to programmatically purchase data bundles for MTN, Telecel, and AirtelTigo. Fulfillment is powered by our infrastructure for reliable delivery across all supported networks.

99.9% System Uptime

Engineered reliability for mission-critical businesses.

24/7 Developer Support

Direct access to our engineering team, day or night.

Free API Access

Get your API key instantly. No subscription fees or hidden charges.

Universal Coverage

Comprehensive support for all major networks across Ghana.

Developer Experience

RESTful endpoints, clean docs, and easy code examples.

Base URL: https://remadata.com/api

Authentication

Authenticate your requests by including your secret API key in the X-API-KEY header.

Keep your key secret! Never share your API key in client-side code (browsers, mobile apps). Only use it from your backend server.

Auth Header
curl -X GET https://remadata.com/api/wallet-balance \
  -H "X-API-KEY: rd_live_your_secret_key" \
  -H "Content-Type: application/json"

Sandbox

Build and test your whole integration before a single cedi moves. A test key never charges your wallet, never reaches a supplier and never creates a real order — but the responses have exactly the same shape as live ones.

Test keys start with rd_test_ and are free. Live keys start with rd_live_ and carry the one-time unlock fee.

Every sandbox response carries "sandbox": true, so a test key can never be mistaken for a live one in your logs. Sandbox references are prefixed TEST_.

Create a free test key
curl -X POST https://remadata.com/api/developer/keys \
  -H "Authorization: Bearer YOUR_DASHBOARD_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "name": "Local dev", "mode": "test" }'
Use it like any other key
curl -H "X-API-KEY: rd_test_..." https://remadata.com/api/wallet-balance
Response
{
  "status": "success",
  "data": {
    "sandbox": true,
    "notice": "Sandbox response — no money moved and no order was placed.",
    "balance": "1000.00",
    "currency": "GHS"
  }
}

Forcing an outcome

The recipient number decides what happens, so you can exercise every branch of your code on demand — including the ones that are hard to reproduce in production.

…0000Required
failedUpstream rejects it. You are not charged.
…1111Required
waitingHeld for one-time number approval; settles days later.
…9999Required
refundedDelivered, then reversed upstream and refunded.
anything elseRequired
completedThe happy path. No special number needed.
Force a failure
curl -X POST https://remadata.com/api/v1/airtime \
  -H "X-API-KEY: rd_test_..." \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: test-001" \
  -d '{ "network": "mtn", "amount": 5, "recipient": "0240000000" }'
Response
{
  "status": "error",
  "code": "provider_failed",
  "sandbox": true,
  "message": "Sandbox: simulated upstream failure. Your wallet was not charged.",
  "reference": "TEST_AIRTIME_9C2A1B4D",
  "refunded": true
}

Testing your webhook

Sandbox purchases fire real webhook deliveries to your registered endpoint, signed with your real secret. This is the point of the sandbox for most integrations: prove your signature check and your state machine work before you go live.

Register your endpoint with a test key, then drive each outcome with the numbers above to see order.completed, order.failed, order.refunded and order.waiting arrive in turn.

Going live

Swap rd_test_ for rd_live_. Nothing else changes — same URLs, same request bodies, same response shapes. Check that your code never branches on the sandbox flag before you do.

Get Available Bundles

GET/api/bundles

Fetch a complete list of data bundles available for purchase. This endpoint returns your specific API Pricing, which may be lower than public prices.

Query Parameters

networkOptional
stringFilter by 'mtn', 'telecel', or 'airteltigo'.
Request
# Get all bundles
curl -X GET https://remadata.com/api/bundles \
  -H "X-API-KEY: your_key"

# Filter by network
curl -X GET https://remadata.com/api/bundles?network=mtn \
  -H "X-API-KEY: your_key"
Response
{
  "status": "success",
  "data": [
    {
      "network": "mtn",
      "name": "1GB",
      "volume": "1.00GB",
      "volumeInMB": 1024,
      "price": 5.50, // Your special API Price
      "description": "Indomie bundle..."
    },
    {
      "network": "telecel",
      "name": "2GB",
      "price": 10.00,
      ...
    }
  ]
}

Check Real-Time Price

POST/api/get-cost-price

Verify the exact cost of a specific data volume before purchasing. This is useful for dynamic pricing displays in your own app.

Body Parameters

networkTypeRequired
stringProvider: 'mtn', 'telecel', 'airteltigo'
volumeInMBRequired
numberSize in numeric MB (e.g. 1024)
Request
curl -X POST https://remadata.com/api/get-cost-price \
  -H "X-API-KEY: your_key" \
  -H "Content-Type: application/json" \
  -d '{"networkType":"mtn","volumeInMB":1024}'
Request Body
{
  "networkType": "mtn",
  "volumeInMB": 1024
}
Success Response
{
  "status": "success",
  "volume": "1024MB",
  "network": "mtn",
  "api_price": "5.50",
  "currency": "GHS"
}
Error Response (e.g. missing fields)
{
  "status": "error",
  "message": "Missing required fields: volumeInMB, networkType"
}

Purchase Data

POST/api/buy-data

Initiate a data purchase transaction. The amount will be deducted from your wallet instantly. successful responses return status: "pending" until delivery. If the provider cannot fulfill the order, your wallet is automatically refunded.

Body Parameters (Rema Data format)

refOptional
stringYour unique reference ID (e.g. ORDER_123)
phoneRequired
stringRecipient number (10 digits, e.g. 0551234567)
volumeInMBRequired
numberBundle size in MB (e.g. 1024 for 1GB)
networkTypeRequired
string'mtn', 'telecel', 'airteltigo'

Alternative format: phoneNumber, network, capacity (GB, e.g. 1 for 1GB). Nested payloads in data / body are also accepted.

Node.js Example
const axios = require('axios');

await axios.post('https://remadata.com/api/buy-data', {
  ref: 'MY_ORDER_001',
  phone: '0551234567',
  volumeInMB: 1024,
  networkType: 'mtn'
}, {
  headers: { 'X-API-KEY': 'your_key' }
});
Success Response
{
  "status": "success",
  "message": "Order placed successfully",
  "data": {
    "reference": "ORD_...",
    "client_reference": "MY_ORDER_001",
    "status": "pending",
    "amount": 5.50,
    "balance": "144.50",
    "provider": "null"
  }
}
Error / Refund Response
{
  "status": "error",
  "message": "Order failed: ... Your wallet has been refunded.",
  "data": { "reference": "ORD_...", "refunded": true }
}

Wallet Balance

GET/api/wallet-balance

Check your current account balance.

Request
curl -H "X-API-KEY: key" https://remadata.com/api/wallet-balance
Response
{
  "status": "success",
  "message": "Wallet balance retrieved successfully",
  "data": {
    "balance": "150.00",
    "currency": "GHS",
    "wallet_id": "...",
    "user_id": "...",
    "last_transaction_at": "2025-02-27T..."
  }
}

Order History

GET/api/orders

1. Check Order Status

Quickly check the current status of an order using its reference number.

refRequired
stringThe order reference (e.g. ORD_...)
Check Status
curl -X GET https://remadata.com/api/order-status/ORD_123456 \
  -H "X-API-KEY: your_api_key"

2. Get All Orders

Retrieve a paginated list of all your orders. By default, it returns the latest 15 orders.

pageOptional
numberPage number (default: 1).
per_pageOptional
numberItems per page (default: 15). Alias: limit.
Get All Orders
curl -H "X-API-KEY: key" https://remadata.com/api/orders

3. Get Order by ID

Fetch details of a single specific order using its unique ID.

idRequired
stringThe unique MongoDB ID of the order (24-char hex).
Get Order by ID
curl -H "X-API-KEY: key" "https://remadata.com/api/orders?id=65b123..."

4. Get by Reference

Find an order using your own custom reference number provided during purchase.

ref_numberRequired
stringPartial or exact match. Alias: ref.
Get by Reference
curl -H "X-API-KEY: key" "https://remadata.com/api/orders?ref_number=MY_REF_001"

5. Advanced Filtering

Combine multiple filters to find specific subsets of orders.

statusOptional
string'completed', 'failed', 'pending', 'refunded'
networkOptional
string'mtn', 'telecel', 'airteltigo'
phoneOptional
stringFilter by recipient phone (partial match).
start_dateOptional
stringYYYY-MM-DD. Alias: startDate.
end_dateOptional
stringYYYY-MM-DD. Alias: endDate.
min_amountOptional
numberMinimum amount paid.
max_amountOptional
numberMaximum amount paid.
Filter by Status & Network
curl -H "X-API-KEY: key" "https://remadata.com/api/orders?status=completed&network=mtn"
Filter by Date Range
curl -H "X-API-KEY: key" "https://remadata.com/api/orders?start_date=2025-01-01&end_date=2025-01-31"

Order Status

GET/api/order-status/{reference}

Look up a single order by the reference returned when you bought it. Cheapest way to poll one order.

Request
curl -H "X-API-KEY: key" https://remadata.com/api/order-status/ORD_123456
Response
{
  "status": "success",
  "data": {
    "reference": "ORD_123456",
    "status": "completed"
  }
}

Full Catalogue

GET/api/v1/catalogue

Every bundle your key can buy, across both products. Buy any of them by posting its id to /api/buy-data.

networkOptional
string'mtn', 'mtn_xpress', 'telecel', 'airteltigo'
typeOptional
string'cheap' or 'instant'

cheap is our discounted route — lower price per GB, delivery can take longer, and a new recipient number may need one-time approval.instant is the operator's own package at face value, delivered immediately.

Request
curl -H "X-API-KEY: key" "https://remadata.com/api/v1/catalogue?network=mtn&type=instant"
Response
{
  "status": "success",
  "message": "Catalogue retrieved",
  "data": {
    "count": 2,
    "bundles": [
      {
        "id": "66f1a2...",
        "name": "1GB",
        "network": "mtn",
        "type": "cheap",
        "volume_mb": 1024,
        "price": 4.5,
        "currency": "GHS"
      },
      {
        "id": "66f1b7...",
        "name": "8683.07 MB",
        "network": "mtn",
        "type": "instant",
        "volume_mb": 8683.07,
        "price": 3,
        "currency": "GHS"
      }
    ]
  }
}

Airtime

POST/api/v1/airtime

Send airtime to any Ghana number. Charged to your wallet.

networkRequired
string'mtn', 'telecel' or 'airteltigo'
amountRequired
numberFace value in GHS, max two decimals.
recipientRequired
string0241234567 or 233241234567.

Always send an Idempotency-Key. If a request times out, retry with the same key and you get the original purchase back instead of topping the number up twice.

Your wallet is debited before the request goes out and refunded in the same call if it fails, so a failure never leaves you short.

Request
curl -X POST https://remadata.com/api/v1/airtime \
  -H "X-API-KEY: key" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: order-10046" \
  -d '{
    "network": "telecel",
    "amount": 10,
    "recipient": "0201234567"
  }'
Response
{
  "status": "success",
  "message": "Airtime sent",
  "data": {
    "reference": "ORD_993412",
    "status": "completed",
    "network": "telecel",
    "recipient": "0201234567",
    "amount": 10,
    "fee": 0,
    "total": 10,
    "currency": "GHS"
  }
}
Check limits first
curl -H "X-API-KEY: key" "https://remadata.com/api/v1/airtime?amount=10"

Delivery Status

GET/api/v1/delivery-status

How each network is delivering right now. Use it to set expectations before a customer buys, or to show a live status strip on your own site.

Request
curl -H "X-API-KEY: key" https://remadata.com/api/v1/delivery-status
Response
{
  "status": "success",
  "data": {
    "available": true,
    "summary": "12 orders in the delivery queue. Typical wait is about 14 min.",
    "networks": [
      {
        "network": "mtn",
        "state": "ok",
        "note": "Delivery is running normally.",
        "typical_wait_seconds": 780,
        "slow_wait_seconds": 2340,
        "pending_orders": 12,
        "last_delivered_at": "2026-10-23T18:34:15.752Z"
      }
    ]
  }
}

AFA Registration

POST/api/v1/afa

Register an applicant for AFA. Charged to your wallet.

fullNameRequired
stringApplicant's full name.
phoneNumberRequired
stringGhana number.
idNumberRequired
stringe.g. GHA-123456789-0
dateOfBirthRequired
stringYYYY-MM-DD
townRequired
stringTown or city.
occupationRequired
stringOccupation.
regionRequired
stringRegion.
cropProduceRequired
stringCrop produce.

Registration is not instant — it settles upstream over the following days. Poll with ?reference= or subscribe to webhooks.

Request
curl -X POST https://remadata.com/api/v1/afa \
  -H "X-API-KEY: key" \
  -H "Content-Type: application/json" \
  -d '{
    "fullName": "John Doe",
    "phoneNumber": "0241234567",
    "idNumber": "GHA-123456789-0",
    "dateOfBirth": "1990-01-15",
    "town": "Accra",
    "occupation": "Teacher",
    "region": "Greater Accra",
    "cropProduce": "Maize"
  }'
Response
{
  "status": "success",
  "message": "AFA registration submitted",
  "data": {
    "reference": "AFA_558210",
    "status": "pending",
    "amount": 9.5,
    "currency": "GHS"
  }
}
Check one registration
curl -H "X-API-KEY: key" "https://remadata.com/api/v1/afa?reference=AFA_558210"

Webhooks

POST/api/v1/webhooks

Register one HTTPS endpoint and we POST to it whenever one of your orders reaches a final state — no polling.

urlRequired
stringYour https:// endpoint.
eventsOptional
string[]Defaults to all. order.completed, order.failed, order.refunded, order.waiting

The signing secret is returned once, on registration. Store it immediately — it cannot be retrieved later.

An endpoint that rejects 20 deliveries in a row is deactivated. Re-register it once you have fixed it.

Register
curl -X POST https://remadata.com/api/v1/webhooks \
  -H "X-API-KEY: key" \
  -H "Content-Type: application/json" \
  -d '{ "url": "https://your-site.com/hooks/remadata" }'
Response (secret shown once)
{
  "status": "success",
  "message": "Webhook registered",
  "data": {
    "id": "6710ab...",
    "url": "https://your-site.com/hooks/remadata",
    "events": ["order.completed", "order.failed", "order.refunded", "order.waiting"],
    "active": true,
    "secret": "whsec_9f2c..."
  }
}
What we POST to you
{
  "event": "order.completed",
  "sent_at": "2026-10-23T18:40:02.114Z",
  "data": {
    "reference": "ORD_123456",
    "status": "completed",
    "network": "mtn",
    "recipient": "0241234567",
    "amount": 4.5,
    "bundle": "1GB",
    "created_at": "2026-10-23T18:31:10.004Z",
    "completed_at": "2026-10-23T18:40:01.900Z"
  }
}

Verifying a delivery

Every call carries X-RemaData-Signature: an HMAC-SHA256 of the raw request body, keyed with your secret. Compare it before trusting the payload.

Node.js
import crypto from "crypto";

// Use the raw body string, not the parsed object.
function isFromRemaData(rawBody, signatureHeader, secret) {
  const expected = crypto
    .createHmac("sha256", secret)
    .update(rawBody)
    .digest("hex");

  const a = Buffer.from(expected);
  const b = Buffer.from(signatureHeader || "");
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}
List / remove
curl -H "X-API-KEY: key" https://remadata.com/api/v1/webhooks

curl -X DELETE -H "X-API-KEY: key" "https://remadata.com/api/v1/webhooks?id=6710ab..."

Errors

Every /api/v1 error carries a stable code. Branch on that, not on the message — wording may change.

unauthorizedRequired
401Missing, invalid or disabled API key.
invalid_requestRequired
400A field is missing or malformed.
insufficient_balanceRequired
402Your wallet cannot cover this purchase.
not_foundRequired
404No such order, registration or webhook.
duplicate_requestRequired
409Already registered.
provider_failedRequired
502Upstream rejected it. You were not charged, or were refunded.
service_unavailableRequired
503That service is switched off right now.
internal_errorRequired
500Our fault. Safe to retry with the same Idempotency-Key.
Error shape
{
  "status": "error",
  "code": "insufficient_balance",
  "message": "Insufficient wallet balance. This top-up costs GHS 10.00.",
  "required": 10,
  "balance": 3.2
}