Getting started
Base URL: https://app.flypaynexus.com/api/v1. All requests and responses use JSON. Amounts you send are decimal strings in major units ("1500.50"); amounts we return are integers in minor units (amount_minor) with an explicit currency.
- Create an account and open API Keys.
- Create a test secret key (
sk_test_…). It is shown once — store it in your server’s secrets. - Create a payment link or payment, then listen for webhooks.
curl https://app.flypaynexus.com/api/v1/payment-links \
-H "Authorization: Bearer $FLYPAY_SECRET_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: link-2026-10-13-001" \
-d '{"title":"Consultation","amount":"50000","currency":"TZS"}'Python example
import os, requests
r = requests.post(
"https://app.flypaynexus.com/api/v1/payment-links",
headers={"Authorization": f"Bearer {os.environ['FLYPAY_SECRET_KEY']}", "Idempotency-Key": "link-001"},
json={"title": "Consultation", "amount": "50000", "currency": "TZS"},
timeout=15,
)
r.raise_for_status()
print(r.json()["url"])Authentication
Send your secret key as a bearer token: Authorization: Bearer sk_test_…. Keys are scoped to one business. Never expose secret keys in browsers or mobile apps — call the API from your server.
| Key prefix | Mode | Behaviour |
|---|---|---|
sk_test_ | Sandbox | Uses simulated sandbox methods only. No real money moves. |
sk_live_ | Live | Available only for verified businesses on deployments with live providers enabled. |
Requests are rate-limited per key (120 requests/minute by default). Exceeding it returns 429.
Sandbox testing
Sandbox methods: sandbox_mpesa, sandbox_airtel, sandbox_mixx, sandbox_halopesa, sandbox_card, sandbox_bank. Mobile money outcomes depend on the payer phone number’s last three digits:
| Phone ends with | Result |
|---|---|
000 | Fails — insufficient_funds |
111 | Customer cancels |
999 | Stays pending (never approved; expires after 30 minutes) |
| anything else | Succeeds after a short delay |
Card and bank-transfer sandbox payments open a simulated provider page where you choose Approve or Decline. Our checkout never collects card numbers.
Idempotency
Send an Idempotency-Key header on POST requests. Retrying with the same key and body returns the original response without creating a duplicate. Reusing a key with a different body returns 409 idempotency_conflict. Keys are kept for 24 hours.
Errors
{
"error": {
"code": "validation_failed",
"message": "Amount must be a positive number with at most 2 decimals",
"details": { "amount": "..." },
"request_id": "6f1c…"
}
}| Status | Common codes |
|---|---|
| 400 | invalid_json |
| 401 | unauthorized |
| 403 | forbidden, not_verified |
| 404 | not_found |
| 409 | idempotency_conflict, not_payable |
| 422 | validation_failed, method_not_allowed, insufficient_balance |
| 429 | rate_limited |
| 502 | provider_error |
Every response includes an X-Request-Id header — include it when contacting support.
Pagination
List endpoints accept page (from 1) and limit (1–100, default 20) and return { object: "list", data, page, limit, total, has_more }.
Versioning
The version is in the path (/api/v1). We add fields and endpoints without a new version; breaking changes ship as a new version with at least 6 months’ notice for the previous one.
Troubleshooting
- 401 unauthorized — check the key is complete, not revoked, and sent as
Bearer. - 422 method_not_allowed — the method is not enabled for your business, not available in this mode, or doesn’t support the currency.
- Payment stuck in pending — mobile money waits for the customer to approve. Poll
GET /payments/{id}or rely on webhooks; unpaid payments expire after 30 minutes. - Webhook signature mismatch — compute the HMAC over the raw request body, not re-serialised JSON.