Error Reference
All POPFAB API errors follow a consistent structure. Use the error code for programmatic handling and message for logging.
Error Response Format
Every error response has the same JSON structure regardless of the endpoint or error type.
Error response structurejson
{
"error": {
"code": "VALIDATION_ERROR",
"message": "Request validation failed",
"details": [
{
"field": "amount",
"message": "amount must be a positive integer"
},
{
"field": "currency",
"message": "currency must be one of: NGN, GHS, KES, ZAR, UGX, XOF, USD"
}
]
}
}✓
Always log the
error.code and error.message fields. For validation errors, inspect error.details for per-field information.HTTP Status Codes
| HTTP Status | Error Code | Description | Action |
|---|---|---|---|
| 400 | VALIDATION_ERROR | The request body or query parameters failed validation. | Check the details array for field-level errors. Fix your request. |
| 401 | UNAUTHORIZED | No API key provided or the key is invalid. | Include a valid Bearer token in the Authorization header. |
| 403 | FORBIDDEN | The API key does not have permission for this operation. | Check that your key's role allows the operation. Owner/Admin required for some endpoints. |
| 404 | NOT_FOUND | The requested resource does not exist or belongs to another merchant. | Check the ID and ensure the resource belongs to your merchant account. |
| 409 | IDEMPOTENCY_CONFLICT | An existing request with this Idempotency-Key was made with a different body. | Generate a new Idempotency-Key for this payment attempt. |
| 422 | UNPROCESSABLE_PAYMENT | The payment request is valid but cannot be processed (e.g. refund on a failed payment). | Check the message field for the specific reason. |
| 429 | RATE_LIMIT_EXCEEDED | The API key has exceeded its rate limit. | Back off and retry after the X-RateLimit-Reset timestamp. |
| 503 | ALL_PROVIDERS_UNAVAILABLE | All providers for this payment type have open circuit breakers. | Retry with exponential backoff. Monitor /v1/providers for recovery. |
| 500 | INTERNAL_ERROR | An unexpected error occurred on POPFAB's servers. | Retry with exponential backoff. Contact support if it persists. |
Handling Errors in Code
Robust error handling — Node.jsjavascript
async function initiatePayment(payload) {
const response = await fetch('https://api.popfab.io/v1/payments', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.POPFAB_API_KEY}`,
'Content-Type': 'application/json',
'Idempotency-Key': payload.idempotencyKey,
},
body: JSON.stringify(payload),
});
const data = await response.json();
if (!response.ok) {
const { error } = data;
switch (error.code) {
case 'VALIDATION_ERROR':
// Fix the request — log details for debugging
throw new Error(`Invalid request: ${JSON.stringify(error.details)}`);
case 'IDEMPOTENCY_CONFLICT':
// This key was used with different params — generate a new key
throw new Error('Idempotency key reused with different body');
case 'RATE_LIMIT_EXCEEDED':
// Back off and retry
await sleep(retryAfterMs(response.headers));
return initiatePayment(payload);
case 'ALL_PROVIDERS_UNAVAILABLE':
// Transient — retry with backoff
await sleep(5000);
return initiatePayment(payload);
default:
console.error('POPFAB error', error.code, error.message);
throw new Error(`Payment failed: ${error.message}`);
}
}
return data;
}
function retryAfterMs(headers) {
const reset = headers.get('X-RateLimit-Reset');
return reset ? Math.max(0, parseInt(reset) * 1000 - Date.now()) : 1000;
}
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}Robust error handling — Pythonpython
import os, time
import httpx
POPFAB_API_KEY = os.environ["POPFAB_API_KEY"]
def initiate_payment(payload: dict, idempotency_key: str) -> dict:
with httpx.Client() as client:
response = client.post(
"https://api.popfab.io/v1/payments",
headers={
"Authorization": f"Bearer {POPFAB_API_KEY}",
"Content-Type": "application/json",
"Idempotency-Key": idempotency_key,
},
json=payload,
)
data = response.json()
if response.status_code >= 400:
error = data["error"]
code = error["code"]
if code == "VALIDATION_ERROR":
raise ValueError(f"Invalid request: {error['details']}")
elif code == "RATE_LIMIT_EXCEEDED":
reset = int(response.headers.get("X-RateLimit-Reset", 0))
wait = max(0, reset - time.time())
time.sleep(wait or 1)
return initiate_payment(payload, idempotency_key)
elif code == "ALL_PROVIDERS_UNAVAILABLE":
time.sleep(5)
return initiate_payment(payload, idempotency_key)
else:
raise RuntimeError(
f"POPFAB error {code}: {error['message']}"
)
return dataIdempotent Retries
Network failures can leave you uncertain whether a request succeeded. Always retry with the same Idempotency-Key — POPFAB will return the original result without creating a duplicate charge.
ℹ
If a request times out before you receive a response, retry it with the same
Idempotency-Key. Do not generate a new key, as that would create a second payment if the first request actually succeeded.← ProvidersNext: SDKs →