Skip to main content

Standard Error Response

{
  "error": "Human-readable error message",
  "error_code": "specific_error_code",
  "details": {
    "field_name": ["Specific validation error"]
  }
}

HTTP Status Codes

CodeMeaningDescriptionAction Required
200OKRequest successfulContinue
201CreatedResource created successfullyContinue
400Bad RequestInvalid request data or validation errorsFix request and retry
401UnauthorizedMissing or invalid authenticationRefresh token and retry
403ForbiddenValid auth but insufficient permissionsCheck business_id scope
404Not FoundResource doesn’t existVerify resource ID
409ConflictDuplicate resource or state conflictCheck for existing resource
500Server ErrorInternal server errorRetry with backoff

Common Error Codes

Authentication Errors

authentication_required
  • Status: 401
  • Cause: No authentication credentials provided
  • Solution: Include valid Bearer token in Authorization header
invalid_token
  • Status: 401
  • Cause: Token is invalid, malformed, or expired
  • Solution: Refresh token or re-authenticate

Authorization Errors

permission_denied
  • Status: 403
  • Cause: Token is valid but lacks permission for this resource
  • Solution: Verify business_id matches token scope

Validation Errors

validation_error
  • Status: 400
  • Cause: Request data failed validation rules
  • Solution: Check details field for specific field errors
Example:
{
  "error": "Validation errors in request data",
  "error_code": "validation_error",
  "details": {
    "email": ["Enter a valid email address."],
    "currency": ["Invalid currency code. Must be one of: GBP, EUR, USD"]
  }
}

Resource Errors

business_not_found
  • Status: 404
  • Cause: Business ID doesn’t exist or doesn’t belong to your partner
  • Solution: Verify business_id is correct
invoice_not_found
  • Status: 404
  • Cause: Invoice ID doesn’t exist for this business
  • Solution: Verify invoice_id is correct
duplicate_external_id
  • Status: 409
  • Cause: A resource with this external_id already exists
  • Solution: This is expected for idempotent requests - use the existing resource

Retry Strategy

When to Retry

Retry these:
  • 500-level errors (server errors)
  • Network timeouts
  • Connection errors
Don’t retry these:
  • 400-level errors (client errors)
  • 401 Unauthorized (refresh token instead)
  • 403 Forbidden (fix permissions)
  • 404 Not Found (resource doesn’t exist)

Exponential Backoff

import time
import requests
from requests.adapters import HTTPAdapter
from requests.packages.urllib3.util.retry import Retry

def create_session_with_retries():
    """Create requests session with automatic retry logic."""
    session = requests.Session()

    # Retry on 500, 502, 503, 504
    retry_strategy = Retry(
        total=3,
        status_forcelist=[500, 502, 503, 504],
        allowed_methods=["HEAD", "GET", "OPTIONS"],  # Safe methods only
        backoff_factor=1  # 1s, 2s, 4s
    )

    adapter = HTTPAdapter(max_retries=retry_strategy)
    session.mount("https://", adapter)
    session.mount("http://", adapter)

    return session

# Usage
session = create_session_with_retries()
response = session.get(
    "https://dev-backend.thredfi.com/v1/platform/businesses/",
    headers={"Authorization": f"Bearer {token}"}
)

Manual Retry with Backoff

import time

def api_call_with_retry(url, headers, data=None, max_retries=3):
    """Make API call with exponential backoff retry."""
    for attempt in range(max_retries):
        try:
            if data:
                response = requests.post(url, headers=headers, json=data)
            else:
                response = requests.get(url, headers=headers)

            # Success
            if 200 <= response.status_code < 300:
                return response.json()

            # Client error - don't retry
            if 400 <= response.status_code < 500:
                error = response.json()
                raise APIClientError(
                    f"{error.get('error')} ({error.get('error_code')})"
                )

            # Server error - retry
            if response.status_code >= 500:
                if attempt < max_retries - 1:
                    wait_time = 2 ** attempt  # 1s, 2s, 4s
                    print(f"Server error, retrying in {wait_time}s...")
                    time.sleep(wait_time)
                    continue
                raise APIServerError(f"Server error after {max_retries} attempts")

        except requests.exceptions.ConnectionError as e:
            if attempt < max_retries - 1:
                wait_time = 2 ** attempt
                print(f"Connection error, retrying in {wait_time}s...")
                time.sleep(wait_time)
            else:
                raise

    raise Exception("Max retries exceeded")

Error Logging Best Practices

import logging

logger = logging.getLogger(__name__)

def make_api_call(url, headers, data=None):
    """Make API call with comprehensive error logging."""
    try:
        response = requests.post(url, headers=headers, json=data)

        if response.status_code >= 400:
            error_data = response.json()
            logger.error(
                "API error",
                extra={
                    "url": url,
                    "status_code": response.status_code,
                    "error_code": error_data.get('error_code'),
                    "error_message": error_data.get('error'),
                    "request_data": data
                }
            )

        return response

    except requests.exceptions.RequestException as e:
        logger.exception(
            "API request failed",
            extra={"url": url, "exception": str(e)}
        )
        raise

Common Scenarios

Handling Validation Errors

try:
    response = create_invoice(business_id, invoice_data)
except ValidationError as e:
    # Check specific field errors
    errors = e.details
    if 'customer_id' in errors:
        print("Invalid customer ID")
    if 'currency' in errors:
        print("Invalid currency code")

    # Show all errors to user
    for field, messages in errors.items():
        print(f"{field}: {', '.join(messages)}")

Handling Authentication Errors

def make_authenticated_request(url, business_id):
    try:
        token = get_business_token(business_id)
        response = requests.get(url, headers={"Authorization": f"Bearer {token}"})

        if response.status_code == 401:
            # Token expired - refresh and retry
            token = get_business_token(business_id, force_refresh=True)
            response = requests.get(url, headers={"Authorization": f"Bearer {token}"})

        return response.json()

    except AuthenticationError:
        # Re-authenticate from scratch
        refresh_all_tokens()
        return make_authenticated_request(url, business_id)

Next Steps