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

# Error Handling

> Handle errors and exceptions in the Reducto Python SDK

The Reducto Python SDK raises specific exceptions for different error conditions. This guide covers all exception types and how to handle them.

***

## Exception Hierarchy

```python theme={null}
reducto.ReductoError (base exception)
└── reducto.APIError
    ├── reducto.APIConnectionError (connection errors)
    │   └── reducto.APITimeoutError (timeout errors)
    └── reducto.APIStatusError (HTTP errors)
        ├── reducto.BadRequestError (400)
        ├── reducto.AuthenticationError (401)
        ├── reducto.PermissionDeniedError (403)
        ├── reducto.NotFoundError (404)
        ├── reducto.ConflictError (409)
        ├── reducto.UnprocessableEntityError (422)
        ├── reducto.RateLimitError (429)
        └── reducto.InternalServerError (>=500)
```

***

## Common Exceptions

### APIConnectionError

Raised when the SDK is unable to connect to the API (network issues, timeouts):

```python theme={null}
from reducto import Reducto
import reducto

try:
    result = client.parse.run(input=upload.file_id)
except reducto.APIConnectionError as e:
    print(f"Connection failed: {e}")
    print(e.__cause__)  # underlying exception, likely raised within httpx
```

### AuthenticationError

Raised when API key is missing or invalid (401):

```python theme={null}
from reducto import Reducto
import reducto

try:
    client = Reducto(api_key="invalid_key")
    result = client.parse.run(input=upload.file_id)
except reducto.AuthenticationError as e:
    print(f"Authentication failed: {e}")
    print("Check your API key in Studio")
```

### RateLimitError

Raised when rate limit is exceeded (429):

```python theme={null}
from reducto import Reducto
import reducto
import time

try:
    result = client.parse.run(input=upload.file_id)
except reducto.RateLimitError as e:
    print(f"Rate limit exceeded: {e}")
    # The SDK automatically retries rate limit errors
    # But you can also handle it manually
    time.sleep(1)
    # Retry the request
```

### APIStatusError

Base class for all HTTP status errors. Contains `status_code` and `response` properties:

```python theme={null}
from reducto import Reducto
import reducto

try:
    result = client.parse.run(input=upload.file_id)
except reducto.APIStatusError as e:
    print(f"API error: {e.status_code}")
    print(f"Response: {e.response}")
```

### APITimeoutError

Raised when a request times out:

```python theme={null}
from reducto import Reducto
import reducto

try:
    result = client.parse.run(input=upload.file_id)
except reducto.APITimeoutError as e:
    print(f"Request timed out: {e}")
    print("Consider using async methods for long-running operations")
```

***

## Error Code Reference

| Status Code | Error Type               | Description                          |
| ----------- | ------------------------ | ------------------------------------ |
| 400         | BadRequestError          | Invalid request parameters           |
| 401         | AuthenticationError      | Missing or invalid API key           |
| 403         | PermissionDeniedError    | Insufficient permissions             |
| 404         | NotFoundError            | Resource not found                   |
| 409         | ConflictError            | Request conflicts with current state |
| 422         | UnprocessableEntityError | Request validation failed            |
| 429         | RateLimitError           | Rate limit exceeded                  |
| >=500       | InternalServerError      | Server error                         |
| N/A         | APIConnectionError       | Network/connection error             |
| N/A         | APITimeoutError          | Request timed out                    |

***

## Generic Error Handling

Handle all errors with a generic catch:

```python theme={null}
from reducto import Reducto
import reducto

try:
    result = client.parse.run(input=upload.file_id)
except reducto.APIError as e:
    print(f"Reducto API error: {e}")
    # Log error, notify user, etc.
except Exception as e:
    print(f"Unexpected error: {e}")
```

***

## Error Response Details

API errors include detailed information:

```python theme={null}
from reducto import Reducto
import reducto

try:
    result = client.parse.run(input=upload.file_id)
except reducto.APIStatusError as e:
    print(f"Status code: {e.status_code}")
    print(f"Response: {e.response}")
    # Access response body if needed
    if hasattr(e.response, 'text'):
        print(f"Response body: {e.response.text}")
```

***

## Retry Logic

The SDK automatically retries certain errors by default (2 times with exponential backoff):

* Connection errors (network issues)
* 408 Request Timeout
* 409 Conflict
* 429 Rate Limit
* > \=500 Internal Server errors

You can configure retry behavior:

