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

# Error Handling

> Standardised error codes, response formats, and recommended handling strategies

## Overview

The Veridox API uses conventional HTTP status codes and structured error responses to indicate the success or failure of requests. This guide covers common error patterns, recommended retry strategies, and best practices for robust error handling.

<Info>
  **Consistent Error Format**: All error responses follow a standardised schema with `error_code`, `error_message`, and optional `error_details` fields.
</Info>

## Error Response Format

All errors follow this consistent structure:

```json theme={null}
{
  "error_code": "string",
  "error_message": "string",
  "error_details": {
    // Additional context (optional)
  }
}
```

### Error Response Fields

| Field           | Type   | Description                                                       |
| --------------- | ------ | ----------------------------------------------------------------- |
| `error_code`    | string | Machine-readable error identifier for programmatic handling       |
| `error_message` | string | Human-readable error description                                  |
| `error_details` | object | Optional additional context (validation errors, retry info, etc.) |

## HTTP Status Codes

The API uses standard HTTP status codes to indicate request outcomes:

| Status Code | Meaning               | Common Causes                                  |
| ----------- | --------------------- | ---------------------------------------------- |
| **200**     | OK                    | Request succeeded                              |
| **201**     | Created               | Resource created successfully                  |
| **400**     | Bad Request           | Malformed request body or invalid parameters   |
| **401**     | Unauthorised          | Missing, invalid, or expired API key           |
| **403**     | Forbidden             | Valid credentials but insufficient permissions |
| **404**     | Not Found             | Resource does not exist                        |
| **409**     | Conflict              | Request conflicts with current resource state  |
| **429**     | Too Many Requests     | Rate limit exceeded                            |
| **500**     | Internal Server Error | Server-side error occurred                     |
| **503**     | Service Unavailable   | Temporary server overload or maintenance       |

## Common Error Types

### Authentication Errors (401)

Authentication failures occur when API key validation fails.

#### Invalid API Key

```json theme={null}
{
  "error_code": "request.authentication.api-key.invalid",
  "error_message": "The provided API key is invalid."
}
```

**Causes**:

* API key typo or formatting error
* API key deleted or revoked
* Wrong environment (staging vs production key)

**Resolution**:
<Check>Verify API key format and value</Check>
<Check>Regenerate API key if compromised</Check>
<Check>Ensure using correct environment key</Check>

#### Expired API Key

```json theme={null}
{
  "error_code": "request.authentication.api-key.expired",
  "error_message": "The API key has expired."
}
```

**Causes**:

* API key reached expiration date
* Organisation subscription expired

**Resolution**:
<Check>Generate new API key from dashboard</Check>
<Check>Update application configuration</Check>
<Check>Verify organisation subscription status</Check>

#### Invalid Membership

```json theme={null}
{
  "error_code": "request.authentication.api-key.membership.invalid",
  "error_message": "You do not have a valid membership associated with this API key."
}
```

**Causes**:

* User removed from organisation
* Organisation membership revoked
* API key associated with deleted user

**Resolution**:
<Check>Verify organisation membership status</Check>
<Check>Contact organisation owner</Check>
<Check>Generate new API key if membership restored</Check>

### Validation Errors (400)

Validation errors provide field-level feedback when request data is malformed or invalid.

#### Request Body Validation

```json theme={null}
{
  "error_code": "client.bad-request",
  "error_message": "The request body was malformed or contained invalid data.",
  "error_details": {
    "validation_errors": [
      {
        "field": "label",
        "message": "Invalid input: expected string, received undefined"
      },
      {
        "field": "expires_at",
        "message": "Expiration date must be in the future"
      }
    ]
  }
}
```

**Handling Strategy**:
<Check>Parse `validation_errors` array for field-specific issues</Check>
<Check>Display errors next to relevant form fields</Check>
<Check>Validate input client-side before submission</Check>
<Check>Log validation patterns to improve UX</Check>

**Example Handler**:

