Skip to main content

Overview

Cases in Veridox provide a structured way to organise document verification workflows. Each case acts as a container for related documents that need to be analysed together, enabling efficient tracking, audit trails, and compliance reporting.

What is a Case?

A case represents a single verification workflow that groups related documents. Common examples include:
  • Customer onboarding: Identity documents, proof of address, employment verification
  • Insurance claims: Damage photos, repair estimates, supporting documentation
  • Loan applications: Bank statements, payslips, proof of residence
  • Compliance checks: Corporate documents, director IDs, company registration
Case Benefits: Cases enable batch processing of documents, maintain audit trails, and support compliance reporting requirements.

Case Lifecycle

1

Creation

Create a case with a descriptive label and optional file placeholders
2

File Upload

Upload documents to the case using pre-signed URLs
3

Locking

Case automatically locks when files start uploading to ensure complete batch analysis
4

Analysis

All files in the case are analysed in parallel
5

Completion

Case reaches completed state when all file analyses finish

Creating Cases

With File Placeholders

Create a case and pre-define expected files to receive upload URLs immediately:
const caseData = await fetch('https://api.{region}.veridox.ai/cases', {
  method: 'POST',
  headers: {
    'X-API-Key': apiKey,
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    label: 'Customer Application #12345',
    file_labels: ['passport.pdf', 'proof_of_address.pdf', 'bank_statement.pdf']
  })
}).then(res => res.json());

console.log('Case ID:', caseData.case_id);
console.log('Upload URLs:', caseData.files.map(f => f.label));
console.log('Case locked:', caseData.is_locked); // true when files expected
Response:
{
  "case_id": "019bfb3c-1257-7455-af2e-0cd3cadab5c7",
  "is_locked": true,
  "stream_url": "/cases/019bfb3c-1257-7455-af2e-0cd3cadab5c7/progress",
  "files": [
    {
      "file_id": "019bfb3c-1260-7861-8248-81aa8cb98faf",
      "label": "passport.pdf",
      "sas_url": "https://storage.example.com/uploads/019bfb3c-1260-7861-8248-81aa8cb98faf?token=...",
      "expires_at": "2026-01-26T17:56:16.741Z"
    }
  ]
}

Without File Placeholders

Create an empty case and add files later:
const caseData = await fetch('https://api.{region}.veridox.ai/cases', {
  method: 'POST',
  headers: {
    'X-API-Key': apiKey,
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    label: 'Customer Verification - Pending Documents'
  })
}).then(res => res.json());

console.log('Case ID:', caseData.case_id);
console.log('Case locked:', caseData.is_locked); // false - no files expected yet

Adding Files to Cases

Upload to Pre-signed URLs

After creating a case with file placeholders, upload files directly to storage:
for (const fileConfig of caseData.files) {
  const filePath = `./${fileConfig.label}`;
  const fileBuffer = fs.readFileSync(filePath);

  await fetch(fileConfig.sas_url, {
    method: 'PUT',
    headers: {
      'Content-Type': 'application/pdf'
    },
    body: fileBuffer
  });

  console.log(`✓ Uploaded: ${fileConfig.label}`);
}

Adding Additional Files

Add files to an existing unlocked case:
const additionalFiles = await fetch(
  `https://api.{region}.veridox.ai/cases/${caseId}/files`,
  {
    method: 'POST',
    headers: {
      'X-API-Key': apiKey,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      file_labels: ['additional_document.pdf', 'supporting_evidence.jpg']
    })
  }
).then(res => res.json());

// Upload to the new URLs
for (const fileConfig of additionalFiles.files) {
  await uploadFile(fileConfig.sas_url, fileConfig.label);
}
Case Locking: Once you start uploading files to a case, it becomes locked and you cannot add more files. Upload all documents in a single batch to avoid this limitation.

Retrieving Cases

List All Cases

Get a paginated list of all cases belonging to you:

Pagination Parameters

ParameterTypeDefaultMinMaxDescription
limitinteger201100Maximum number of cases to return per request
offsetinteger00-Number of cases to skip (for pagination)
// Get first 50 cases
const response = await fetch(
  'https://api.{region}.veridox.ai/cases?limit=50&offset=0',
  {
    headers: { 'X-API-Key': apiKey }
  }
);

