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

# Error Handling

> Handle API errors with structured error responses and codes

## HTTP Status Codes

| Status Code | Meaning               | Description                                                      |
| ----------- | --------------------- | ---------------------------------------------------------------- |
| `200`       | OK                    | Request succeeded                                                |
| `201`       | Created               | Resource was successfully created                                |
| `202`       | Accepted              | Request accepted for processing (async operations)               |
| `204`       | No Content            | Request succeeded with no response body                          |
| `400`       | Bad Request           | Invalid request data or parameters                               |
| `401`       | Unauthorized          | Invalid API key (rejected by backend validation)                 |
| `403`       | Forbidden             | Missing API key, empty Bearer token, or insufficient permissions |
| `404`       | Not Found             | Resource doesn't exist                                           |
| `412`       | Precondition Failed   | Results not yet complete (poll again)                            |
| `429`       | Too Many Requests     | Rate limit exceeded (check `Retry-After` header)                 |
| `500`       | Internal Server Error | Unexpected server error                                          |
| `502`       | Bad Gateway           | Upstream service error                                           |
| `503`       | Service Unavailable   | Service temporarily unavailable                                  |
| `504`       | Gateway Timeout       | Backend did not respond in time                                  |

## Error Response Format

All error responses follow a consistent JSON structure:

```json theme={null}
{
  "error": "Brief, human-readable error description",
  "detail": "Detailed explanation of what went wrong",
  "error_code": "MACHINE_READABLE_ERROR_CODE",
  "retryable": false,
  "request_id": "a1b2c3d4e5f67890abcdef1234567890"
}
```

| Field        | Description                                                                |
| ------------ | -------------------------------------------------------------------------- |
| `error`      | Short, human-readable summary                                              |
| `detail`     | Detailed explanation for debugging (may be an array for validation errors) |
| `error_code` | Machine-readable code for programmatic handling                            |
| `retryable`  | Whether the client should retry this request                               |
| `request_id` | Unique request identifier for debugging and support                        |

## Error Codes Reference

| Error Code            | HTTP Status | Retryable | Description                                                      |
| --------------------- | ----------- | --------- | ---------------------------------------------------------------- |
| `AUTH_FAILED`         | 401         | No        | Invalid API key (rejected by backend)                            |
| `ACCESS_DENIED`       | 403         | No        | Missing API key, empty Bearer token, or insufficient permissions |
| `VALIDATION_ERROR`    | 400         | No        | Request validation failed                                        |
| `INVALID_JSON`        | 400         | No        | Malformed JSON in request body                                   |
| `NOT_FOUND`           | 404         | No        | Resource not found                                               |
| `PRECONDITION_FAILED` | 412         | Yes       | Results not yet complete (poll again)                            |
| `RATE_LIMITED`        | 429         | Yes       | Too many requests (check `Retry-After` header)                   |
| `INTERNAL_ERROR`      | 500/502/503 | Yes       | Server error                                                     |
| `GATEWAY_TIMEOUT`     | 504         | Yes       | Backend did not respond in time                                  |

### Authentication Errors

**AUTH\_FAILED** (401) — Invalid API key

```json theme={null}
{
  "error": "Authentication failed",
  "detail": "Invalid or missing authentication credentials.",
  "error_code": "AUTH_FAILED",
  "retryable": false,
  "request_id": "a1b2c3d4e5f67890abcdef1234567890"
}
```

**ACCESS\_DENIED** (403) — Missing API key or insufficient permissions

```json theme={null}
{
  "error": "Access denied",
  "detail": "API key is required. Use 'Authorization: Bearer <key>'.",
  "error_code": "ACCESS_DENIED",
  "retryable": false,
  "request_id": "a1b2c3d4e5f67890abcdef1234567890"
}
```

### Validation Errors

**VALIDATION\_ERROR** (400) — The `detail` field is an array of validation error objects:

```json theme={null}
{
  "error": "Validation failed",
  "detail": [
    {"type": "missing", "loc": ["body", "name"], "msg": "Field required"},
    {"type": "missing", "loc": ["body", "fields"], "msg": "Field required"}
  ],
  "error_code": "VALIDATION_ERROR",
  "retryable": false,
  "request_id": "a1b2c3d4e5f67890abcdef1234567890"
}
```