```javascript theme={null}
function handleValidationError(error) {
  if (error.error_details?.validation_errors) {
    const fieldErrors = {};

    error.error_details.validation_errors.forEach(({ field, message }) => {
      fieldErrors[field] = message;
    });

    return {
      type: 'validation',
      fields: fieldErrors
    };
  }

  return {
    type: 'generic',
    message: error.error_message
  };
}
```

### Rate Limiting Errors (429)

Rate limits protect API infrastructure and ensure fair usage.

```json theme={null}
{
  "error_code": "request.rate-limit",
  "error_message": "Too many requests. Please try again later.",
  "error_details": {
    "retry_after": 60
  }
}
```

**Rate Limits by Operation**:

| Operation               | Limit        | Scope       | Window     |
| ----------------------- | ------------ | ----------- | ---------- |
| Create case             | 10 requests  | Per IP      | Per minute |
| Create document request | 10 requests  | Per IP      | Per minute |
| File analysis retrieval | 300 requests | Per user    | Per hour   |
| List operations         | 30 requests  | Per IP      | Per minute |
| Risk confirmations      | 10 requests  | Per user    | Per minute |
| Progress polling        | 600 requests | Per API key | Per hour   |

**Handling Strategy**:
<Check>Implement exponential backoff retry logic</Check>
<Check>Respect `retry_after` header when provided</Check>
<Check>Use Server-Sent Events instead of polling where possible</Check>
<Check>Cache responses to reduce redundant requests</Check>

**Exponential Backoff Implementation**:

```javascript theme={null}
async function retryWithBackoff(fn, maxRetries = 5) {
  let retries = 0;

  while (retries < maxRetries) {
    try {
      return await fn();
    } catch (error) {
      if (error.status === 429) {
        retries++;

        // Extract retry_after or calculate exponential backoff
        const retryAfter = error.error_details?.retry_after ||
                          Math.min(1000 * Math.pow(2, retries), 60000);

        console.log(`Rate limited. Retrying after ${retryAfter}ms (attempt ${retries}/${maxRetries})`);

        await new Promise(resolve => setTimeout(resolve, retryAfter));
      } else {
        throw error;
      }
    }
  }

  throw new Error('Max retries exceeded');
}

// Usage
const caseData = await retryWithBackoff(() =>
  fetch('https://api.{region}.veridox.ai/cases', {
    method: 'POST',
    headers: { 'X-API-Key': apiKey },
    body: JSON.stringify({ label: 'Customer #123', file_labels: ['id.pdf'] })
  }).then(res => res.json())
);
```

### Resource Errors (404, 409)

#### Resource Not Found (404)

```json theme={null}
{
  "error_code": "resource.not-found",
  "error_message": "The requested resource was not found."
}
```

**Common Causes**:

* Case ID, file ID, or request ID doesn't exist
* Resource deleted before request
* Typo in resource identifier

**Handling Strategy**:
<Check>Validate UUIDs before making requests</Check>
<Check>Implement graceful fallback for missing resources</Check>
<Check>Cache resource existence checks</Check>

#### Resource Conflict (409)

```json theme={null}
{
  "error_code": "resource.conflict.case-locked",
  "error_message": "Cannot add files to a locked case. The case is already processing."
}
```

**Common Scenarios**:

* Attempting to upload files to locked case
* Duplicate operation detected
* State transition not allowed

**Handling Strategy**:
<Check>Check resource state before operations</Check>
<Check>Create new case if existing case is locked</Check>
<Check>Implement idempotency where possible</Check>

### Server Errors (500, 503)

#### Internal Server Error (500)

```json theme={null}
{
  "error_code": "server.internal-error",
  "error_message": "An unexpected error occurred. Please try again later."
}
```

**Causes**:

* Unexpected server-side exception
* Database connectivity issues
* Processing pipeline failures

**Handling Strategy**:
<Check>Retry after short delay (2-5 seconds)</Check>
<Check>Log full error context for debugging</Check>
<Check>Contact support if error persists</Check>
<Check>Implement fallback or degraded mode</Check>

#### Service Unavailable (503)

