First Purchase in 5 Minutes
Complete a sandbox purchase end to end with no real money involved
Make your first purchase against the sandbox: authenticate, check your balance, buy airtime, and verify it completed. Nothing is charged and no real airtime is delivered.
All requests in this guide use the sandbox base URL:
https://my.rizpay.app/api/partners/sandbox/v1
Prerequisites
- A RizPay business account
- API access enabled in your settings
That's it. No funding required: the sandbox returns a mock balance.
Step 1: Create a Sandbox API Key
- Log in at my.rizpay.app
- Go to Settings > API Keys
- Click Create New Key
- Select the Sandbox environment
- Select these scopes:
read_balance,view_products,purchase_airtime,read_transactions - Copy your secret key (shown only once!)
Your key looks like sk_test_xxxxxxxxxxxx. Sandbox keys only work on sandbox endpoints, so there is no risk of touching production.
Warning: Never share your secret key or commit it to version control. Store it in an environment variable:
bashexport RIZPAY_API_KEY="sk_test_your_test_key"
Step 2: Confirm the API Is Up
The health endpoint needs no authentication:
curl -X GET \
https://my.rizpay.app/api/partners/sandbox/v1/health
Response:
{
"status": { "code": 200, "message": "OK" },
"data": {
"service": "partners-api-sandbox",
"version": "1.0.0",
"environment": "sandbox",
"timestamp": "2026-05-18T11:39:50+01:00"
}
}
Step 3: Check Your Balance
Verify your key works. The sandbox returns a mock balance:
curl -X GET \
-H "Authorization: Bearer $RIZPAY_API_KEY" \
https://my.rizpay.app/api/partners/sandbox/v1/account/balance
Response:
{
"status": { "code": 200, "message": "Balance retrieved" },
"data": {
"balance": "100000.00",
"currency": "NGN",
"updated_at": "2026-05-18T11:40:02+01:00"
}
}
If you get an ENVIRONMENT_MISMATCH error, you are using a sk_live_ key. Create a sandbox key (sk_test_) and try again.
Step 4: Pick a Product
List airtime products. The sandbox returns real product data:
curl -X GET \
-H "Authorization: Bearer $RIZPAY_API_KEY" \
"https://my.rizpay.app/api/partners/sandbox/v1/products/airtimes?network=MTN"
Response:
{
"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
}
}
Note the product id: you need it for the purchase. Product ids are numeric with a prd_ prefix (e.g. prd_42) and differ per environment, so always use the id from your own response, not the one in this example.
Step 5: Create the Purchase
The sandbox decides the outcome of a purchase from the phone number you send. Only these test numbers are recognized for airtime:
| Phone number | Outcome |
|---|---|
08011111111 | successful immediately |
08022222222 | pending, auto-transitions to successful after ~30 seconds |
08033333333 | pending, auto-transitions to failed after ~30 seconds |
08044444444 | failed with INSUFFICIENT_BALANCE |
08055555555 | failed with VALIDATION_ERROR |
08066666666 | reversed |
| Any other number | failed with PURCHASE_FAILED |
Use 08011111111 for an instant success. Replace prd_42 with the product id from Step 4:
curl -X POST \
-H "Authorization: Bearer $RIZPAY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"product_id": "prd_42",
"phone_number": "08011111111",
"amount": "100.00",
"external_reference": "1736234400T3S4T5"
}' \
https://my.rizpay.app/api/partners/sandbox/v1/purchases
Response:
{
"status": { "code": 201, "message": "Purchase created successfully" },
"data": {
"id": "txn_5f1c3a9e-2b4d-4c6f-8a1e-9d7b5c3a2f4e",
"type": "transaction",
"attributes": {
"amount": "100.0",
"currency": "NGN",
"status": "successful",
"category": "purchase",
"description": "Successful airtime purchase",
"reference": "sandbox_1a2b3c4d5e6f7a8b9c0d",
"external_reference": "1736234400T3S4T5",
"product_type": "airtime",
"phone_number": "08011111111",
"meter_number": null,
"price": {
"product_amount": "100.00",
"fee_amount": "0.00",
"total_debit": "100.00",
"currency": "NGN",
"basis": "face_value"
},
"created_at": "2026-05-18T11:41:10+01:00",
"updated_at": "2026-05-18T11:41:10+01:00"
}
}
}
Tip: The
external_referencemust be exactly 16 characters: a 10-digit Unix timestamp plus 6 alphanumeric characters, and unique per transaction. See Duplicate Prevention for code that generates one.
If you send a phone number that is not in the table above, the sandbox rejects the purchase:
{
"status": { "code": 400, "message": "Bad Request" },
"error": {
"code": "PURCHASE_FAILED",
"message": "Purchase failed. Please try again."
}
}
Step 6: Walk the Pending Lifecycle
Production purchases usually start as pending and complete on the provider side. To rehearse that flow, create a second purchase with the pending test number 08022222222 (and a fresh external_reference):
curl -X POST \
-H "Authorization: Bearer $RIZPAY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"product_id": "prd_42",
"phone_number": "08022222222",
"amount": "100.00",
"external_reference": "1736234460P3N6D1"
}' \
https://my.rizpay.app/api/partners/sandbox/v1/purchases
The response has "status": "pending". It transitions to successful on its own after about 30 seconds, or you can query it to complete it immediately (replace the id with the id from your response):
curl -X POST \
-H "Authorization: Bearer $RIZPAY_API_KEY" \
https://my.rizpay.app/api/partners/sandbox/v1/purchases/txn_7e2d4b1c-9a3f-4e5d-b6c8-1f0a9e8d7c6b/query
Response:
{
"status": { "code": 200, "message": "Purchase details retrieved" },
"data": {
"id": "txn_7e2d4b1c-9a3f-4e5d-b6c8-1f0a9e8d7c6b",
"type": "transaction",
"attributes": {
"amount": "100.0",
"currency": "NGN",
"status": "successful",
"category": "purchase",
"description": "Pending transaction - transitions to successful after delay",
"reference": "sandbox_0d9c8b7a6f5e4d3c2b1a",
"external_reference": "1736234460P3N6D1",
"product_type": "airtime",
"phone_number": "08022222222",
"meter_number": null,
"price": {
"product_amount": "100.00",
"fee_amount": "0.00",
"total_debit": "100.00",
"currency": "NGN",
"basis": "face_value"
},
"created_at": "2026-05-18T11:42:30+01:00",
"updated_at": "2026-05-18T11:42:35+01:00"
}
}
}
You can fetch the transaction at any time with GET /purchases/:id and get the same shape back.
Note:
POST /purchases/:id/queryonly works while a transaction ispending. Querying a transaction that already completed returns anINVALID_STATUSerror, so there is nothing to query on the Step 5 purchase: it wassuccessfulfrom the start.
That's the complete purchase lifecycle: created, pending, successful. In production the transition happens on the provider side and you can receive it as a webhook event instead of polling.
What You Just Did
| Step | Endpoint | What it proved |
|---|---|---|
| Health check | GET /health | The API is reachable |
| Balance | GET /account/balance | Your key authenticates |
| List products | GET /products/airtimes | You can discover products |
| Purchase | POST /purchases | You can create transactions |
| Lifecycle | POST /purchases/:id/query then GET /purchases/:id | You can handle pending statuses |
Going Live
When your integration works end to end in the sandbox:
- Create a production API key (
sk_live_) - Change the base URL to
https://my.rizpay.app/api/partners/v1 - Fund your account and test with a small amount first
The endpoints and payloads are identical between sandbox and production. The test phone numbers are sandbox-only: in production, every valid number is a real purchase.
Next Steps
- Sandbox Testing - Full sandbox behavior and testing checklist
- Authentication - Scopes and environment separation
- Error Handling - Handle every error code gracefully
- Webhooks - Get notified instead of polling
