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

Exception Hierarchy

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):
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):
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):
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:
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:
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 CodeError TypeDescription
400BadRequestErrorInvalid request parameters
401AuthenticationErrorMissing or invalid API key
403PermissionDeniedErrorInsufficient permissions
404NotFoundErrorResource not found
409ConflictErrorRequest conflicts with current state
422UnprocessableEntityErrorRequest validation failed
429RateLimitErrorRate limit exceeded
>=500InternalServerErrorServer error
N/AAPIConnectionErrorNetwork/connection error
N/AAPITimeoutErrorRequest timed out

Generic Error Handling

Handle all errors with a generic catch:
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:
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:
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:
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:
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.

Best Practices

Handle Specific Exceptions

Catch specific exception types rather than generic Exception for better error handling.

Log Errors

Log errors with context (job_id, request_id) for debugging.

Retry Transient Errors

The SDK automatically retries transient errors, but you can customize retry behavior.

Provide User Feedback

Show meaningful error messages to users, not raw exceptions.

Check Connection Errors

For APIConnectionError, check e.__cause__ to see the underlying exception.

Handle Rate Limits

Rate limit errors are automatically retried, but you can also handle them manually.

Complete Example

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