6 min read

Error Handling

Best practices for handling DocuRift API errors

Error Response Format

All DocuRift API errors follow a consistent JSON structure, making it easy to handle errors programmatically:

{
  "success": false,
  "error": {
    "code": "ERROR_CODE",
    "message": "Human-readable error message",
    "details": {
      "field": "Additional context about the error"
    }
  },
  "requestId": "req_abc123def456"
}

Response Fields

| Field | Type | Description | |-------|------|-------------| | success | boolean | Always false for error responses | | error.code | string | Machine-readable error code (see Error Codes) | | error.message | string | Human-readable description | | error.details | object | Additional context (optional) | | requestId | string | Unique identifier for support requests |


HTTP Status Codes

DocuRift uses standard HTTP status codes to indicate the general category of error:

| Status | Meaning | Common Causes | |--------|---------|---------------| | 400 | Bad Request | Invalid parameters, malformed JSON, unsupported file type | | 401 | Unauthorized | Missing or invalid API key | | 402 | Payment Required | Insufficient credits or free tier exhausted | | 403 | Forbidden | Access denied, IP blocked, or resource not owned | | 404 | Not Found | Document or resource doesn't exist | | 413 | Payload Too Large | File exceeds 50MB limit | | 429 | Too Many Requests | Rate limit or concurrent request limit exceeded | | 500 | Internal Error | Server-side processing error |


Retry Strategy

For transient errors (5xx, 429), implement exponential backoff with jitter:

| Parameter | Value | |-----------|-------| | Initial delay | 1 second | | Maximum delay | 60 seconds | | Maximum retries | 5 | | Backoff multiplier | 2 | | Jitter | 0-500ms random |

Retryable Errors

Only retry these error codes:

  • 429 - Rate limit exceeded
  • 500 - Internal server error
  • 502 - Bad gateway
  • 503 - Service unavailable
  • 504 - Gateway timeout

Do not retry 4xx errors (except 429) as they indicate client-side issues.


Error Handling Examples

Python

import time
import random
import requests
from typing import Optional

class DocuRiftError(Exception):
    def __init__(self, code: str, message: str, status: int, request_id: str):
        self.code = code
        self.message = message
        self.status = status
        self.request_id = request_id
        super().__init__(f"{code}: {message}")

def process_document(file_path: str, api_key: str, max_retries: int = 5) -> dict:
    """Process a document with automatic retry logic."""

    base_delay = 1
    max_delay = 60

    for attempt in range(max_retries + 1):
        try:
            with open(file_path, 'rb') as f:
                response = requests.post(
                    'https://api.docurift.com/v1/documents/process',
                    headers={'X-API-Key': api_key},
                    files={'file': f},
                    data={'documentType': 'invoice'}
                )

            data = response.json()

            if response.ok:
                return data

            # Extract error details
            error = data.get('error', {})
            request_id = data.get('requestId', 'unknown')

            # Check if error is retryable
            if response.status_code in [429, 500, 502, 503, 504]:
                if attempt < max_retries:
                    delay = min(base_delay * (2 ** attempt), max_delay)
                    jitter = random.uniform(0, 0.5)
                    time.sleep(delay + jitter)
                    continue

            # Non-retryable error or max retries exceeded
            raise DocuRiftError(
                code=error.get('code', 'UNKNOWN_ERROR'),
                message=error.get('message', 'Unknown error'),
                status=response.status_code,
                request_id=request_id
            )

        except requests.RequestException as e:
            if attempt < max_retries:
                delay = min(base_delay * (2 ** attempt), max_delay)
                time.sleep(delay)
                continue
            raise

    raise DocuRiftError('MAX_RETRIES', 'Maximum retries exceeded', 0, 'none')

# Usage
try:
    result = process_document('invoice.pdf', 'your-api-key')
    print(f"Extracted data: {result['data']}")
except DocuRiftError as e:
    print(f"API Error [{e.code}]: {e.message}")
    print(f"Request ID for support: {e.request_id}")
except Exception as e:
    print(f"Unexpected error: {e}")

JavaScript / TypeScript

interface DocuRiftError {
  code: string;
  message: string;
  details?: Record<string, unknown>;
}

interface DocuRiftResponse<T> {
  success: boolean;
  data?: T;
  error?: DocuRiftError;
  requestId: string;
}

class DocuRiftAPIError extends Error {
  constructor(
    public code: string,
    public message: string,
    public status: number,
    public requestId: string,
    public details?: Record<string, unknown>
  ) {
    super(`${code}: ${message}`);
    this.name = 'DocuRiftAPIError';
  }
}