```json theme={null}
{
  "error_code": "server.unavailable",
  "error_message": "Service temporarily unavailable. Please retry after indicated time.",
  "error_details": {
    "retry_after": 120
  }
}
```

**Causes**:

* Scheduled maintenance
* Temporary overload
* Infrastructure scaling

**Handling Strategy**:
<Check>Display maintenance message to users</Check>
<Check>Retry after specified delay</Check>
<Check>Queue operations for later processing</Check>

## Error Handling Best Practices

### Comprehensive Error Handler

```javascript theme={null}
class VeridoxAPIClient {
  constructor(apiKey, baseUrl = 'https://api.{region}.veridox.ai') {
    this.apiKey = apiKey;
    this.baseUrl = baseUrl;
  }

  async request(endpoint, options = {}) {
    const url = `${this.baseUrl}${endpoint}`;

    try {
      const response = await fetch(url, {
        ...options,
        headers: {
          'X-API-Key': this.apiKey,
          'Content-Type': 'application/json',
          ...options.headers
        }
      });

      // Parse response
      const data = await response.json();

      // Handle success
      if (response.ok) {
        return data;
      }

      // Handle errors
      return this.handleError(response.status, data);

    } catch (error) {
      // Network errors
      if (error.name === 'TypeError' || error.name === 'NetworkError') {
        throw {
          type: 'network',
          message: 'Network connection failed. Please check your internet connection.',
          originalError: error
        };
      }

      throw error;
    }
  }

  handleError(status, errorData) {
    const error = {
      status,
      code: errorData.error_code,
      message: errorData.error_message,
      details: errorData.error_details
    };

    switch (status) {
      case 400:
        return this.handleValidationError(error);
      case 401:
        return this.handleAuthError(error);
      case 404:
        return this.handleNotFoundError(error);
      case 409:
        return this.handleConflictError(error);
      case 429:
        return this.handleRateLimitError(error);
      case 500:
      case 503:
        return this.handleServerError(error);
      default:
        throw error;
    }
  }

  handleValidationError(error) {
    throw {
      type: 'validation',
      message: 'Request validation failed',
      fields: error.details?.validation_errors || [],
      originalError: error
    };
  }

  handleAuthError(error) {
    throw {
      type: 'authentication',
      message: 'Authentication failed. Please check your API key.',
      code: error.code,
      originalError: error
    };
  }

  handleNotFoundError(error) {
    throw {
      type: 'not_found',
      message: 'Resource not found',
      originalError: error
    };
  }

  handleConflictError(error) {
    throw {
      type: 'conflict',
      message: error.message,
      code: error.code,
      originalError: error
    };
  }

  handleRateLimitError(error) {
    const retryAfter = error.details?.retry_after || 60;

    throw {
      type: 'rate_limit',
      message: `Rate limit exceeded. Retry after ${retryAfter} seconds.`,
      retryAfter,
      originalError: error
    };
  }

  handleServerError(error) {
    throw {
      type: 'server_error',
      message: 'Server error occurred. Please try again later.',
      retryable: true,
      originalError: error
    };
  }
}

// Usage with error handling
const client = new VeridoxAPIClient('vdx_your_api_key');

try {
  const caseData = await client.request('/cases', {
    method: 'POST',
    body: JSON.stringify({
      label: 'Customer Verification #12345',
      file_labels: ['passport.pdf', 'utility_bill.pdf']
    })
  });

  console.log('Case created:', caseData.case_id);

} catch (error) {
  switch (error.type) {
    case 'validation':
      console.error('Validation failed:', error.fields);
      // Display field-specific errors in UI
      break;

    case 'authentication':
      console.error('Auth failed:', error.message);
      // Redirect to login or API key configuration
      break;

    case 'rate_limit':
      console.error('Rate limited:', error.message);
      // Wait and retry
      await new Promise(r => setTimeout(r, error.retryAfter * 1000));
      break;

    case 'server_error':
      console.error('Server error:', error.message);
      // Retry with exponential backoff
      break;

    case 'network':
      console.error('Network error:', error.message);
      // Display offline message
      break;

    default:
      console.error('Unexpected error:', error);
  }
}
```