```python theme={null}
from reducto import Reducto

# Configure the default for all requests
client = Reducto(
    max_retries=0,  # Disable retries (default is 2)
)

# Or configure per-request
client.with_options(max_retries=5).parse.run(
    input=upload.file_id
)
```

### Manual Retry Logic

For more control, implement your own retry logic:

```python theme={null}
import time
from reducto import Reducto
import reducto

def parse_with_retry(client, upload, max_retries=3):
    for attempt in range(max_retries):
        try:
            return client.parse.run(input=upload.file_id)
        except (reducto.RateLimitError, reducto.InternalServerError) as e:
            if attempt == max_retries - 1:
                raise
            
            wait_time = 2 ** attempt  # Exponential backoff
            print(f"Retrying in {wait_time} seconds...")
            time.sleep(wait_time)
    
    raise Exception("Max retries exceeded")

result = parse_with_retry(client, upload)
```

***

## Timeout Handling

Configure timeouts for requests:

```python theme={null}
from reducto import Reducto
import httpx
import reducto

# Configure default timeout (default is 1 hour)
client = Reducto(
    timeout=20.0,  # 20 seconds
)

# More granular control
client = Reducto(
    timeout=httpx.Timeout(60.0, read=5.0, write=10.0, connect=2.0),
)

# Override per-request
try:
    result = client.with_options(timeout=5.0).parse.run(
        input=upload.file_id
    )
except reducto.APITimeoutError as e:
    print(f"Request timed out: {e}")
```

Note that requests that time out are retried twice by default.

### Long Documents and Timeouts

For large documents like lengthy PDFs or spreadsheets with many sheets, we recommend using the [async endpoint](/sdk/python/async). The async endpoint lets you submit a job and poll for results without holding an open connection, which is ideal for documents that take longer to process.

If you need to use the sync endpoint with a custom `timeout`, set `max_retries=0` to disable automatic retries. Otherwise, if your request times out while the server is still processing, the SDK will retry and create additional processing jobs for the same document.

```python theme={null}
from reducto import Reducto

# If using sync with a custom timeout, disable retries
client = Reducto(timeout=300.0, max_retries=0)
```

***

## Best Practices

<CardGroup cols={2}>
  <Card title="Handle Specific Exceptions" icon="code">
    Catch specific exception types rather than generic Exception for better error handling.
  </Card>

  <Card title="Log Errors" icon="file-lines">
    Log errors with context (job\_id, request\_id) for debugging.
  </Card>

  <Card title="Retry Transient Errors" icon="repeat">
    The SDK automatically retries transient errors, but you can customize retry behavior.
  </Card>

  <Card title="Provide User Feedback" icon="user">
    Show meaningful error messages to users, not raw exceptions.
  </Card>

  <Card title="Check Connection Errors" icon="wifi">
    For `APIConnectionError`, check `e.__cause__` to see the underlying exception.
  </Card>

  <Card title="Handle Rate Limits" icon="gauge">
    Rate limit errors are automatically retried, but you can also handle them manually.
  </Card>
</CardGroup>

***

## Complete Example

```python theme={null}
from pathlib import Path
from reducto import Reducto
import reducto

def parse_with_comprehensive_error_handling(client, upload):
    """Parse with comprehensive error handling."""
    try:
        result = client.parse.run(input=upload.file_id)
        return result
    except reducto.AuthenticationError as e:
        print(f"Authentication failed: {e}")
        print("Check your API key in Studio")
        raise  # Don't retry auth errors
    except reducto.RateLimitError as e:
        print(f"Rate limited: {e}")
        # SDK will automatically retry, but you can also handle manually
        raise
    except reducto.APIConnectionError as e:
        print(f"Connection failed: {e}")
        print(f"Underlying error: {e.__cause__}")
        raise
    except reducto.APITimeoutError as e:
        print(f"Request timed out: {e}")
        print("Consider using async methods or increasing timeout")
        raise
    except reducto.APIStatusError as e:
        print(f"API error {e.status_code}: {e}")
        if e.status_code >= 500:
            print("Server error - may be transient")
        raise
    except Exception as e:
        print(f"Unexpected error: {e}")
        raise

# Use the function
client = Reducto()
upload = client.upload(file=Path("document.pdf"))
result = parse_with_comprehensive_error_handling(client, upload)
```

***

## Next Steps

* Learn about [job management](/sdk/python/job-management) for async operations
* Check out the [async client](/sdk/python/async) for concurrent processing