async function processDocument(
  file: File,
  apiKey: string,
  maxRetries = 5
): Promise<DocuRiftResponse<unknown>> {
  const baseDelay = 1000;
  const maxDelay = 60000;
  const retryableCodes = [429, 500, 502, 503, 504];

  for (let attempt = 0; attempt <= maxRetries; attempt++) {
    try {
      const formData = new FormData();
      formData.append('file', file);
      formData.append('documentType', 'invoice');

      const response = await fetch(
        'https://api.docurift.com/v1/documents/process',
        {
          method: 'POST',
          headers: { 'X-API-Key': apiKey },
          body: formData,
        }
      );

      const data: DocuRiftResponse<unknown> = await response.json();

      if (response.ok) {
        return data;
      }

      // Check if we should retry
      if (retryableCodes.includes(response.status) && attempt < maxRetries) {
        const delay = Math.min(baseDelay * Math.pow(2, attempt), maxDelay);
        const jitter = Math.random() * 500;
        await new Promise((resolve) => setTimeout(resolve, delay + jitter));
        continue;
      }

      // Throw non-retryable error
      throw new DocuRiftAPIError(
        data.error?.code || 'UNKNOWN_ERROR',
        data.error?.message || 'Unknown error occurred',
        response.status,
        data.requestId,
        data.error?.details
      );
    } catch (error) {
      if (error instanceof DocuRiftAPIError) {
        throw error;
      }

      // Network error - retry if attempts remain
      if (attempt < maxRetries) {
        const delay = Math.min(baseDelay * Math.pow(2, attempt), maxDelay);
        await new Promise((resolve) => setTimeout(resolve, delay));
        continue;
      }

      throw error;
    }
  }

  throw new DocuRiftAPIError(
    'MAX_RETRIES',
    'Maximum retries exceeded',
    0,
    'none'
  );
}

// Usage
async function handleDocument(file: File) {
  try {
    const result = await processDocument(file, 'your-api-key');
    console.log('Extracted data:', result.data);
  } catch (error) {
    if (error instanceof DocuRiftAPIError) {
      console.error(`API Error [${error.code}]: ${error.message}`);
      console.error(`Request ID for support: ${error.requestId}`);

      // Handle specific errors
      switch (error.code) {
        case 'INSUFFICIENT_CREDITS':
          // Redirect to billing page
          break;
        case 'RATE_LIMIT_EXCEEDED':
          // Show rate limit message
          break;
        default:
          // Show generic error
          break;
      }
    } else {
      console.error('Unexpected error:', error);
    }
  }
}

Logging Best Practices

Always Include Request ID

The requestId is essential for debugging. Include it in all error logs:

import logging

logger = logging.getLogger('docurift')

try:
    result = process_document('invoice.pdf', api_key)
except DocuRiftError as e:
    logger.error(
        "DocuRift API error",
        extra={
            'error_code': e.code,
            'error_message': e.message,
            'request_id': e.request_id,
            'http_status': e.status
        }
    )

Structured Logging Format

{
  "timestamp": "2024-01-15T10:30:00Z",
  "level": "error",
  "service": "document-processor",
  "event": "docurift_api_error",
  "error_code": "PROCESSING_FAILED",
  "error_message": "OCR processing failed for document",
  "request_id": "req_abc123def456",
  "http_status": 500,
  "file_name": "invoice.pdf",
  "file_size": 2048576
}

Contacting Support

When contacting support, always include:

  1. Request ID - Found in every API response
  2. Timestamp - When the error occurred
  3. Error code and message - The full error response
  4. File details - Type, size, and anonymized sample if possible
  5. Steps to reproduce - What API call triggered the error

Common Error Patterns

Handling Credit Exhaustion

try:
    result = process_document('invoice.pdf', api_key)
except DocuRiftError as e:
    if e.code == 'INSUFFICIENT_CREDITS':
        # Check current balance
        balance = get_credit_balance(api_key)
        logger.warning(f"Low credits: {balance['remaining']} remaining")
        # Notify admin or auto-purchase
        notify_low_credits(balance)
    raise

Handling Rate Limits Gracefully

from datetime import datetime, timedelta

class RateLimitTracker:
    def __init__(self):
        self.reset_time = None

    def handle_rate_limit(self, error: DocuRiftError):
        if 'retryAfter' in error.details:
            self.reset_time = datetime.now() + timedelta(
                seconds=error.details['retryAfter']
            )
        else:
            self.reset_time = datetime.now() + timedelta(seconds=60)

    def can_make_request(self) -> bool:
        if self.reset_time is None:
            return True
        return datetime.now() >= self.reset_time

Handling Concurrent Limits

import asyncio
from asyncio import Semaphore

# Limit to 5 concurrent requests (matching API limit)
semaphore = Semaphore(5)

async def process_with_limit(file_path: str, api_key: str):
    async with semaphore:
        return await async_process_document(file_path, api_key)

# Process multiple documents while respecting limits
async def batch_process(files: list[str], api_key: str):
    tasks = [process_with_limit(f, api_key) for f in files]
    results = await asyncio.gather(*tasks, return_exceptions=True)
    return results