> ## 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.

# Case Management

> Organise verification workflows into cases for tracking, audit, and compliance

## 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

<Info>
  **Case Benefits**: Cases enable batch processing of documents, maintain audit trails, and support compliance reporting requirements.
</Info>

## Case Lifecycle

<Steps>
  <Step title="Creation">
    Create a case with a descriptive label and optional file placeholders
  </Step>

  <Step title="File Upload">
    Upload documents to the case using pre-signed URLs
  </Step>

  <Step title="Locking">
    Case automatically locks when files start uploading to ensure complete batch analysis
  </Step>

  <Step title="Analysis">
    All files in the case are analysed in parallel
  </Step>

  <Step title="Completion">
    Case reaches completed state when all file analyses finish
  </Step>
</Steps>

## Creating Cases

### With File Placeholders

Create a case and pre-define expected files to receive upload URLs immediately:

```javascript theme={null}
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:**

```json theme={null}
{
  "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:

```javascript theme={null}
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:

```javascript theme={null}
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:

```javascript theme={null}
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);
}
```

<Warning>
  **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.
</Warning>

## Retrieving Cases

### List All Cases

Get a paginated list of all cases belonging to you:

#### Pagination Parameters

| Parameter | Type    | Default | Min | Max | Description                                   |
| --------- | ------- | ------- | --- | --- | --------------------------------------------- |
| `limit`   | integer | 20      | 1   | 100 | Maximum number of cases to return per request |
| `offset`  | integer | 0       | 0   | -   | Number of cases to skip (for pagination)      |

```javascript theme={null}
// 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:**

```json theme={null}
{
  "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
}
```

<Info>
  **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.
</Info>

### Get Case Details

Retrieve comprehensive information about a specific case:

```javascript theme={null}
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:**

```json theme={null}
{
  "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:

```javascript theme={null}
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:

```javascript theme={null}
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

| Status                      | Description                      |
| --------------------------- | -------------------------------- |
| `waiting_for_file_analysis` | Files uploaded, analysis pending |
| `file_analysis_in_progress` | Analysis currently running       |
| `file_analysis_complete`    | All file analyses finished       |

### Individual File Status

| Status               | Description                               |
| -------------------- | ----------------------------------------- |
| `waiting_for_upload` | File placeholder created, awaiting upload |
| `processing`         | File uploaded and being analysed          |
| `settled`            | Analysis completed successfully           |

## Case Labelling Best Practices

Use descriptive, meaningful labels for easy identification and tracking:

### Good Examples

```javascript theme={null}
// 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

```javascript theme={null}
// Too generic
{ label: 'New Case' }
{ label: 'Documents' }

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

## Use Cases and Patterns

### Identity Verification (KYC)

```javascript theme={null}
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

```javascript theme={null}
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

```javascript theme={null}
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:

```javascript theme={null}
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:

```javascript theme={null}
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:

```javascript theme={null}
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](/guides/rate-limiting).

## Best Practices

### Organisation

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

### Performance

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

### Security

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

### Error Handling

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

## 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.

## Related Resources

<CardGroup cols={2}>
  <Card title="Create Case API" icon="plus" href="/api-reference/endpoints/cases-create">
    Detailed API reference for creating cases
  </Card>

  <Card title="List Cases API" icon="list" href="/api-reference/endpoints/cases-list">
    Retrieve and filter cases
  </Card>

  <Card title="Document Analysis" icon="magnifying-glass" href="/guides/document-analysis">
    Understanding forensic analysis results
  </Card>

  <Card title="Quickstart Guide" icon="rocket" href="/quickstart">
    Complete workflow from case creation to results
  </Card>
</CardGroup>