const data = await response.json();

console.log(`Showing ${data.cases.length} of ${data.total} cases`);
console.log(`Limit: ${data.limit}, Offset: ${data.offset}`);

data.cases.forEach(c => {
  console.log(`${c.label}: ${c.file_analysis_status}`);
});
Response:
{
  "cases": [
    {
      "case_id": "019bfb3c-1257-7455-af2e-0cd3cadab5c7",
      "label": "Customer Application #12345",
      "is_locked": true,
      "file_analysis_status": "file_analysis_complete",
      "total_files": 3,
      "created_at": "2026-01-26T16:25:15.000Z"
    }
  ],
  "total": 147,
  "limit": 50,
  "offset": 0
}
Pagination Best Practice: The API returns a maximum of 100 cases per request. For large datasets, implement pagination by incrementing the offset parameter by limit on each request.

Get Case Details

Retrieve comprehensive information about a specific case:
const caseDetails = await fetch(
  `https://api.{region}.veridox.ai/cases/${caseId}`,
  {
    headers: { 'X-API-Key': apiKey }
  }
).then(res => res.json());

console.log('Case:', caseDetails.label);
console.log('Status:', caseDetails.file_analysis_status);
console.log('Files:', caseDetails.files.length);

caseDetails.files.forEach(file => {
  console.log(`- ${file.label}: ${file.analysis_status}`);
});
Response:
{
  "case_id": "019b2b88-71e8-7d07-a9fb-e96671eabdd7",
  "label": "Customer Application #12345",
  "metadata": null,
  "is_locked": true,
  "locked_at": "2025-12-17T08:58:41.095Z",
  "file_analysis_status": "file_analysis_complete",
  "file_analysis_completed_at": "2025-12-17T09:05:23.442Z",
  "created_at": "2025-12-17T08:58:41.000Z",
  "updated_at": "2025-12-17T09:05:23.442Z",
  "files": [
    {
      "file_id": "019b2b88-71f9-7c93-a0cf-c2c833f01c98",
      "label": "passport.pdf",
      "uploaded_at": "2025-12-17T08:59:15.324Z",
      "thumbnail_url": "https://storage.example.com/thumbnails/...",
      "mime_type": "application/pdf",
      "analysis_status": "settled",
      "analysis_completed_at": "2025-12-17T09:01:42.123Z"
    }
  ],
  "total_files": 1
}

Monitoring Progress

Real-time with Server-Sent Events (SSE)

Monitor case analysis progress in real-time:
const eventSource = new EventSource(
  `https://api.{region}.veridox.ai/cases/${caseId}/progress/stream?api_key=${apiKey}`
);

eventSource.onmessage = (event) => {
  const data = JSON.parse(event.data);

  switch (data.type) {
    case 'file.upload.completed':
      console.log(`✓ File uploaded: ${data.file_label}`);
      break;

    case 'file.analysis.started':
      console.log(`⏳ Analysis started: ${data.file_label}`);
      break;

    case 'file.analysis.progress':
      console.log(`📊 Progress: ${data.stage} - ${data.progress}%`);
      break;

    case 'file.analysis.completed':
      console.log(`✓ File complete: ${data.file_label}`);
      break;

    case 'case.analysis.completed':
      console.log('✅ All analysis complete!');
      eventSource.close();
      break;

    case 'error':
      console.error('❌ Error:', data.message);
      eventSource.close();
      break;
  }
};

eventSource.onerror = (error) => {
  console.error('Connection error:', error);
  eventSource.close();
};

Polling for Updates

Alternative progress monitoring using HTTP polling:
async function pollCaseProgress(caseId, apiKey) {
  let hasMore = true;
  let lastEventId = null;

  while (hasMore) {
    const url = new URL(`https://api.{region}.veridox.ai/cases/${caseId}/progress/poll`);
    if (lastEventId) {
      url.searchParams.append('last_event_id', lastEventId);
    }

    const response = await fetch(url, {
      headers: { 'X-API-Key': apiKey }
    });

    const { events, has_more } = await response.json();

    events.forEach(event => {
      console.log(`[${event.type}] ${event.message || ''}`);
      lastEventId = event.id;
    });

    hasMore = has_more;

    if (hasMore) {
      await new Promise(resolve => setTimeout(resolve, 3000)); // 3-second interval
    }
  }

  console.log('Polling complete');
}

