This document provides a comprehensive reference for all error codes, their causes, and recommended resolution steps.
All API errors follow a consistent JSON structure:
JSON
{
"__all__": {
"message": "Descriptive error message",
"code": "error_code"
}
}
Field-specific errors:
JSON
{
"email": {
"message": "Enter a valid email address",
"code": "invalid"
},
"purchase.currency": {
"message": "This field is required",
"code": "required"
}
}
| Status | Description | Common Causes |
|---|---|---|
200 | Success | Request completed successfully |
201 | Created | Resource created successfully |
204 | No Content | Successful deletion |
400 | Bad Request | Validation errors, malformed JSON |
401 | Unauthorized | Invalid or missing API key |
403 | Forbidden | Insufficient permissions |
404 | Not Found | Resource doesn't exist |
409 | Conflict | Duplicate request, state conflict |
422 | Unprocessable Entity | Business logic error |
429 | Too Many Requests | Rate limit exceeded |
500 | Internal Server Error | Server-side error |
502 | Bad Gateway | Upstream service unavailable |
503 | Service Unavailable | Temporary maintenance |
invalid_token
HTTP Status: 401
Message: Invalid or expired authentication token
Causes:
Resolution:
missing_authorization
HTTP Status: 401
Message: Authorization header is required
Causes:
Resolution:
BASH
# Correct format
-H "Authorization: Bearer YOUR_API_KEY"
# Common mistakes
-H "Authorization: YOUR_API_KEY" # Missing "Bearer"
-H "Bearer YOUR_API_KEY" # Missing "Authorization:"
required
HTTP Status: 400
Message: This field is required
Example:
JSON
{
"brand_id": {
"message": "This field is required",
"code": "required"
}
}
Resolution: Include all required fields in your request.
invalid
HTTP Status: 400
Message: Invalid value for this field
Common Cases:
| Field | Valid Format | Example Error |
|---|---|---|
email | Valid email | invalid_email@ |
currency | ISO 4217 (3 chars) | RAND instead of ZAR |
phone | E.164 format | 0821234567 instead of +27821234567 |
country | ISO 3166-1 alpha-2 | South Africa instead of ZA |
invalid_uuid
HTTP Status: 400
Message: Invalid UUID format
Causes:
client_id or brand_id is not a valid UUID
Valid UUID format:
3fa85f64-5717-4562-b3fc-2c963f66afa6
max_length
HTTP Status: 400
Message: Ensure this field has no more than X characters
Field Limits:
| Field | Max Length |
|---|---|
reference | 128 |
purchase.products[].name | 256 |
success_redirect | 500 |
cardholder_name | 30 |
card_number | 19 |
min_value / max_value
HTTP Status: 400
Message: Ensure this value is greater/less than X
Amount Limits:
| Field | Min | Max |
|---|---|---|
purchase.products[].price | 0 | 9,999,999,999 |
purchase.total_override | 0 | 999,999,999,999,999 |
purchase_not_found
HTTP Status: 404
Message: Purchase not found
Causes:
Resolution:
purchase_already_paid
HTTP Status: 409
Message: Purchase has already been paid
Causes:
Resolution:
purchase_cancelled
HTTP Status: 409
Message: Purchase has been cancelled
Causes:
Resolution:
purchase_expired
HTTP Status: 409
Message: Purchase has expired
Causes:
due the date has passed with due_strict: true
Resolution:
invalid_amount
HTTP Status: 400
Message: Invalid amount specified
Causes:
Resolution:
capture_not_allowed
HTTP Status: 409
Message: Capture is not allowed for this purchase
Causes:
skip_capture: truehold status
Resolution:
holdskip_capture: true for pre-auth
refund_exceeds_amount
HTTP Status: 400
Message: Refund amount exceeds available balance
Causes:
Resolution:
PYTHON
# Check available refund amount
purchase = get_purchase(purchase_id)
available = purchase['purchase']['total'] - purchase.get('refunded_amount', 0)
payment_method_not_available
HTTP Status: 400
Message: Payment method not available
Causes:
payment_method_whitelist
Resolution:
payment_method_whitelist in request
recurring_token_not_found
HTTP Status: 404
Message: Recurring token not found
Causes:
Resolution:
GET /clients/{id}/recurring_tokens/
recurring_token_expired
HTTP Status: 409
Message: Recurring token has expired
Causes:
Resolution:
card_declined
HTTP Status: 422
Message: Card was declined
Decline Reasons:
| Code | Description | Customer Action |
|---|---|---|
do_not_honour | Generic decline | Try different card |
insufficient_funds | Not enough balance | Add funds or use different card |
expired_card | Card has expired | Use valid card |
invalid_card | Card number invalid | Check card details |
lost_stolen | Card reported lost/stolen | Contact bank |
suspected_fraud | Fraud suspicion | Contact bank |
3ds_authentication_failed
HTTP Status: 422
Message: 3D Secure authentication failed
Causes:
Resolution:
client_not_found
HTTP Status: 404
Message: Client not found
Resolution:
duplicate_client
HTTP Status: 409
Message: Client with this email already exists
Causes:
Resolution:
GET /clients/?email=customer@example.com
billing_template_not_found
HTTP Status: 404
Message: Billing template not found
subscriber_already_exists
HTTP Status: 409
Message: Client is already subscribed to this template
Resolution:
subscription_inactive
HTTP Status: 409
Message: Subscription is not active
Causes:
subscription_active: false on template
webhook_url_invalid
HTTP Status: 400
Message: Invalid webhook URL
Requirements:
webhook_url_unreachable
HTTP Status: 400
Message: Webhook URL is not reachable
Resolution:
rate_limit_exceeded
HTTP Status: 429
Message: Rate limit exceeded
Response Headers:
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1619740860
Resolution:
Retry Logic:
PYTHON
import time
def api_request_with_retry(func, max_retries=3):
for attempt in range(max_retries):
response = func()
if response.status_code != 429:
return response
reset_time = int(response.headers.get('X-RateLimit-Reset', 0))
wait_time = max(reset_time - time.time(), 2 ** attempt)
time.sleep(wait_time)
raise Exception("Rate limit exceeded after retries")
direct_post_expired
HTTP Status: 400
Message: Direct post URL has expired
Causes:
Resolution:
invalid_card_data
HTTP Status: 400
Message: Invalid card data submitted
Field Validation:
| Field | Validation | Error |
|---|---|---|
cardholder_name | Latin letters, space, apostrophe, dot, dash | Non-Latin characters |
card_number | Digits only, valid Luhn | Invalid format |
expires | MM/YY format, future date | Past date or invalid format |
cvc | 3-4 digits | Invalid length |
1. Always Check HTTP Status
PYTHON
response = requests.post(url, json=data, headers=headers)
if response.status_code == 201:
# Success - process response
purchase = response.json()
elif response.status_code == 400:
# Validation error - show user-friendly message
errors = response.json()
handle_validation_errors(errors)
elif response.status_code == 401:
# Auth error - check credentials
refresh_credentials()
elif response.status_code == 429:
# Rate limited - retry with backoff
retry_with_backoff()
else:
# Other error - log and alert
log_error(response)
2. Display User-Friendly Messages
PYTHON
ERROR_MESSAGES = {
'card_declined': 'Your card was declined. Please try a different payment method.',
'insufficient_funds': 'Insufficient funds. Please use a different card.',
'expired_card': 'Your card has expired. Please use a valid card.',
'3ds_authentication_failed': 'Authentication failed. Please try again.',
'invalid': 'Please check your information and try again.'
}
def get_user_message(error_code):
return ERROR_MESSAGES.get(error_code, 'An error occurred. Please try again.')
3. Log Errors for Debugging
PYTHON
import logging
def log_api_error(response, request_data):
logging.error(
"API Error",
extra={
'status_code': response.status_code,
'response_body': response.text,
'request_url': response.url,
'request_data': sanitize_sensitive_data(request_data)
}
)
4. Implement Retry Logic
PYTHON
from tenacity import retry, stop_after_attempt, wait_exponential
@retry(
stop=stop_after_attempt(3),
wait=wait_exponential(multiplier=1, min=2, max=10),
retry=lambda e: isinstance(e, RetryableError)
)
def make_api_request():
response = requests.post(...)
if response.status_code in [429, 502, 503]:
raise RetryableError()
return response
| Code | HTTP | Category | Action |
|---|---|---|---|
invalid_token | 401 | Auth | Check API key |
required | 400 | Validation | Add missing field |
invalid | 400 | Validation | Fix field format |
purchase_not_found | 404 | Resource | Verify ID |
purchase_already_paid | 409 | State | Check status first |
card_declined | 422 | Payment | Try different card |
insufficient_funds | 422 | Payment | Try different card |
3ds_authentication_failed | 422 | Payment | Retry authentication |
recurring_token_expired | 409 | Token | Re-tokenize |
rate_limit_exceeded | 429 | Rate Limit | Implement backoff |