### React Error Boundary

```javascript theme={null}
import { Component } from 'react';

class APIErrorBoundary extends Component {
  state = { hasError: false, error: null };

  static getDerivedStateFromError(error) {
    return { hasError: true, error };
  }

  render() {
    if (this.state.hasError) {
      const { error } = this.state;

      if (error.type === 'authentication') {
        return (
          <div className="error-auth">
            <h2>Authentication Required</h2>
            <p>Please check your API key configuration.</p>
            <button onClick={() => window.location.reload()}>
              Retry
            </button>
          </div>
        );
      }

      if (error.type === 'rate_limit') {
        return (
          <div className="error-rate-limit">
            <h2>Rate Limit Exceeded</h2>
            <p>Please wait {error.retryAfter} seconds before retrying.</p>
          </div>
        );
      }

      return (
        <div className="error-generic">
          <h2>Something went wrong</h2>
          <p>{error.message || 'An unexpected error occurred'}</p>
          <button onClick={() => this.setState({ hasError: false, error: null })}>
            Try Again
          </button>
        </div>
      );
    }

    return this.props.children;
  }
}
```

## Error Handling Guidelines

### Design Principles

<Check>**Fail gracefully** - Provide clear error messages and recovery paths</Check>
<Check>**Be specific** - Use `error_code` for programmatic handling, not just status codes</Check>
<Check>**Retry intelligently** - Implement exponential backoff for transient errors</Check>
<Check>**Log comprehensively** - Capture full error context for debugging</Check>

### User Experience

<Check>**User-friendly messages** - Translate technical errors into actionable guidance</Check>
<Check>**Show progress** - Display retry countdown or progress indicators</Check>
<Check>**Provide alternatives** - Offer fallback options when primary action fails</Check>
<Check>**Don't expose internals** - Hide technical details from end users</Check>

### Monitoring and Debugging

<Check>**Track error rates** - Monitor error types and frequencies</Check>
<Check>**Set up alerts** - Get notified of unusual error patterns</Check>
<Check>**Log context** - Include request IDs, timestamps, and user actions</Check>
<Check>**Correlate errors** - Group related errors for root cause analysis</Check>

### Security Considerations

<Check>**Don't leak data** - Avoid exposing sensitive information in error messages</Check>
<Check>**Rate limit retries** - Prevent abuse through excessive retry attempts</Check>
<Check>**Validate client-side** - Reduce unnecessary API calls with validation</Check>
<Check>**Sanitise error logs** - Remove PII before logging error details</Check>

## Common Troubleshooting

### API Key Issues

**Symptom**: Consistent 401 errors

**Checklist**:

* Verify API key format (`vdx_` prefix)
* Check environment (staging vs production)
* Confirm organisation membership
* Test with newly generated key

### Rate Limiting

**Symptom**: Frequent 429 errors

**Solutions**:

* Implement request queueing
* Use Server-Sent Events instead of polling
* Cache API responses
* Batch operations where possible

### Timeout Errors

**Symptom**: Requests timing out

**Causes**:

* Large file uploads
* Complex analysis operations
* Network connectivity issues

**Solutions**:

* Increase timeout thresholds
* Use progress monitoring endpoints
* Split large batches into smaller chunks

### Validation Failures

**Symptom**: Consistent 400 errors

**Solutions**:

* Review API documentation for field requirements
* Validate input formats (email, UUID, ISO dates)
* Check field length limits
* Ensure required fields are provided

## Related Resources

<CardGroup cols={2}>
  <Card title="API Reference" icon="terminal" href="/api-reference/introduction">
    Complete endpoint documentation with example requests
  </Card>

  <Card title="Quickstart Guide" icon="rocket" href="/quickstart">
    Step-by-step tutorial with error handling examples
  </Card>

  <Card title="Rate Limiting" icon="gauge" href="/guides/rate-limiting">
    Detailed rate limit information by endpoint
  </Card>

  <Card title="Authentication" icon="key" href="/api-reference/endpoints/auth-status">
    API key validation and authentication testing
  </Card>
</CardGroup>