Case Status Values

File Analysis Status

StatusDescription
waiting_for_file_analysisFiles uploaded, analysis pending
file_analysis_in_progressAnalysis currently running
file_analysis_completeAll file analyses finished

Individual File Status

StatusDescription
waiting_for_uploadFile placeholder created, awaiting upload
processingFile uploaded and being analysed
settledAnalysis completed successfully

Case Labelling Best Practices

Use descriptive, meaningful labels for easy identification and tracking:

Good Examples

// Customer reference numbers
{ label: 'Customer Application #APP-2025-1234' }

// Claim identifiers
{ label: 'Insurance Claim #CLM-2025-5678' }

// Policy numbers
{ label: 'Policy Renewal #POL-ABC-123456' }

// Customer name with workflow type
{ label: 'KYC Verification - John Smith' }

// Date-based tracking
{ label: 'Loan Application - 2026-01-27 - Jane Doe' }

Avoid

// Too generic
{ label: 'New Case' }
{ label: 'Documents' }

// No context
{ label: 'Case 1' }
{ label: 'Test' }

Use Cases and Patterns

Identity Verification (KYC)

const kycCase = await createCase('KYC - John Smith - ACC-12345', [
  'passport_front.jpg',
  'passport_back.jpg',
  'proof_of_address.pdf',
  'selfie.jpg'
]);

// Monitor analysis
await monitorCaseProgress(kycCase.case_id);

// Retrieve results
const results = await getCaseDetails(kycCase.case_id);

// Check risk levels
const highRiskFiles = results.files.filter(f =>
  f.current_risk_score === 'high' || f.current_risk_score === 'medium'
);

if (highRiskFiles.length > 0) {
  console.log('⚠️  Manual review required');
} else {
  console.log('✅ Automated approval');
}

Insurance Claims Processing

const claimCase = await createCase('Claim #CLM-2025-9876', [
  'damage_photo_front.jpg',
  'damage_photo_rear.jpg',
  'damage_photo_interior.jpg',
  'repair_estimate.pdf',
  'police_report.pdf'
]);

// Upload files
await uploadFilesToCase(claimCase);

// Wait for completion
await monitorCaseProgress(claimCase.case_id);

// Get detailed analysis
for (const file of claimCase.files) {
  const analysis = await getFileAnalysis(file.file_id);

  if (analysis.current_risk_score === 'high') {
    console.log(`⚠️  ${file.label}: High risk - ${analysis.analysis_results.full_analysis.summary}`);
  }
}

Loan Application Verification

const loanCase = await createCase('Loan App #LOAN-2025-4321', [
  'drivers_license.jpg',
  'bank_statement_month1.pdf',
  'bank_statement_month2.pdf',
  'bank_statement_month3.pdf',
  'payslip.pdf',
  'employment_letter.pdf'
]);

await uploadFilesToCase(loanCase);
await monitorCaseProgress(loanCase.case_id);

const caseDetails = await getCaseDetails(loanCase.case_id);

// Calculate overall application risk
const riskProfile = {
  high: caseDetails.files.filter(f => f.current_risk_score === 'high').length,
  medium: caseDetails.files.filter(f => f.current_risk_score === 'medium').length,
  low: caseDetails.files.filter(f => f.current_risk_score === 'low').length,
  informational: caseDetails.files.filter(f => f.current_risk_score === 'informational').length
};

console.log('Risk Profile:', riskProfile);

if (riskProfile.high > 0) {
  console.log('Decision: REJECT - High-risk documents detected');
} else if (riskProfile.medium > 1) {
  console.log('Decision: ENHANCED REVIEW - Multiple medium-risk documents');
} else {
  console.log('Decision: APPROVE - Low risk profile');
}

Advanced Case Management

Batch Case Creation

