Table of contents

Error mapping

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:

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"
  }
}

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:

  1. Verify your API key in the dashboard
  2. Ensure you're using the correct environment
  3. 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:

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:"

Validation Errors

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
  • UUID contains invalid characters

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 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:

  1. Verify the purchase ID is correct
  2. 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:

  • due the date has passed with due_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 hold status
  • Authorization has expired

Resolution:

  • Verify the purchase status is hold
  • Create a new purchase with skip_capture: true for 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:

PYTHON

# 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:

  1. Check the enabled payment methods
  2. Verify currency support for the payment method
  3. Review payment_method_whitelist in 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:

  1. List client tokens: GET /clients/{id}/recurring_tokens/
  2. Verify the token ID is correct
  3. 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:

  1. Verify client ID is correct
  2. 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: false on 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:

  1. Verify URL is publicly accessible
  2. Check firewall rules
  3. 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: 1619740860

Resolution:

  1. Implement exponential backoff
  2. Cache responses where possible
  3. Batch operations when available

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 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

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

Error 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|