**INVALID\_JSON** (400)

```json theme={null}
{
  "error": "Invalid JSON format",
  "detail": "The request body contains invalid JSON. Please check your JSON syntax.",
  "error_code": "INVALID_JSON",
  "retryable": false,
  "request_id": "a1b2c3d4e5f67890abcdef1234567890"
}
```

### Resource Errors

**NOT\_FOUND** (404)

```json theme={null}
{
  "error": "Resource not found",
  "detail": "The requested resource could not be found.",
  "error_code": "NOT_FOUND",
  "retryable": false,
  "request_id": "a1b2c3d4e5f67890abcdef1234567890"
}
```

### Processing Status

**PRECONDITION\_FAILED** (412) — Results not yet available.

The `retryable` flag indicates whether to continue polling:

* `retryable: true` — processing is still running. Poll again with backoff.
* `retryable: false` — processing reached a terminal failure (`error` or `cancelled`). Stop polling.

```json theme={null}
{
  "error": "Extraction not yet available",
  "detail": "Extraction status is 'processing'. Results are available when status is 'processed'.",
  "error_code": "PRECONDITION_FAILED",
  "retryable": true,
  "request_id": "a1b2c3d4e5f67890abcdef1234567890"
}
```

### Rate Limiting

**RATE\_LIMITED** (429) — Response includes `Retry-After` header

```json theme={null}
{
  "error": "Rate limited",
  "detail": "Rate limit exceeded. Try again in 12s.",
  "error_code": "RATE_LIMITED",
  "retryable": true,
  "request_id": "a1b2c3d4e5f67890abcdef1234567890"
}
```

<Info>
  Rate-limited responses include a `Retry-After` header indicating how many seconds to wait before retrying. All successful responses include `x-ratelimit-limit`, `x-ratelimit-remaining`, and `x-ratelimit-reset` headers showing the limit for the tier that applies to that endpoint (extraction or general). The `Retry-After` header is only present on 429 responses.
</Info>

### Server Errors

**INTERNAL\_ERROR** (500/502/503)

```json theme={null}
{
  "error": "Internal server error",
  "detail": "An unexpected error occurred while processing your request. Please try again or contact support.",
  "error_code": "INTERNAL_ERROR",
  "retryable": true,
  "reference_id": "err_1234",
  "request_id": "a1b2c3d4e5f67890abcdef1234567890"
}
```

**GATEWAY\_TIMEOUT** (504)

```json theme={null}
{
  "error": "Gateway timeout",
  "detail": "The backend did not respond in time. Please try again.",
  "error_code": "GATEWAY_TIMEOUT",
  "retryable": true,
  "request_id": "a1b2c3d4e5f67890abcdef1234567890"
}
```

<Info>
  Server errors (500) include a `reference_id` field. Use this ID along with the `request_id` when contacting support to help identify and resolve the specific issue.
</Info>

## Error Handling Best Practices

### 1. Check HTTP Status Codes

```python theme={null}
import requests

response = requests.get(
    "https://api.anyformat.ai/v2/workflows/",
    headers={"Authorization": "Bearer your-api-key"}
)

if response.status_code == 200:
    workflows = response.json()
elif response.status_code == 401:
    print("Authentication failed. Check your API key.")
elif response.status_code == 403:
    print("Access denied. API key may be missing.")
elif response.status_code == 404:
    print("Resource not found.")
elif response.status_code == 412:
    print("Results not ready yet. Retry after a delay.")
elif response.status_code == 429:
    retry_after = response.headers.get("Retry-After", "5")
    print(f"Rate limited. Retry after {retry_after}s.")
elif response.status_code >= 500:
    print("Server error. Retry with backoff.")
else:
    error = response.json()
    print(f"Error: {error.get('detail')}")
```

### 2. Use Error Codes for Logic

