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
Creation
Create a case with a descriptive label and optional file placeholders
File Upload
Upload documents to the case using pre-signed URLs
Locking
Case automatically locks when files start uploading to ensure complete batch analysis
Analysis
All files in the case are analysed in parallel
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:
Parameter Type Default Min Max Description limitinteger 20 1 100 Maximum number of cases to return per request offsetinteger 0 0 - 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
Status Description waiting_for_file_analysisFiles uploaded, analysis pending file_analysis_in_progressAnalysis currently running file_analysis_completeAll file analyses finished
Individual File Status
Status Description 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
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