Errors, retries and lists
Amounts#
Every amount is an integer in the currency's smallest unit: kobo for NGN (₦1 = 100 kobo) and cents for USD. 1000000 is ₦10,000. There are no decimals anywhere in the API, so nothing gets rounded on the way.
Errors#
Biftpay uses HTTP status codes, and every error has the same body:
{
"error": {
"type": "invalid_request_error",
"code": "parameter_invalid",
"message": "amount: Number must be greater than 0",
"param": "amount"
}
}| Status | type | What to do |
|---|---|---|
400 | invalid_request_error | Something in the request is wrong. param names the field. Fix it; retrying won't help. |
401 | authentication_error | The API key is missing, wrong or revoked. |
403 | authentication_error | The key can't do this, or the account can't yet (for example live payments before approval). |
404 | invalid_request_error | No such object, or it belongs to another account or mode. |
409 | state_error | The object is in the wrong state, for example canceling a payment that already succeeded, or a payout larger than your available balance (insufficient_funds). |
422 | idempotency_error | The Idempotency-Key was already used with a different request. |
5xx | api_error | Something went wrong on Biftpay's side. Retry with the same Idempotency-Key. |
Use code in your own logic, not message: messages are written for people and may change.
Idempotency: retrying safely#
Networks fail. If a request to create a payment or a payout times out, you can't tell whether it went through. Every POST therefore needs an Idempotency-Key header: a unique value per action, such as a UUID or your order id.
curl https://sandbox.api.biftpay.com/v1/payouts \
-H "Authorization: Bearer bp_test_..." \
-H "Idempotency-Key: payout-2026-10-04-vendor-17" \
-H "Content-Type: application/json" \
-d '{ "amount": 500000, "currency": "NGN", "bank_account_id": "..." }'- Retry with the same key and the same body and you get the original response back, with the header
Idempotent-Replayed: true. Nothing happens twice. - The same key with a different body is refused with
422 idempotency_key_reused. - If the first try failed with a
5xx, nothing was saved, so the retry runs normally. - A
4xxanswer is saved and replayed too; fix the request and use a new key.
Use a new key for each new action, and the same key only when retrying that action.
Lists and pages#
List endpoints return the newest first:
{ "object": "list", "data": [ ... ], "has_more": true }Ask for up to 100 at a time with limit (default 20). For the next page, pass the id of the last item you got as starting_after:
curl "https://sandbox.api.biftpay.com/v1/payment_intents?limit=100&starting_after=5b0c3f6e-..." \
-H "Authorization: Bearer bp_test_..."Stop when has_more is false.
Test and live#
Keys starting bp_test_ see only test data; bp_live_ keys see only live data. Ids from one mode aren't found in the other, and every object has livemode so you can tell them apart.