# Buy Bulk Airtime

Top up many phone numbers in one request. Send a JSON list of recipients; Paybeta checks the whole list, confirms your wallet covers it, then processes **each number** on its own — one failed number never holds up the rest.

### Authentication
Every request is authenticated with your live API key in the `P-API-KEY` header. No transaction PIN is needed. [See Authentication details.](/overview#required-request-headers)

### How it works
1. Submit the list. Every recipient is validated up front; if any row is invalid the whole request is rejected with the row in the error key (e.g. `recipients.3.phone_number`) and nothing is queued.
2. The priced total (`total_charge`, after your API commission) must be covered by your wallet balance.
3. Each recipient is then processed in turn (a few seconds apart): priced with your API commission, debited from your wallet, sent to the network once, and reported to your **webhook** like any airtime purchase.
4. A recipient that can't be charged (e.g. balance ran out mid-way, network down) is marked failed and not debited. An unconfirmed network response stays `pending` until it is confirmed (`delivered`) or refunded (`reversed`).
5. The same phone number appearing twice in one request is topped up at least 5 minutes apart.


### Rules
- Up to 1,000 recipients per request; total of all amounts at least **NGN100**.
- biller: `mtn`, `glo`, `airtel` or `9mobile` (`mtn_vtu` etc. also accepted).
- `phone_number`: 234 + 10 digits (e.g. `2348031234567`); `08031234567` is also accepted.
- amount: NGN50 – NGN20,000 per recipient; a phone number can receive at most NGN50,000 airtime per day.
- `reference` (request): yours, required, **5-40** chars (letters, numbers, `.` `_` `-`), unique per account.
- `recipients[].reference`: optional, 5-60 chars, unique. Defaults to `<reference>-<row>` (e.g. `PAYROLL-OCT-2`). No reference may have been used for an earlier transaction.

   
### Tracking a number
Each recipient is its own transaction: query it with `POST /v2/transaction/query` and the recipient's `reference`, or watch your webhook — `data.reference` is the recipient reference, plus `batchReference`, `batchId` and `row`.

### Statuses
Batch: `queued` → `processing` → `completed`, or `cancelled`. Recipient: `queued`, `processing`, `delivered`, `pending`, `failed` (not charged), `reversed` (refunded), `cancelled` (skipped).

### Response formats
**Success Response**
**HTTP Status:** `200 OK` (or `201 Created`)

```js
{
  "status": "successful",
  "message": "Operation completed successfully.",
  "data": {}
}
```

### Error Response
**HTTP Status:** `4xx`


```js
{
  "status": "failed",
  "message": "Error description here.",
  "errors": {
    "field_name": [
      "Validation or constraint error message."
    ]
  }
}
```


### HTTP Status Codes


| Code | Status | Trigger / Description
| --- | --- | --- |
| **401** | Unauthorized | Missing or invalid API key |
| **404** | Not Found | Requested resource not found |
| **422** | Unprocessable Entity | Validation failed or action not allowed in current state |
