Precium API Error Reference
This document provides a comprehensive reference for all error codes, their causes, and recommended resolution steps.
Error Response Format
All API errors follow a consistent JSON structure:
{
"__all__": {
"message": "Descriptive error message",
"code": "error_code"
}
}Field-specific errors:
{
"email": {
"message": "Enter a valid email address",
"code": "invalid"
},
"purchase.currency": {
"message": "This field is required",
"code": "required"
}
}HTTP Status Codes
| 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 |
Authentication Errors
invalid_token
HTTP Status: 401
Message: Invalid or expired authentication token
Causes:
- API key is incorrect
- API key has been revoked
- Using sandbox key in production or vice versa
Resolution:
- Verify your API key in the dashboard
- Ensure you’re using the correct environment
- Generate a new API key if necessary
missing_authorization
HTTP Status: 401
Message: Authorization header is required
Causes:
- No Authorization header provided
- Malformed Authorization header
Resolution:
# Correct format
-H "Authorization: Bearer YOUR_API_KEY"
# Common mistakes
-H "Authorization: YOUR_API_KEY" # Missing "Bearer"
-H "Bearer YOUR_API_KEY" # Missing "Authorization:"Validation Errors
required
HTTP Status: 400
Message: This field is required
Example:
{
"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_idorbrand_idis not a valid UUID- UUID contains invalid characters
Valid UUID format:
3fa85f64-5717-4562-b3fc-2c963f66afa6max_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 Errors
purchase_not_found
HTTP Status: 404
Message: Purchase not found
Causes:
- Invalid purchase ID
- The purchase belongs to a different merchant
- Purchase has been deleted
Resolution:
- Verify the purchase ID is correct
- Ensure you’re using the correct API key
purchase_already_paid
HTTP Status: 409
Message: Purchase has already been paid
Causes:
- Attempting to charge an already-completed purchase
- Duplicate payment submission
Resolution:
- Check purchase status before charging
- Implement idempotency in your integration
purchase_cancelled
HTTP Status: 409
Message: Purchase has been cancelled
Causes:
- Attempting to charge a cancelled purchase
- Purchase expired and was auto-cancelled
Resolution:
- Create a new purchase for the transaction
purchase_expired
HTTP Status: 409
Message: Purchase has expired
Causes:
duethe date has passed withdue_strict: true- Direct post URL has expired (15-minute validity)
Resolution:
- Create a new purchase
- Process payments promptly after creation
invalid_amount
HTTP Status: 400
Message: Invalid amount specified
Causes:
- Negative amount
- Amount exceeds original purchase
- Zero amount for non-authorization purchase
Resolution:
- Ensure the amount is positive
- For partial captures/refunds, ensure the amount ≤ remaining
capture_not_allowed
HTTP Status: 409
Message: Capture is not allowed for this purchase
Causes:
- Purchase was not created with
skip_capture: true - Purchase is not in
holdstatus - Authorization has expired
Resolution:
- Verify the purchase status is
hold - Create a new purchase with
skip_capture: truefor pre-auth
refund_exceeds_amount
HTTP Status: 400
Message: Refund amount exceeds available balance
Causes:
- Refund amount greater than the original payment
- Previous refunds have reduced the available balance
Resolution:
# Check available refund amount
purchase = get_purchase(purchase_id)
available = purchase['purchase']['total'] - purchase.get('refunded_amount', 0)Payment Method Errors
payment_method_not_available
HTTP Status: 400
Message: Payment method not available
Causes:
- Payment method not enabled for your merchant
- Payment method not supported for the currency
- Payment method excluded by
payment_method_whitelist
Resolution:
- Check the enabled payment methods
- Verify currency support for the payment method
- Review
payment_method_whitelistin request
recurring_token_not_found
HTTP Status: 404
Message: Recurring token not found
Causes:
- Invalid token ID
- Token has been deleted
- The token belongs to a different client
Resolution:
- List client tokens:
GET /clients/{id}/recurring_tokens/ - Verify the token ID is correct
- Re-tokenize if necessary
recurring_token_expired
HTTP Status: 409
Message: Recurring token has expired
Causes:
- The card has expired
- Token validity period exceeded
- Mandate expired (DebiCheck)
Resolution:
- Request the customer to re-tokenize payment method
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:
- Customer failed 3DS challenge
- 3DS timeout
- Customer cancelled authentication
Resolution:
- The customer should retry with the correct credentials
- Ensure the 3DS page loads correctly
Client Errors
client_not_found
HTTP Status: 404
Message: Client not found
Resolution:
- Verify client ID is correct
- Create a client if it doesn’t exist
duplicate_client
HTTP Status: 409
Message: Client with this email already exists
Causes:
- Attempting to create a client with an existing email
Resolution:
- Search for existing client:
GET /clients/?email=customer@example.com - Use existing client ID
Billing Template Errors
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:
- Check existing subscription status
- Update existing subscription instead of creating new one
subscription_inactive
HTTP Status: 409
Message: Subscription is not active
Causes:
subscription_active: falseon template- Subscription has been cancelled
Webhook Errors
webhook_url_invalid
HTTP Status: 400
Message: Invalid webhook URL
Requirements:
- Must be HTTPS
- Must be publicly accessible
- Must respond within 5 seconds
webhook_url_unreachable
HTTP Status: 400
Message: Webhook URL is not reachable
Resolution:
- Verify URL is publicly accessible
- Check firewall rules
- Ensure the SSL certificate is valid
Rate Limiting Errors
rate_limit_exceeded
HTTP Status: 429
Message: Rate limit exceeded
Response Headers:
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1619740860Resolution:
- Implement exponential backoff
- Cache responses where possible
- Batch operations when available
Retry Logic:
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 Errors
direct_post_expired
HTTP Status: 400
Message: Direct post URL has expired
Causes:
- URL validity is 15 minutes
- URL has already been used
Resolution:
- Create a new purchase for the fresh direct post URL
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 |
Error Handling Best Practices
1. Always Check HTTP Status
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
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
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
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 responseError Code Quick Reference
| 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 |