Skip to main content

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.
Consistent Error Format: All error responses follow a standardised schema with error_code, error_message, and optional error_details fields.

Error Response Format

All errors follow this consistent structure:
{
  "error_code": "string",
  "error_message": "string",
  "error_details": {
    // Additional context (optional)
  }
}

Error Response Fields

FieldTypeDescription
error_codestringMachine-readable error identifier for programmatic handling
error_messagestringHuman-readable error description
error_detailsobjectOptional additional context (validation errors, retry info, etc.)

HTTP Status Codes

The API uses standard HTTP status codes to indicate request outcomes:
Status CodeMeaningCommon Causes
200OKRequest succeeded
201CreatedResource created successfully
400Bad RequestMalformed request body or invalid parameters
401UnauthorisedMissing, invalid, or expired API key
403ForbiddenValid credentials but insufficient permissions
404Not FoundResource does not exist
409ConflictRequest conflicts with current resource state
429Too Many RequestsRate limit exceeded
500Internal Server ErrorServer-side error occurred
503Service UnavailableTemporary server overload or maintenance

Common Error Types

Authentication Errors (401)

Authentication failures occur when API key validation fails.

Invalid API Key

{
  "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:
Verify API key format and value
Regenerate API key if compromised
Ensure using correct environment key

Expired API Key

{
  "error_code": "request.authentication.api-key.expired",
  "error_message": "The API key has expired."
}
Causes:
  • API key reached expiration date
  • Organisation subscription expired
Resolution:
Generate new API key from dashboard
Update application configuration
Verify organisation subscription status

Invalid Membership

{
  "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:
Verify organisation membership status
Contact organisation administrator
Generate new API key if membership restored

Validation Errors (400)

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

Request Body Validation

{
  "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:
Parse validation_errors array for field-specific issues
Display errors next to relevant form fields
Validate input client-side before submission
Log validation patterns to improve UX
Example Handler:
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.
{
  "error_code": "request.rate-limit",
  "error_message": "Too many requests. Please try again later.",
  "error_details": {
    "retry_after": 60
  }
}
Rate Limits by Operation:
OperationLimitScopeWindow
Create case10 requestsPer IPPer minute
Create document request10 requestsPer IPPer minute
File analysis retrieval300 requestsPer userPer hour
List operations30 requestsPer IPPer minute
Risk confirmations10 requestsPer userPer minute
Progress polling600 requestsPer API keyPer hour
Handling Strategy:
Implement exponential backoff retry logic
Respect retry_after header when provided
Use Server-Sent Events instead of polling where possible
Cache responses to reduce redundant requests
Exponential Backoff Implementation:
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)

{
  "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:
Validate UUIDs before making requests
Implement graceful fallback for missing resources
Cache resource existence checks

Resource Conflict (409)

{
  "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 resource state before operations
Create new case if existing case is locked
Implement idempotency where possible

Server Errors (500, 503)

Internal Server Error (500)

{
  "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:
Retry after short delay (2-5 seconds)
Log full error context for debugging
Contact support if error persists
Implement fallback or degraded mode

Service Unavailable (503)

{
  "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:
Display maintenance message to users
Retry after specified delay
Queue operations for later processing

Error Handling Best Practices

Comprehensive Error Handler

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

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

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

User Experience

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

Monitoring and Debugging

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

Security Considerations

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

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

API Reference

Complete endpoint documentation with example requests

Quickstart Guide

Step-by-step tutorial with error handling examples

Rate Limiting

Detailed rate limit information by endpoint

Authentication

API key validation and authentication testing