---
title: Airtime
description: Purchase mobile airtime for all major networks
---

Purchase airtime for MTN, Airtel, Glo, and 9mobile networks.

## Supported Networks

| Network        | Code      |
| -------------- | --------- |
| MTN Nigeria    | `MTN`     |
| Airtel Nigeria | `AIRTEL`  |
| Globacom       | `GLO`     |
| 9mobile        | `9MOBILE` |

## Purchase Flow

Airtime purchases are straightforward - no verification required.

1. **List products** - Get available airtime products
2. **Make purchase** - Send the purchase request

## Step 1: List Products

```bash
curl -X GET \
  -H "Authorization: Bearer sk_live_your_secret_key" \
  "https://my.rizpay.app/api/partners/v1/products/airtimes?network=MTN"
```

Response:

```json
{
  "status": { "code": 200, "message": "Airtime products retrieved" },
  "data": [
    {
      "id": "prd_42",
      "type": "airtime",
      "attributes": {
        "display_name": "MTN Airtime",
        "network": "MTN",
        "min_amount": "50.0",
        "max_amount": "50000.0",
        "price": {
          "currency": "NGN",
          "basis": "face_value",
          "min_amount": "50.00",
          "max_amount": "50000.00"
        }
      }
    }
  ],
  "pagination": {
    "page": 1,
    "per_page": 20,
    "total_pages": 1,
    "total_count": 1
  }
}
```

Product ids are numeric with a `prd_` prefix (e.g. `prd_42`). Use the `id` from your own response in the purchase request.

### The `price` block

Airtime is a variable-priced product - the partner picks the amount. RizPay
returns the supported range as a `face_value` tuple. The partner will be
billed exactly the `amount` they submit on the purchase request (no markup
today). To quote your end-user, take their request amount, add your margin,
and bill them; RizPay will bill you the underlying amount.

| Field        | Type   | Notes                                     |
| ------------ | ------ | ----------------------------------------- |
| `currency`   | string | Always `NGN`                              |
| `basis`      | string | `face_value` (partner chooses the amount) |
| `min_amount` | string | Smallest amount accepted by the network   |
| `max_amount` | string | Largest amount accepted by the network    |

## Step 2: Make Purchase

```bash
curl -X POST \
  -H "Authorization: Bearer sk_live_your_secret_key" \
  -H "Content-Type: application/json" \
  -d '{
    "product_id": "prd_42",
    "phone_number": "08012345678",
    "amount": "500.00",
    "external_reference": "1736234400B2C3D4"
  }' \
  https://my.rizpay.app/api/partners/v1/purchases
```

Response:

```json
{
  "status": { "code": 201, "message": "Purchase created successfully" },
  "data": {
    "id": "txn_abc123",
    "type": "transaction",
    "attributes": {
      "amount": "500.0",
      "currency": "NGN",
      "status": "pending",
      "category": "purchase",
      "description": "Purchase of MTN Airtime",
      "reference": "a1b2c3d4e5f607181736234400",
      "external_reference": "1736234400B2C3D4",
      "product_type": "airtime",
      "phone_number": "08012345678",
      "meter_number": null,
      "price": {
        "product_amount": "500.00",
        "fee_amount": "0.00",
        "total_debit": "500.00",
        "currency": "NGN",
        "basis": "face_value"
      },
      "created_at": "2026-05-18T10:30:00+01:00",
      "updated_at": "2026-05-18T10:30:00+01:00"
    }
  }
}
```

### The `price` breakdown

Every purchase response carries a `price` object so you can reconcile the
debit against your customer charge:

| Field            | Type   | Notes                                                                       |
| ---------------- | ------ | --------------------------------------------------------------------------- |
| `product_amount` | string | What the underlying product cost. For airtime equals your requested amount. |
| `fee_amount`     | string | Any partner-tier markup RizPay applied. Zero today.                         |
| `total_debit`    | string | What was actually taken from your wallet (`product_amount + fee_amount`).   |
| `currency`       | string | Always `NGN`.                                                               |
| `basis`          | string | `face_value` for airtime/electricity, `fixed` for data/cable TV.            |

## Check Transaction Status

Poll the transaction or use webhooks to get the final status:

```bash
curl -X GET \
  -H "Authorization: Bearer sk_live_your_secret_key" \
  "https://my.rizpay.app/api/partners/v1/purchases/txn_abc123"
```