```python theme={null}
def handle_api_error(response):
    if response.status_code >= 400:
        error_data = response.json()
        error_code = error_data.get('error_code')

        if error_code == 'AUTH_FAILED':
            refresh_authentication()
        elif error_code == 'ACCESS_DENIED':
            print("Missing or invalid API key.")
        elif error_code == 'VALIDATION_ERROR':
            show_validation_error(error_data.get('detail'))
        elif error_code == 'NOT_FOUND':
            handle_missing_resource()
        elif error_code == 'PRECONDITION_FAILED':
            # Still processing — retry
            return retry_after_delay()
        elif error_code == 'RATE_LIMITED':
            retry_after = int(response.headers.get('Retry-After', 5))
            time.sleep(retry_after)
            return retry_request()
        elif error_code == 'INTERNAL_ERROR':
            retry_with_backoff()
        elif error_code == 'GATEWAY_TIMEOUT':
            retry_with_backoff()
        else:
            log_unexpected_error(error_data)
```

### 3. Implement Retry Logic

For retryable errors (5xx, 429, 412), implement exponential backoff:

```python theme={null}
import time
import random

def api_request_with_retry(url, headers, max_retries=3):
    for attempt in range(max_retries + 1):
        try:
            response = requests.get(url, headers=headers)

            # Don't retry non-retryable client errors
            if 400 <= response.status_code < 500 and response.status_code not in (412, 429):
                return response

            # Handle rate limiting
            if response.status_code == 429:
                delay = int(response.headers.get('Retry-After', 5))
                time.sleep(delay)
                continue

            # Handle results polling
            if response.status_code == 412 and attempt < max_retries:
                time.sleep(5)
                continue

            # Retry server errors (5xx)
            if response.status_code >= 500 and attempt < max_retries:
                delay = (2 ** attempt) + random.uniform(0, 1)
                time.sleep(delay)
                continue

            return response

        except requests.exceptions.RequestException as e:
            if attempt < max_retries:
                delay = (2 ** attempt) + random.uniform(0, 1)
                time.sleep(delay)
                continue
            raise e
```

### 4. Validate Before Sending

```python theme={null}
def create_workflow(name, description, fields):
    # Validate required fields locally first
    if not name:
        raise ValueError("Workflow name is required")
    if not fields or len(fields) == 0:
        raise ValueError("At least one field is required")

    for field in fields:
        if not field.get('name'):
            raise ValueError("Field name is required")
        if not field.get('data_type'):
            raise ValueError("Field data_type is required")

    # Make API request
    response = requests.post(
        "https://api.anyformat.ai/v2/workflows/",
        headers={"Authorization": "Bearer your-api-key", "Content-Type": "application/json"},
        json={"name": name, "description": description, "fields": fields}
    )

    return handle_response(response)
```

### 5. Log Errors for Debugging

```python theme={null}
import logging

def log_api_error(response, context=""):
    try:
        error_data = response.json()
        error_code = error_data.get('error_code', 'UNKNOWN')
        request_id = error_data.get('request_id', 'N/A')
        reference_id = error_data.get('reference_id', 'N/A')

        logging.error(
            f"API Error {response.status_code}: {error_code} - "
            f"{error_data.get('detail', 'No detail')} "
            f"[Context: {context}] [Request: {request_id}] [Reference: {reference_id}]"
        )
    except ValueError:
        logging.error(f"API Error {response.status_code}: {response.text}")
```

## Testing Error Scenarios

When building your integration, test these common scenarios:

| Scenario                | How to Test                                                                                         |
| ----------------------- | --------------------------------------------------------------------------------------------------- |
| Missing API key         | Omit the `Authorization` header                                                                     |
| Invalid API key         | Use a fake or expired key                                                                           |
| Missing required fields | Create workflow without `name` or `fields`                                                          |
| Invalid JSON            | Send malformed JSON in request body                                                                 |
| Nonexistent resource    | Request a workflow or file ID that doesn't exist                                                    |
| Results not ready       | Poll `GET /v2/workflows/{workflow_id}/files/{id}/results/` before processing completes (expect 412) |
| Rate limit exceeded     | Send rapid requests to trigger 429 with `Retry-After` header                                        |
| Server errors           | Test retry logic (mock 500 responses)                                                               |

## Framework-Level Errors

<Note>
  While most errors use the structured `{ "error", "detail", "error_code", "retryable", "request_id" }` format, some framework-level responses (e.g., unknown URL path) may return a simpler format:

  ```json theme={null}
  { "detail": "Not found." }
  ```
</Note>