Create multiple cases efficiently:
async function createBatchCases(applications) {
  const casePromises = applications.map(app =>
    fetch('https://api.{region}.veridox.ai/cases', {
      method: 'POST',
      headers: {
        'X-API-Key': apiKey,
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({
        label: `Application ${app.id}`,
        file_labels: app.documents
      })
    }).then(res => res.json())
  );

  const cases = await Promise.all(casePromises);
  console.log(`Created ${cases.length} cases`);
  return cases;
}

Case Search and Filtering

Filter cases by status or date:
async function getCasesByStatus(status) {
  let allCases = [];
  let offset = 0;
  const limit = 100;

  while (true) {
    const response = await fetch(
      `https://api.{region}.veridox.ai/cases?limit=${limit}&offset=${offset}`,
      { headers: { 'X-API-Key': apiKey } }
    );

    const data = await response.json();
    allCases.push(...data.cases);

    if (allCases.length >= data.total) break;
    offset += limit;
  }

  return allCases.filter(c => c.file_analysis_status === status);
}

// Get all completed cases
const completedCases = await getCasesByStatus('file_analysis_complete');

Error Handling

Implement robust error handling for case operations:
async function createCaseWithRetry(caseData, maxRetries = 3) {
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    try {
      const response = await fetch('https://api.{region}.veridox.ai/cases', {
        method: 'POST',
        headers: {
          'X-API-Key': apiKey,
          'Content-Type': 'application/json'
        },
        body: JSON.stringify(caseData)
      });

      if (!response.ok) {
        const error = await response.json();

        if (response.status === 429) {
          // Rate limited - wait and retry
          const retryAfter = response.headers.get('Retry-After') || 60;
          console.log(`Rate limited. Retrying after ${retryAfter}s...`);
          await new Promise(resolve => setTimeout(resolve, retryAfter * 1000));
          continue;
        }

        throw new Error(error.error_message);
      }

      return await response.json();
    } catch (error) {
      console.error(`Attempt ${attempt} failed:`, error.message);

      if (attempt === maxRetries) {
        throw new Error(`Failed after ${maxRetries} attempts: ${error.message}`);
      }

      // Exponential backoff
      await new Promise(resolve =>
        setTimeout(resolve, Math.pow(2, attempt) * 1000)
      );
    }
  }
}

Rate Limits

Case operations are subject to rate limiting to ensure system stability. For a complete list of limits across all endpoints, see the Rate Limiting Guide.

Best Practices

Organisation

Descriptive labels - Use meaningful identifiers for easy case tracking
Unique labels (case-insensitive) - Case labels must be unique per user, even if the only difference is letter casing
Consistent naming - Establish naming conventions across your organisation
Batch related documents - Group all related files in a single case

Performance

Pre-plan files - Create cases with file labels to get upload URLs immediately
Parallel uploads - Upload multiple files simultaneously for faster processing
Use SSE - Prefer Server-Sent Events over polling for real-time updates

Security

API key protection - Never expose API keys in client-side code
Upload URL expiration - Complete uploads within 1 hour (URL expiry time)
Access control - Only you can access your own case data
Audit trails - All case operations are logged for compliance

Error Handling

Retry logic - Implement exponential backoff for transient failures
Rate limit handling - Respect rate limits and implement appropriate delays
Upload validation - Verify file uploads completed successfully before proceeding

Common Issues

Case Already Locked

Problem: Cannot add more files to a case Solution: Cases lock when file uploads begin. Plan all required files upfront or create a new case for additional documents.

Upload URL Expired

Problem: Pre-signed upload URL no longer works Solution: URLs expire after 1 hour. Request fresh URLs if needed by creating a new case or using the additional files endpoint.

Analysis Timeout

Problem: Analysis takes longer than expected Solution: Large files (>10 MB) may take 3-5 minutes. Check case status periodically and contact support if analysis exceeds 10 minutes.

SSE Connection Drops

Problem: Real-time progress monitoring disconnects Solution: Corporate networks may block SSE. Implement automatic fallback to HTTP polling.

Create Case API

Detailed API reference for creating cases

List Cases API

Retrieve and filter cases

Document Analysis

Understanding forensic analysis results

Quickstart Guide

Complete workflow from case creation to results