Buying data
The request
Section titled “The request”POST /v1/purchases with your reference, the number and the bundle.
POST /v1/purchases HTTP/1.1Host: api.poweredbyaura.comAuthorization: Bearer aura_live_…Content-Type: application/json
{ "reference": "order-10293", "phone_number": "+2348000000001", "bundle_id": "bnd_ng_a_1gb_1d", "metadata": { "customer_id": "cus_88412" }}| Field | Required | Notes |
|---|---|---|
reference |
Yes | Your unique ID for this purchase: letters, numbers, -, _, . and :, up to 64 characters. |
phone_number |
Yes | E.164 format. |
bundle_id |
Yes | From List bundles. |
network_id |
No | Send it if you want Aura to refuse the purchase when the number is on a different network. |
metadata |
No | Up to 20 string values of your own, returned on the purchase and in webhooks. Do not put personal data here. |
The response
Section titled “The response”A new purchase returns 201 Created with the purchase in its current state. That is usually pending.
{ "id": "pur_01JA7ZK3Q9W2D8M5X4N6B1C0VE", "object": "purchase", "reference": "order-10293", "status": "pending", "phone_number": "+2348000000001", "network_id": "net_ng_a", "bundle": { "id": "bnd_ng_a_1gb_1d", "name": "1GB Daily", "data_mb": 1024, "validity_hours": 24 }, "price": { "amount": 35000, "currency": "NGN" }, "created_at": "2026-10-12T09:14:30Z", "resolve_by": "2026-10-12T09:44:30Z", "completed_at": null, "delivery": null, "failure": null, "reversal": null, "metadata": { "customer_id": "cus_88412" }}Store the id and resolve_by with your order. Do not tell your customer the data has arrived until the
purchase is successful.
What can come back
Section titled “What can come back”| Status | Code | Meaning | What to do |
|---|---|---|---|
201 |
Accepted. | Wait for the final result. | |
200 |
You already sent this reference with the same body. The original purchase is returned, with Aura-Idempotent-Replay: true. |
Treat it as the same purchase. | |
400 |
invalid_request, invalid_phone_number |
A field is missing or malformed. | Fix the request. |
401 |
invalid_api_key |
The key is missing, wrong or revoked. | Check the key. |
402 |
insufficient_balance |
Your available balance does not cover the price. | Top up, then retry with the same reference. |
403 |
ip_not_allowed |
The request came from outside your IP allowlist. | Send from an allowed address. |
409 |
reference_conflict |
The reference was used for a different purchase. | Use a new reference. |
422 |
bundle_unavailable, network_mismatch, unsupported_network |
The purchase cannot be made as asked. | Refresh the catalogue or pick another bundle. |
429 |
rate_limited |
Too many requests. | Wait for Retry-After, then retry. |
500 |
internal_error |
Something failed on Aura’s side. | Retry with the same reference. |
503 |
network_unavailable |
The network is not taking purchases. The error carries its live status. | Retry later with the same reference, or offer another network. |
Nothing is bought or held for any response other than 201. The full list is in Errors.
A purchase with safe retries
Section titled “A purchase with safe retries”A timeout or a dropped connection leaves you not knowing whether Aura received the request. Retrying with the same reference is always safe: you get the original purchase back, never a second one.
const API = 'https://api.poweredbyaura.com/v1';
export async function buyData({ reference, phoneNumber, bundleId }) { const body = JSON.stringify({ reference, phone_number: phoneNumber, bundle_id: bundleId });
for (let attempt = 1; attempt <= 5; attempt++) { let res; try { res = await fetch(`${API}/purchases`, { method: 'POST', headers: { Authorization: `Bearer ${process.env.AURA_API_KEY}`, 'Content-Type': 'application/json' }, body, signal: AbortSignal.timeout(15_000), }); } catch { res = undefined; // Timed out or the connection dropped: safe to retry with the same reference. } if (res && res.status !== 429 && res.status < 500) { return { httpStatus: res.status, body: await res.json() }; } const waitSeconds = Number(res?.headers.get('retry-after')) || 2 ** attempt; await new Promise((resolve) => setTimeout(resolve, waitSeconds * 1000)); } throw new Error(`No answer for ${reference}. Look it up by reference before trying anything else.`);}import os, time, requests
API = "https://api.poweredbyaura.com/v1"
def buy_data(reference: str, phone_number: str, bundle_id: str) -> tuple[int, dict]: body = {"reference": reference, "phone_number": phone_number, "bundle_id": bundle_id} headers = {"Authorization": f"Bearer {os.environ['AURA_API_KEY']}"}
for attempt in range(1, 6): try: res = requests.post(f"{API}/purchases", json=body, headers=headers, timeout=15) except requests.RequestException: res = None # Timed out or the connection dropped: safe to retry with the same reference. if res is not None and res.status_code != 429 and res.status_code < 500: return res.status_code, res.json() wait = int(res.headers.get("Retry-After", 0)) if res is not None else 0 time.sleep(wait or 2 ** attempt) raise RuntimeError(f"No answer for {reference}. Look it up by reference before trying anything else.")© 2026 Aura Mobile Communications Ltd ·poweredbyaura.com ·Privacy ·Terms