Successful response:

```json
{
  "status": { "code": 200, "message": "Purchase details retrieved" },
  "data": {
    "id": "txn_abc123",
    "type": "transaction",
    "attributes": {
      "amount": "500.0",
      "currency": "NGN",
      "status": "successful",
      "category": "purchase",
      "description": "Purchase of MTN Airtime",
      "reference": "a1b2c3d4e5f607181736234400",
      "external_reference": "1736234400B2C3D4",
      "product_type": "airtime",
      "phone_number": "08012345678",
      "meter_number": null,
      "price": {
        "product_amount": "500.00",
        "fee_amount": "0.00",
        "total_debit": "500.00",
        "currency": "NGN",
        "basis": "face_value"
      },
      "created_at": "2026-05-18T10:30:00+01:00",
      "updated_at": "2026-05-18T10:30:02+01:00"
    }
  }
}
```

The transaction is done when `status` reaches `successful`, `failed`, or `reversed`. There is no separate completion timestamp: use `updated_at`.

## Complete Example

```javascript
// Generate external reference: 10-digit timestamp + 6 alphanumeric
function generateReference() {
  const timestamp = Math.floor(Date.now() / 1000);
  const chars =
    "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
  let suffix = "";
  for (let i = 0; i < 6; i++) {
    suffix += chars.charAt(Math.floor(Math.random() * chars.length));
  }
  return `${timestamp}${suffix}`;
}

async function purchaseAirtime(network, phoneNumber, amount) {
  const API_KEY = "sk_live_your_secret_key";
  const BASE_URL = "https://my.rizpay.app/api/partners/v1";

  // Step 1: Find the product
  const productsRes = await fetch(
    `${BASE_URL}/products/airtimes?network=${network}`,
    { headers: { Authorization: `Bearer ${API_KEY}` } }
  );
  const products = await productsRes.json();
  const product = products.data[0];

  // Validate amount
  if (parseFloat(amount) < parseFloat(product.attributes.min_amount)) {
    throw new Error(`Minimum amount is ${product.attributes.min_amount}`);
  }
  if (parseFloat(amount) > parseFloat(product.attributes.max_amount)) {
    throw new Error(`Maximum amount is ${product.attributes.max_amount}`);
  }

  // Step 2: Make purchase
  const purchaseRes = await fetch(`${BASE_URL}/purchases`, {
    method: "POST",
    headers: {
      Authorization: `Bearer ${API_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      product_id: product.id,
      phone_number: phoneNumber,
      amount: amount,
      external_reference: generateReference(),
    }),
  });

  return await purchaseRes.json();
}

// Usage
purchaseAirtime("MTN", "08012345678", "1000.00")
  .then((result) => console.log(result))
  .catch((error) => console.error(error));
```

## Phone Number Validation

Nigerian phone numbers should:

- Start with `080`, `081`, `070`, `090`, or `091`
- Be 11 digits total
- Match the network of the product (or be cross-network if supported)

| Prefix                                                     | Network |
| ---------------------------------------------------------- | ------- |
| 0803, 0806, 0813, 0816, 0810, 0814, 0903, 0906, 0913, 0916 | MTN     |
| 0802, 0808, 0812, 0701, 0708, 0902, 0907, 0912             | Airtel  |
| 0805, 0807, 0811, 0815, 0705, 0905, 0915                   | Glo     |
| 0809, 0817, 0818, 0908, 0909                               | 9mobile |

## Amount Limits

| Network | Min | Max    |
| ------- | --- | ------ |
| MTN     | 50  | 50,000 |
| Airtel  | 50  | 50,000 |
| Glo     | 50  | 50,000 |
| 9mobile | 50  | 50,000 |

_Limits may vary. Check the product `min_amount` and `max_amount` fields._

## Transaction States

| Status       | Description                        |
| ------------ | ---------------------------------- |
| `pending`    | Processing with provider           |
| `successful` | Airtime delivered                  |
| `failed`     | Purchase failed (balance refunded) |

## Required Scope

Requires the `purchase_airtime` scope on your API key.

## Next Steps

- [Data Plans](/docs/products/data-plans) - Buy data bundles
- [Webhooks](/docs/webhooks/overview) - Get notified when purchase completes