Table of Contents
Overview
Striae's backend consists of six Cloudflare Workers that provide specialized API services. All APIs use RESTful conventions and return JSON responses.
Authentication
Authentication Methods
All worker APIs use custom authentication headers:
X-Custom-Auth-Key: {api_key}
API keys are generated by the Keys Worker and should be obtained through the authentication flow.
Note: The Image Worker uses a different authentication method with Authorization: Bearer {token} header.
Authentication Environment Variables:
-
User Worker: Uses
USER_DB_AUTH,SL_API_KEY,R2_KEY_SECRET, andIMAGES_API_TOKENenvironment variables -
Data Worker: Uses
R2_KEY_SECRETenvironment variable -
Keys Worker: Uses
KEYS_AUTHenvironment variable -
Image Worker: Uses
API_TOKENenvironment variable -
PDF Worker: No authentication required
-
Turnstile Worker: No authentication required
CORS Policy
All workers implement strict CORS policies:
-
Allowed Origin:
https://www.striae.org -
Allowed Methods: Varies by worker
-
Allowed Headers:
Content-Type, X-Custom-Auth-Key, Authorization
User Worker API
Base URL: {USER_WORKER_URL}
Authentication: X-Custom-Auth-Key header
Environment Variables Required:
-
USER_DB_AUTH- KV database authentication -
SL_API_KEY- SendLayer email service for deletion confirmations -
R2_KEY_SECRET- Data worker API authentication for case deletion -
IMAGES_API_TOKEN- Image worker API authentication for file deletion
Endpoints
Get User Data
GET /{userUid}
Description: Retrieve user profile data from KV storage
Parameters:
userUid(path): Firebase user UID
Response:
{
"uid": "string",
"email": "string",
"firstName": "string",
"lastName": "string",
"company": "string",
"permitted": boolean,
"cases": [
{
"caseNumber": "string",
"createdAt": "ISO 8601 date"
}
],
"createdAt": "ISO 8601 date",
"updatedAt": "ISO 8601 date"
}
Status Codes:
-
200: Success -
404: User not found -
401: Unauthorized (invalid auth header) -
500: Server error
Create/Update User
PUT /{userUid}
Description: Create new user or update existing user data. Preserves existing cases when updating.
Parameters:
userUid(path): Firebase user UID
Request Body:
{
"email": "string",
"firstName": "string",
"lastName": "string",
"company": "string",
"permitted": boolean
}
Response: User object (same as GET response)
Status Codes:
-
200: User updated -
201: User created -
401: Unauthorized -
500: Server error
Delete User
DELETE /{userUid}
Description: Delete user and all associated data including cases, files, and images. Sends confirmation emails to user and admin.
Parameters:
userUid(path): Firebase user UID
Process:
-
Retrieves user data for email confirmation
-
Deletes all user's cases and associated files from data worker
-
Deletes all associated images from image worker
-
Sends deletion confirmation email to user via SendLayer
-
Sends admin notification email
-
Deletes user account from KV storage
Security Notes:
-
Demo accounts (
permitted: false) should be protected from deletion at the UI level -
Deletion includes all user data, cases, files, and annotations
-
Action is irreversible and permanent
-
Sends email confirmations using SendLayer service
Response:
{
"success": true,
"message": "Account successfully deleted and confirmation emails sent"
}
Error Response:
{
"success": false,
"message": "Account deletion failed: Unable to send confirmation emails"
}
Status Codes:
-
200: Success -
404: User not found -
401: Unauthorized -
500: Server error (including email sending failures)
Add Cases to User
PUT /{userUid}/cases
Description: Add case assignments to user. Filters out duplicate case numbers.
Parameters:
userUid(path): Firebase user UID
Request Body:
{
"cases": [
{
"caseNumber": "string"
}
]
}
Response: Updated user object
Status Codes:
-
200: Success -
404: User not found -
401: Unauthorized -
500: Server error
Remove Cases from User
DELETE /{userUid}/cases
Description: Remove case assignments from user. Does not delete the actual case data.
Parameters:
userUid(path): Firebase user UID
Request Body:
{
"casesToDelete": ["caseNumber1", "caseNumber2"]
}
Response: Updated user object
Status Codes:
-
200: Success -
404: User not found -
401: Unauthorized -
500: Server error
Image Worker API
Base URL: {IMAGE_WORKER_URL}
Authentication: Authorization: Bearer {API_TOKEN} header
Endpoints
Upload Image
POST /
Description: Upload image to Cloudflare Images
Request: Multipart form data with image file
Response:
{
"result": {
"id": "string",
"filename": "string",
"uploaded": "ISO 8601 date",
"requireSignedURLs": true,
"variants": ["string"]
},
"success": boolean,
"errors": [],
"messages": []
}
Status Codes:
-
200: Success -
403: Forbidden (invalid token) -
400: Invalid file or missing file -
500: Server error
Get Signed URL
GET /{imageDeliveryPath}
Description: Generate signed URL for image access from imagedelivery.net URL
Parameters:
imageDeliveryPath(path): Full imagedelivery.net URL path
Response: Signed URL as plain text
Status Codes:
-
200: Success -
403: Forbidden (invalid token) -
500: Server error
Delete Image
DELETE /{imageId}
Description: Delete image from Cloudflare Images
Parameters:
imageId(path): Cloudflare Images ID
Response:
{
"success": boolean,
"result": {},
"errors": [],
"messages": []
}
Status Codes:
-
200: Success -
403: Forbidden (invalid token) -
404: Image not found -
500: Server error
PDF Worker API
Base URL: {PDF_WORKER_URL}
Authentication: None required
Endpoints
Generate PDF
POST /
Description: Generate PDF report with annotations
Request Body:
{
"imageUrl": "string",
"caseNumber": "string",
"annotationData": {
"leftCase": "string",
"rightCase": "string",
"leftItem": "string",
"rightItem": "string",
"caseFontColor": "string",
"classType": "Bullet | Cartridge Case | Other",
"customClass": "string",
"classNote": "string",
"indexType": "number | color",
"indexNumber": "string",
"indexColor": "string",
"supportLevel": "ID | Exclusion | Inconclusive",
"hasSubclass": boolean,
"includeConfirmation": boolean,
"additionalNotes": "string",
"boxAnnotations": [
{
"id": "string",
"x": "number (percentage 0-100)",
"y": "number (percentage 0-100)",
"width": "number (percentage 0-100)",
"height": "number (percentage 0-100)",
"color": "string (hex color)"
}
]
},
"activeAnnotations": ["string"],
"currentDate": "string",
"notesUpdatedFormatted": "string"
}
Response: PDF file (binary data)
Headers:
Content-Type:application/pdf
Status Codes:
-
200: Success -
405: Method not allowed -
500: Server error
Data Worker API
Base URL: {DATA_WORKER_URL}
Authentication: X-Custom-Auth-Key header
Endpoints
Get File Data
GET /{filename}.json
Description: Retrieve JSON file data from R2 storage
Parameters:
filename(path): JSON filename (without .json extension in URL)
Response: JSON data from file or empty array if file doesn't exist
Status Codes:
-
200: Success -
403: Forbidden -
500: Server error
Save File Data
PUT /{filename}.json
Description: Save JSON data to R2 storage file
Parameters:
filename(path): JSON filename (without .json extension in URL)
Request Body: Any valid JSON data
Response:
{
"success": true
}
Status Codes:
-
200: Success -
400: Invalid file type (non-JSON) -
403: Forbidden -
500: Server error
Delete File
DELETE /{filename}.json
Description: Delete JSON file from R2 storage
Parameters:
filename(path): JSON filename (without .json extension in URL)
Response:
{
"success": true
}
Status Codes:
-
200: Success -
404: File not found -
403: Forbidden -
500: Server error
Store Audit Entry
POST /audit/
Description: Create a new audit trail entry for compliance tracking
Query Parameters:
userId(required): User identifier
Request Body:
{
"timestamp": "2025-09-23T14:30:15.123Z",
"userId": "string",
"userEmail": "string",
"action": "AuditAction",
"result": "AuditResult",
"details": {
"fileName": "string",
"fileType": "AuditFileType",
"caseNumber": "string",
"hashValid": "boolean",
"validationErrors": ["string"],
"workflowPhase": "WorkflowPhase",
"performanceMetrics": {
"processingTimeMs": "number",
"fileSizeBytes": "number"
},
"fileDetails": {
"fileId": "string",
"originalFileName": "string",
"fileSize": "number",
"mimeType": "string",
"uploadMethod": "string"
},
"annotationDetails": {
"annotationType": "string",
"tool": "string",
"canvasPosition": {"x": "number", "y": "number"}
}
}
}
Response:
{
"success": true,
"entryCount": 15,
"filename": "audit-trails/aDzwq3G6IBVRJVCEFijdg7B0fwq2-2025-09-23.json"
}
Status Codes:
-
200: Audit entry stored successfully -
400: userId parameter required or invalid request data -
403: Forbidden -
500: Server error
Get User Audit Entries
GET /audit/
Description: Retrieve audit trail entries for a specific user
Query Parameters:
-
userId(required): User identifier -
startDate(optional): Start date filter (YYYY-MM-DD format) -
endDate(optional): End date filter (YYYY-MM-DD format)
Example:
GET /audit/?userId=aDzwq3G6IBVRJVCEFijdg7B0fwq2&startDate=2025-09-01&endDate=2025-09-30
Response:
{
"entries": [
{
"timestamp": "2025-09-23T14:30:15.123Z",
"userId": "aDzwq3G6IBVRJVCEFijdg7B0fwq2",
"userEmail": "user@example.com",
"action": "export",
"result": "success",
"details": {
"fileName": "CASE-2025-001-annotations.json",
"fileType": "json-data",
"caseNumber": "CASE-2025-001",
"hashValid": true,
"validationErrors": [],
"workflowPhase": "case-export"
}
}
],
"total": 15
}
Status Codes:
-
200: Success -
400: userId parameter required -
403: Forbidden -
500: Server error
Note: If no date range is specified, returns entries for the current date only. Date range queries read multiple daily files and aggregate results.
Keys Worker API
Base URL: {KEYS_WORKER_URL}
Authentication: X-Custom-Auth-Key header
Endpoints
Get Environment Key
GET /{keyName}
Description: Retrieve environment variable value
Parameters:
keyName(path): Environment variable name
Response: Plain text value of the environment variable
Status Codes:
-
200: Success -
400: Key name required -
404: Key not found -
403: Forbidden -
500: Server error
Turnstile Worker API
Base URL: {TURNSTILE_WORKER_URL}
Authentication: None required
Endpoints
Verify CAPTCHA
POST /
Description: Verify Cloudflare Turnstile token
Request Body:
{
"cf-turnstile-response": "string"
}
Response:
{
"success": boolean,
"error-codes": ["string"],
"challenge_ts": "ISO 8601 date",
"hostname": "string"
}
Status Codes:
-
200: Verification completed (check success field) -
400: Invalid request or token missing -
405: Method not allowed -
500: Server error
Error Handling
Standard Error Response Format
{
"error": "string",
"code": "string",
"message": "string",
"details": {}
}
Common Error Codes
-
UNAUTHORIZED: Invalid or missing authentication -
FORBIDDEN: Insufficient permissions -
NOT_FOUND: Resource not found -
VALIDATION_ERROR: Invalid request data -
SERVER_ERROR: Internal server error
HTTP Status Codes
-
200: Success -
201: Created -
400: Bad Request -
401: Unauthorized -
403: Forbidden -
404: Not Found -
405: Method Not Allowed -
500: Internal Server Error
Type Definitions
Audit Trail Types
ValidationAuditEntry Interface
Core audit entry structure for all validation events:
interface ValidationAuditEntry {
timestamp: string; // ISO 8601 timestamp
userId: string; // User identifier
userEmail: string; // User email for identification
action: AuditAction; // What action was performed
result: AuditResult; // Success/failure/warning/blocked
details: AuditDetails; // Action-specific details
}
AuditAction Type
All supported audit actions:
type AuditAction =
// Case Management Actions
| 'case-create' | 'case-rename' | 'case-delete'
// Confirmation Workflow Actions
| 'case-export' | 'case-import' | 'confirmation-create'
| 'confirmation-export' | 'confirmation-import'
// File Operations
| 'file-upload' | 'file-delete' | 'file-access'
// Annotation Operations
| 'annotation-create' | 'annotation-edit' | 'annotation-delete'
// User & Session Management
| 'user-login' | 'user-logout' | 'user-profile-update'
| 'user-password-reset' | 'user-account-delete'
// Document Generation
| 'pdf-generate'
// Security & Monitoring
| 'security-violation'
// Legacy actions (for backward compatibility)
| 'import' | 'export' | 'confirm' | 'validate';
AuditResult Type
Result types for audit operations:
type AuditResult = 'success' | 'failure' | 'warning' | 'blocked' | 'pending';
AuditDetails Interface
Detailed information for each audit entry:
interface AuditDetails {
// Core identification
fileName?: string;
fileType?: AuditFileType;
caseNumber?: string;
confirmationId?: string;
// Validation & Security
hashValid?: boolean;
validationErrors: string[];
securityChecks?: SecurityCheckResults;
// Context & Workflow
originalExaminerUid?: string;
reviewingExaminerUid?: string;
workflowPhase?: WorkflowPhase;
// Performance & Metrics
performanceMetrics?: PerformanceMetrics;
// Specialized details
caseDetails?: CaseAuditDetails;
fileDetails?: FileAuditDetails;
annotationDetails?: AnnotationAuditDetails;
sessionDetails?: SessionAuditDetails;
securityDetails?: SecurityAuditDetails;
userProfileDetails?: UserProfileAuditDetails;
}
AuditTrail Interface
Complete audit trail for a case or workflow:
interface AuditTrail {
caseNumber: string;
workflowId: string; // Unique identifier linking related entries
entries: ValidationAuditEntry[];
summary: AuditSummary;
}
AuditSummary Interface
Summary of audit trail for reporting and compliance:
interface AuditSummary {
totalEvents: number;
successfulEvents: number;
failedEvents: number;
warningEvents: number;
workflowPhases: WorkflowPhase[];
participatingUsers: string[]; // User IDs
startTimestamp: string;
endTimestamp: string;
complianceStatus: 'compliant' | 'non-compliant' | 'pending';
securityIncidents: number;
}
WorkflowPhase Type
Workflow phases for tracking different types of forensic activities:
type WorkflowPhase =
| 'casework' // Case, notes, image, and pdf related actions
| 'case-export' // Only case exporting
| 'case-import' // Only case importing
| 'confirmation' // Only confirmation-related activity
| 'user-management'; // User login, logout, profile management, account activities
Core Annotation Types
AnnotationData Interface
The comprehensive data structure for all annotation information:
interface AnnotationData {
leftCase: string;
rightCase: string;
leftItem: string;
rightItem: string;
caseFontColor?: string;
classType?: 'Bullet' | 'Cartridge Case' | 'Other';
customClass?: string;
classNote?: string;
indexType?: 'number' | 'color';
indexNumber?: string;
indexColor?: string;
supportLevel?: 'ID' | 'Exclusion' | 'Inconclusive';
hasSubclass?: boolean;
includeConfirmation: boolean;
additionalNotes?: string;
boxAnnotations?: BoxAnnotation[];
updatedAt: string;
}
BoxAnnotation Interface
Individual box annotation structure with percentage-based coordinates (defined in app/types/annotations.ts):
interface BoxAnnotation {
id: string;
x: number; // Percentage 0-100
y: number; // Percentage 0-100
width: number; // Percentage 0-100
height: number; // Percentage 0-100
color: string; // Hex color code
label?: string; // Optional label text
timestamp: string; // Creation timestamp (ISO 8601 format)
}
User Management Types
UserData Interface
Core user profile and permissions structure:
interface UserData {
uid: string;
email: string | null;
firstName: string;
lastName: string;
company: string;
permitted: boolean;
cases: Array<{
caseNumber: string;
createdAt: string;
}>;
readOnlyCases?: Array<{
caseNumber: string;
importedAt: string;
originalExportDate: string;
originalExportedBy: string;
sourceHash?: string;
isReadOnly: true;
}>;
createdAt: string;
updatedAt?: string;
}
UserLimits Interface
User account limitations and quotas:
interface UserLimits {
maxCases: number;
maxFilesPerCase: number;
}
File Management Types
FileData Interface
Core file information structure:
interface FileData {
id: string;
originalFilename: string;
uploadedAt: string;
}
FileUploadResponse Interface
Cloudflare Images API response structure:
interface FileUploadResponse {
success: boolean;
result: {
id: string;
filename: string;
uploaded: string;
requireSignedURLs: boolean;
variants: string[];
};
errors: Array<{
code: number;
message: string;
}>;
messages: string[];
}
ImageUploadResponse Interface
Image upload response wrapper (extends FileUploadResponse):
interface ImageUploadResponse {
success: boolean;
result: FileUploadResponse['result'];
errors: FileUploadResponse['errors'];
messages: FileUploadResponse['messages'];
}
Case Management Types
CaseData Interface
Case structure with associated files:
interface CaseData {
createdAt: string;
caseNumber: string;
files: FileData[];
}
CaseActionType
Case operation type definition for UI state management:
type CaseActionType = 'loaded' | 'created' | 'deleted' | null;
CasesToDelete Interface
Structure for batch case deletion operations:
interface CasesToDelete {
casesToDelete: string[];
}
Case Export Types
CaseExportData Interface
Complete case export data structure for single case exports:
interface CaseExportData {
metadata: {
caseNumber: string;
exportDate: string;
exportedBy: string | null;
striaeExportSchemaVersion: string;
totalFiles: number;
};
files: Array<{
fileData: FileData;
annotations?: AnnotationData;
hasAnnotations: boolean;
}>;
summary?: {
filesWithAnnotations: number;
filesWithoutAnnotations: number;
totalBoxAnnotations: number;
lastModified?: string;
exportError?: string;
};
}
AllCasesExportData Interface
Comprehensive export data structure for bulk case exports:
interface AllCasesExportData {
metadata: {
exportDate: string;
exportedBy: string | null;
striaeExportSchemaVersion: string;
totalCases: number;
totalFiles: number;
totalAnnotations: number;
};
cases: CaseExportData[];
summary?: {
casesWithFiles: number;
casesWithAnnotations: number;
casesWithoutFiles: number;
lastModified?: string;
};
}
ExportFormat Type
Export format type definition:
export type ExportFormat = 'json' | 'csv';
ExportOptions Interface
Configuration options for case export operations:
interface ExportOptions {
includeAnnotations?: boolean;
includeMetadata?: boolean;
}
Export Features:
-
Format Support: JSON, CSV/Excel, and ZIP export formats
-
ZIP Export with Images: Single case exports can include associated image files packaged in ZIP format
-
Comprehensive Data: All annotation fields including case identifiers, colors, classifications, and box annotations
-
Bulk Operations: Export all cases with progress tracking and error handling
-
Excel Multi-Worksheet: CSV format creates Excel files with multiple worksheets for bulk exports
-
Split Box Annotations: Each box annotation gets its own row in CSV/Excel for better data analysis
-
Metadata Rich: Complete export metadata including timestamps, user info, and summary statistics
-
Error Handling: Graceful handling of failed case exports with detailed error information
-
Progress Tracking: Real-time progress updates for ZIP generation and image downloads
Case Import Types
CaseImportProps Interface
Props interface for the main case import component:
interface CaseImportProps {
isOpen: boolean;
onClose: () => void;
onImportComplete?: (result: ImportResult | ConfirmationImportResult) => void;
}
ImportOptions Interface
Configuration options for case import operations:
interface ImportOptions {
overwriteExisting?: boolean;
validateIntegrity?: boolean;
preserveTimestamps?: boolean;
}
ImportResult Interface
Result structure returned from case import operations:
interface ImportResult {
success: boolean;
caseNumber: string;
isReadOnly: boolean;
filesImported: number;
annotationsImported: number;
errors?: string[];
warnings?: string[];
}
ConfirmationImportResult Interface
Result structure returned from confirmation import operations:
interface ConfirmationImportResult {
success: boolean;
caseNumber: string;
confirmationsImported: number;
imagesUpdated: number;
errors?: string[];
warnings?: string[];
}
ReadOnlyCaseMetadata Interface
Metadata structure for imported read-only cases:
interface ReadOnlyCaseMetadata {
caseNumber: string;
importedAt: string;
originalExportDate: string;
originalExportedBy: string;
sourceHash?: string;
isReadOnly: true;
}
CaseImportPreview Interface
Preview information generated during ZIP parsing before import:
interface CaseImportPreview {
caseNumber: string;
fileCount: number;
annotationCount: number;
exportDate: string;
exportedBy: string;
hasImages: boolean;
canImport: boolean;
warnings: string[];
}
Import Features:
-
Complete ZIP Package Import: Full case data and image import from exported ZIP packages
-
Read-Only Protection: Imported cases are automatically set to read-only mode for secure review
-
Duplicate Prevention: Prevents import if user was the original case analyst
-
Progress Tracking: Multi-stage progress reporting with detailed status updates
-
Image Integration: Automatic upload and association of all case images
-
Metadata Preservation: Complete preservation of original export metadata and timestamps
-
Data Integrity: Comprehensive validation of ZIP contents and case data structure
CSV/Excel Export Details:
-
Single Case CSV: Comprehensive CSV with individual rows for each box annotation
-
Bulk Export Excel: Multi-worksheet Excel file with summary sheet and individual case sheets
-
Complete Data Parity: CSV/Excel exports contain identical data to JSON exports
-
Enhanced Box Annotations: Includes coordinates, colors, and timestamps (label removed)
-
Classification Data: Full support for case identifiers, color schemes, and forensic classifications
-
Optimized Layout: File metadata appears only on first row per file, subsequent rows contain only box data
ZIP Export Functionality:
-
Single Case Only: ZIP exports are restricted to individual cases to manage file size
-
Image Inclusion: All associated image files packaged with original filenames preserved
-
Data File: Selected format (JSON or CSV) included in ZIP package
-
README Generation: Automatic README.txt with case summary and statistics
-
Progress Feedback: Real-time progress updates during ZIP creation and image downloads
-
Error Recovery: Graceful handling of individual image download failures
-
JSZip Integration: Browser-based ZIP generation using JSZip library
Centralized Type Management
Type Definition Location: All interfaces are centrally defined in app/types/ with barrel exports from app/types/index.ts:
// Barrel export structure
export * from './annotations'; // AnnotationData, BoxAnnotation
export * from './user'; // UserData, UserLimits
export * from './file'; // FileData, FileUploadResponse, ImageUploadResponse
export * from './case'; // CaseData, CaseMetadata, CaseActionType, CaseExportData, AllCasesExportData
Usage Patterns:
-
Centralized Imports:
import { UserData, FileData, AnnotationData } from '~/types'; -
Component Props: All component interfaces extend or use centralized types
-
Worker APIs: Type validation based on shared interface definitions
-
PDF Generation: Type-safe data flow from frontend through workers
-
Data Storage: Structured data persistence with comprehensive type validation
Benefits:
-
Single source of truth for all data structures
-
Type safety across entire application stack
-
Consistent API contracts between frontend and workers
-
Easier refactoring and maintenance with centralized type management
-
Compile-time error detection for data structure changes
-
Comprehensive coverage of all application domains (user, file, case, annotation)
-
Utility types for common patterns (loading states, API responses, pagination)
SDK Examples
JavaScript/TypeScript Client
class StriaeAPI {
constructor(private apiKey: string, private baseUrl: string) {}
async getUser(userUid: string): Promise<UserData> {
const response = await fetch(`${this.baseUrl}/${userUid}`, {
headers: {
'X-Custom-Auth-Key': this.apiKey,
'Content-Type': 'application/json'
}
});
if (!response.ok) {
throw new Error(`API Error: ${response.status}`);
}
return response.json();
}
async uploadImage(file: File): Promise<UploadResult> {
const formData = new FormData();
formData.append('file', file);
const response = await fetch(`${this.baseUrl}`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${this.apiKey}`
},
body: formData
});
return response.json();
}
}
Error Handling Example
try {
const userData = await api.getUser(userUid);
console.log('User data:', userData);
} catch (error) {
if (error.status === 404) {
console.log('User not found');
} else if (error.status === 401) {
console.log('Authentication failed');
} else {
console.error('API error:', error);
}
}