> ## Documentation Index
> Fetch the complete documentation index at: https://docs.thredfi.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Error Handling

> Understanding error responses and how to handle them

## Standard Error Response

```json theme={null}
{
  "error": "Human-readable error message",
  "error_code": "specific_error_code",
  "details": {
    "field_name": ["Specific validation error"]
  }
}
```

## HTTP Status Codes

| Code    | Meaning      | Description                               | Action Required             |
| ------- | ------------ | ----------------------------------------- | --------------------------- |
| **200** | OK           | Request successful                        | Continue                    |
| **201** | Created      | Resource created successfully             | Continue                    |
| **400** | Bad Request  | Invalid request data or validation errors | Fix request and retry       |
| **401** | Unauthorized | Missing or invalid authentication         | Refresh token and retry     |
| **403** | Forbidden    | Valid auth but insufficient permissions   | Check business\_id scope    |
| **404** | Not Found    | Resource doesn't exist                    | Verify resource ID          |
| **409** | Conflict     | Duplicate resource or state conflict      | Check for existing resource |
| **500** | Server Error | Internal server error                     | Retry 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**:

```json theme={null}
{
  "error": "Validation errors in request data",
  "error_code": "validation_error",
  "details": {
    "email": ["Enter a valid email address."],
    "base_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

```python theme={null}
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://sandbox.thredfi.com/v1/platform/businesses/",
    headers={"Authorization": f"Bearer {token}"}
)
```

### Manual Retry with Backoff

```python theme={null}
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

```python theme={null}
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

```python theme={null}
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 'line_items' in errors:
        print("Invalid invoice line item data, such as an unknown tax_code")

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

### Handling Authentication Errors

```python theme={null}
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

<CardGroup cols={2}>
  <Card title="Authentication" icon="key" href="/authentication">
    Learn about token management
  </Card>

  <Card title="API Reference" icon="book" href="/api-reference">
    See specific endpoint error responses
  </Card>
</CardGroup>
