Reference documentation for the REST API, stores, hooks, utilities, and types.
- REST API
- Authentication
- Error Handling
- Stores
- AI Client
- AI Validation
- Template System
- Connector Types
- PDF Export
- Utilities
- Type Reference
The QAtrial backend exposes a REST API at http://localhost:3001/api. All endpoints return JSON. Endpoints marked with "Auth: Yes" require a valid JWT access token in the Authorization: Bearer <token> header.
80+ endpoints across 28+ route files.
| Method | Path | Auth | Description |
|---|---|---|---|
GET |
/api/health |
No | Basic health check |
GET |
/api/status |
No | Detailed status (version, uptime, DB, AI, memory) |
GET /api/health Response (200):
{ "status": "ok", "version": "3.0.0" }GET /api/status Response (200):
{
"status": "ok",
"version": "3.0.0",
"uptime": 12345,
"database": "connected",
"aiProvider": "configured",
"storage": "ok",
"freeMemory": "512MB"
}Creates a new user account along with an organization and default workspace.
Auth: No
Request Body:
{
"email": "user@example.com",
"password": "minimum8chars",
"name": "Full Name"
}Response (201):
{
"accessToken": "eyJhbGci...",
"refreshToken": "eyJhbGci...",
"user": {
"id": "uuid",
"email": "user@example.com",
"name": "Full Name",
"role": "admin",
"orgId": "uuid"
}
}Error Responses:
400-- Missing required fields or password shorter than 8 characters409-- Email already registered
Authenticates an existing user.
Auth: No
Request Body:
{
"email": "user@example.com",
"password": "password123"
}Response (200):
{
"accessToken": "eyJhbGci...",
"refreshToken": "eyJhbGci...",
"user": {
"id": "uuid",
"email": "user@example.com",
"name": "Full Name",
"role": "admin",
"orgId": "uuid"
}
}Error Responses:
400-- Missing email or password401-- Invalid email or password
Exchanges a valid refresh token for a new access/refresh token pair.
Auth: No
Request Body:
{
"refreshToken": "eyJhbGci..."
}Response (200):
{
"accessToken": "eyJhbGci...",
"refreshToken": "eyJhbGci..."
}Error Responses:
400-- Missing refresh token401-- Invalid or expired refresh token
Returns the current authenticated user's profile.
Auth: Yes
Response (200):
{
"user": {
"id": "uuid",
"email": "user@example.com",
"name": "Full Name",
"role": "admin",
"orgId": "uuid",
"createdAt": "2026-04-01T12:00:00.000Z"
}
}Redirects to the OIDC identity provider for authentication.
Auth: No
Query Parameters:
- None
Response: 302 redirect to IdP authorization endpoint.
Handles the OIDC callback after IdP authentication. Exchanges the authorization code for tokens, looks up or provisions the user, and redirects to the frontend with QAtrial JWT tokens.
Auth: No
Query Parameters:
code-- Authorization code from IdP
Response: 302 redirect to frontend with tokens.
Returns SSO configuration status.
Auth: No
Response (200):
{
"enabled": true,
"type": "oidc",
"issuerUrl": "https://your-idp.okta.com"
}All project endpoints require authentication.
List all projects in the user's workspace.
Auth: Yes
Response (200):
{
"projects": [
{
"id": "uuid",
"workspaceId": "uuid",
"name": "Project Name",
"description": "...",
"owner": "Owner Name",
"version": "1.0",
"country": "US",
"vertical": "pharma",
"modules": ["audit_trail", "e_signatures"],
"type": "software",
"createdAt": "2026-04-01T12:00:00.000Z",
"updatedAt": "2026-04-01T12:00:00.000Z"
}
]
}Create a new project.
Auth: Yes
Request Body:
{
"name": "Project Name",
"description": "Optional description",
"owner": "Owner Name",
"version": "1.0",
"country": "US",
"vertical": "pharma",
"modules": ["audit_trail", "e_signatures"],
"type": "software"
}Response (201):
{ "project": { "id": "uuid", "name": "Project Name", ... } }Error Responses:
400-- Missing required fieldname
Get a single project by ID. Auth: Yes
Update a project. Auth: Yes. Partial project fields to update.
Delete a project and all its child records (cascade). Auth: Yes
List all requirements for a project. Auth: Yes
Query Parameters: projectId (required)
Create a new requirement. Auto-generates seqId (REQ-001, REQ-002, etc.). Auth: Yes
Request Body:
{
"projectId": "uuid",
"title": "Data Integrity",
"description": "System shall ensure ALCOA+ data integrity",
"status": "Draft",
"tags": ["data-integrity"],
"riskLevel": "high",
"regulatoryRef": "21 CFR 11.10(e)"
}Get a single requirement. Auth: Yes
Update a requirement. Auth: Yes
Delete a requirement. Auth: Yes
List all tests for a project. Auth: Yes
Create a new test. Auto-generates seqId (TST-001, etc.). Auth: Yes
Request Body:
{
"projectId": "uuid",
"title": "Verify Data Integrity",
"description": "Confirm ALCOA+ compliance...",
"status": "Not Run",
"linkedRequirementIds": ["uuid"]
}Standard CRUD. Auth: Yes
List all CAPA records. Auth: Yes
Create a new CAPA record (status defaults to "open"). Auth: Yes
Update a CAPA record. Status transitions are enforced server-side:
open -> investigation -> in_progress -> verification -> resolved -> closed
Attempting to skip a stage returns 400. Auth: Yes
Delete a CAPA record. Auth: Yes
List all risks. Auth: Yes
Create a new risk. riskScore (severity x likelihood) and riskLevel are auto-computed. Auth: Yes
Request Body:
{
"projectId": "uuid",
"requirementId": "uuid",
"severity": 4,
"likelihood": 3,
"detectability": 2,
"mitigation": "..."
}Standard CRUD. Auth: Yes
List audit entries (read-only). Auth: Yes
Query Parameters:
projectId-- Filter by projectentityType-- Filter by entity typeaction-- Filter by actionfrom/to-- Date range (ISO 8601)
Export audit entries as CSV. Auth: Yes
Same query parameters as GET /api/audit.
List all users in the organization. Auth: Yes
Invite a new user. Auth: Yes (admin only)
Request Body:
{
"email": "newuser@example.com",
"role": "qa_engineer"
}Change a user's role. Auth: Yes (admin only)
Request Body:
{ "role": "qa_manager" }Upload a CSV file for preview. Auto-detects delimiter (comma, semicolon, tab) and suggests column mapping.
Auth: Yes
Content-Type: multipart/form-data
Response (200):
{
"columns": ["Title", "Description", "Status"],
"sampleRows": [...],
"suggestedMapping": {
"Title": "title",
"Description": "description",
"Status": "status"
},
"delimiter": ","
}Execute the import with mapped columns.
Auth: Yes
Request Body:
{
"projectId": "uuid",
"entityType": "requirement",
"mapping": { "Title": "title", "Description": "description" },
"data": [...],
"duplicateHandling": "skip"
}Response (200):
{
"created": 15,
"skipped": 2,
"overwritten": 0,
"errors": []
}Export project data as CSV with UTF-8 BOM.
Auth: Yes
Query Parameters:
type--requirements,tests, orall
Response: CSV file download.
Server-side AI proxy. Sends prompts to the configured AI provider (API keys stay on server).
Auth: Yes
Request Body:
{
"prompt": "...",
"purpose": "test_generation",
"maxTokens": 2000,
"temperature": 0.3
}Response (200):
{
"text": "...",
"model": "claude-sonnet-4-20250514",
"tokensUsed": 1234
}List configured AI providers (API keys masked).
Auth: Yes
Test connection to an AI provider.
Auth: Yes
List all webhooks for the organization (secrets masked).
Auth: Yes
Create a new webhook.
Auth: Yes (admin)
Request Body:
{
"name": "CI Pipeline",
"url": "https://example.com/webhook",
"secret": "optional-hmac-secret",
"events": ["requirement.created", "test.failed"]
}Update a webhook. Auth: Yes (admin)
Delete a webhook. Auth: Yes (admin)
Send a test payload to the webhook. Auth: Yes (admin)
Connect to Jira Cloud. Validates credentials and project key.
Auth: Yes (admin)
Request Body:
{
"baseUrl": "https://yourcompany.atlassian.net",
"email": "user@example.com",
"apiToken": "...",
"projectKey": "PROJ"
}Check Jira connection status. Auth: Yes
Bidirectional sync between QAtrial and Jira. Auth: Yes
List Jira issues from the connected project. Auth: Yes
Import a Jira issue as a QAtrial requirement. Auth: Yes
Request Body:
{ "issueKey": "PROJ-123", "projectId": "uuid" }Connect to a GitHub repository. Validates token and repo.
Auth: Yes (admin)
Request Body:
{
"owner": "MeyerThorsten",
"repo": "QAtrial",
"token": "ghp_..."
}Check GitHub connection status. Auth: Yes
Link a GitHub PR to a QAtrial requirement. Auth: Yes
Request Body:
{ "prNumber": 42, "requirementId": "uuid" }Import test results from a GitHub Actions workflow run. Auth: Yes
Request Body:
{ "runId": 12345, "projectId": "uuid" }Generate a time-limited read-only audit link.
Auth: Yes (admin only)
Request Body:
{ "projectId": "uuid", "expiresIn": "24h" }Response (200):
{
"token": "eyJhbGci...",
"url": "/audit/eyJhbGci...",
"expiresAt": "2026-04-02T12:00:00.000Z"
}Read project data via audit mode token (no auth required).
Read requirements via audit mode token (no auth required).
Read tests via audit mode token (no auth required).
Read traceability matrix via audit mode token (no auth required).
Read evidence data via audit mode token (no auth required).
Read audit trail via audit mode token (no auth required).
Read electronic signatures via audit mode token (no auth required).
Weighted compliance readiness score.
Auth: Yes
Response (200):
{
"score": 78.5,
"metrics": {
"requirementCoverage": 0.85,
"testCoverage": 0.72,
"testPassRate": 0.90,
"riskAssessed": 0.65,
"signatureCompleteness": 0.50
}
}Requirements and tests without evidence.
Auth: Yes
Counts by entity type and approval status.
Auth: Yes
Open CAPAs with aging buckets (0-7d, 7-30d, 30-90d, 90d+).
Auth: Yes
Risk level counts and matrix data.
Auth: Yes
List all complaints for a project. Auth: Yes
Create a new complaint with intake form data. Defaults to status "received". Auth: Yes
Request Body:
{
"projectId": "uuid",
"title": "Device malfunction report",
"description": "Patient reported intermittent shutdown...",
"severity": "major",
"product": "CardioMonitor X100",
"regulatoryReportable": true
}Update a complaint. Status transitions are enforced server-side:
received -> investigating -> resolved -> closed
Supports FSCA tracking and CAPA linkage fields. Auth: Yes
Complaint trending dashboard data: by month, severity, product, and MTTR (mean time to resolution). Auth: Yes
Delete a complaint. Auth: Yes
List all suppliers with quality scorecards. Auth: Yes
Create a new supplier with performance metrics. Auth: Yes
Request Body:
{
"projectId": "uuid",
"name": "Precision Components Ltd.",
"defectRate": 0.02,
"onTimeDelivery": 0.95,
"riskScore": 72
}Update supplier metrics. Auto-requalification triggers when score < 50 (status set to "conditional"). Auth: Yes
Schedule or record an audit for a supplier. Auth: Yes
Delete a supplier record. Auth: Yes
List all PMS entries for a project. Auth: Yes
Create a new PMS entry. Supports PSUR data assembly. Auth: Yes
PMS summary dashboard with aggregated entries. Auth: Yes
Standard CRUD. Auth: Yes
List all UDI device identifiers. Auth: Yes
Create a new UDI entry with device identifier tracking. Auth: Yes
Export UDI data in GUDID or EUDAMED format. Auth: Yes
Standard CRUD. Auth: Yes
List all batch records for a project. Auth: Yes
Create a new batch record from a template. Auth: Yes
Request Body:
{
"projectId": "uuid",
"templateId": "uuid",
"batchNumber": "BATCH-2026-0042",
"product": "Aspirin 500mg"
}Execute a batch step. Supports deviations, review-by-exception, and yield calculation. Auth: Yes
E-signature release for a completed batch record. Auth: Yes (canApprove)
Delete a batch record. Auth: Yes
List all stability studies. Auth: Yes
Create a stability study with ICH Q1A design, storage conditions, and pull schedules. Auth: Yes
Add stability reading data. OOS/OOT auto-detection is applied. Auth: Yes
Trending charts data for a stability study. Auth: Yes
Standard CRUD. Auth: Yes
List all environmental monitoring points. Auth: Yes
Create a monitoring point with thresholds. Auth: Yes
Add a reading. Auto-excursion detection triggers when thresholds are breached. Auth: Yes
Environmental monitoring trending data. Auth: Yes
Standard CRUD. Auth: Yes
List training plans, courses, and records. Auth: Yes
Create a training plan. Auth: Yes
Create a training course. Auth: Yes
Record training completion for a user. Auth: Yes
Training matrix showing user/course completion status. Auth: Yes
Training compliance dashboard with auto-retraining trigger detection. Auth: Yes
List all documents with version history. Auth: Yes
Create a new document (SOP, work instruction, etc.). Starts in "draft" status. Auth: Yes
Request Body:
{
"projectId": "uuid",
"title": "SOP-001: Equipment Calibration",
"type": "sop",
"content": "..."
}Update a document. Status transitions (SOP versioning):
draft -> review -> approved -> effective -> superseded -> retired
Auth: Yes
Version history for a document. Auth: Yes
Distribution tracking for a document. Auth: Yes
Delete a document. Auth: Yes
Get requirement/test graph chains for a project. Auth: Yes
Run a what-if analysis: given a proposed change to a requirement, return all downstream affected tests, CAPAs, and documents. Auth: Yes
Request Body:
{
"projectId": "uuid",
"requirementId": "uuid",
"changeDescription": "Modify validation threshold from 95% to 99%"
}List all computerized systems in the inventory. Auth: Yes
Create a system entry with GAMP 5 category, validation status, and risk level. Auth: Yes
Request Body:
{
"projectId": "uuid",
"name": "LIMS v4.2",
"gampCategory": 4,
"validationStatus": "validated",
"riskLevel": "medium",
"nextReviewDate": "2027-01-15"
}Detect systems with overdue periodic reviews. Auth: Yes
Standard CRUD. Auth: Yes
Start a 7-step periodic review wizard for a system. Auto-pulls data from related records. Auth: Yes
Update periodic review progress (step completion). Schedules next review on completion. Auth: Yes
List all audit records (schedule, findings, actions). Auth: Yes
Create an audit record with schedule and classification. Auth: Yes
Request Body:
{
"projectId": "uuid",
"title": "Annual ISO 13485 Internal Audit",
"auditType": "internal",
"scheduledDate": "2026-06-15",
"scope": "Design Controls, CAPA, Document Control"
}Add a finding to an audit. Classification: observation, minor, major, or critical. CAPA linkage supported. Auth: Yes
Request Body:
{
"description": "Training records not updated after SOP revision",
"classification": "minor",
"capaId": "uuid"
}Standard CRUD. Auth: Yes
QAtrial uses JWT-based authentication. After login, include the access token in every API request:
Authorization: Bearer <accessToken>
Access tokens expire after 24 hours. Use the refresh token to get a new pair:
curl -X POST /api/auth/refresh \
-H "Content-Type: application/json" \
-d '{"refreshToken":"eyJhbGci..."}'When SSO is enabled, users authenticate via OIDC redirect flow. The callback endpoint exchanges the IdP code for QAtrial JWT tokens.
All endpoints return JSON error responses:
{ "message": "Human-readable error description" }| Status Code | Meaning |
|---|---|
200 |
Success |
201 |
Created |
400 |
Validation error |
401 |
Authentication error |
403 |
Authorization error (insufficient permission) |
404 |
Not found |
409 |
Conflict |
500 |
Internal server error |
QAtrial uses 20 Zustand stores with localStorage persistence. See Architecture > State Management for the full store reference table.
File: src/ai/client.ts
Sends a prompt to the resolved AI provider.
interface CompletionRequest {
prompt: string;
purpose: LLMPurpose;
maxTokens?: number;
temperature?: number;
}
interface CompletionResponse {
text: string;
model: string;
providerId: string;
tokensUsed: number;
}
async function complete(request: CompletionRequest): Promise<CompletionResponse>Purpose routing: The function checks for a server-side proxy URL first, then resolves the provider from useLLMStore based on the purpose. If VITE_AI_PROXY_URL is set or the server's /api/ai/complete endpoint is configured, it proxies through the server.
File: src/ai/validation.ts
Parses AI response text and validates against a JSON schema:
function safeParse<T>(text: string, schema: JsonSchema): TStrips markdown code fences, parses JSON, validates against schema. Throws ValidationError on failure.
Combines complete() with schema validation and automatic retry:
async function completeWithValidation<T>(options: {
prompt: string;
purpose: LLMPurpose;
schema: JsonSchema;
maxRetries?: number;
}): Promise<T>File: src/templates/composer.ts
Composes templates from country, vertical, project type, and modules:
function composeTemplate(config: {
country: string;
vertical?: IndustryVertical;
projectType?: ProjectType;
modules?: string[];
}): { requirements: TemplateRequirement[]; tests: TemplateTest[] }File: src/templates/packs/index.ts
interface CompliancePack {
id: string;
name: string;
description: string;
icon: string;
country: string;
vertical: string;
projectType: string;
modules: string[];
tags: string[];
}
const COMPLIANCE_PACKS: CompliancePack[] // 4 packsFile: src/connectors/types.ts
type ConnectorType = 'jira' | 'azure_devops' | 'csv' | 'custom';
interface Connector {
id: string;
type: ConnectorType;
name: string;
connect(config: ConnectorConfig): Promise<boolean>;
disconnect(): Promise<void>;
sync(direction: SyncRecord['direction']): Promise<SyncRecord>;
testConnection(): Promise<{ ok: boolean; message: string }>;
}File: src/lib/pdfExport.ts
function exportReportAsPDF(options: {
projectName: string;
version: string;
sections: ReportSection[];
includeSignatures: boolean;
}): voidGenerates a professional PDF with cover page, table of contents, report sections, and optional signature blocks.
function apiFetch<T>(path: string, options?: RequestInit): Promise<T>Authenticated fetch wrapper. Injects Bearer token from localStorage, handles error responses.
function generateId(prefix: string, counter: number): string
// generateId("REQ", 1) => "REQ-001"
// generateId("TST", 42) => "TST-042"type RequirementStatus = 'Draft' | 'Active' | 'Closed';
type TestStatus = 'Not Run' | 'Passed' | 'Failed';
type CAPAStatus = 'open' | 'investigation' | 'in_progress' | 'verification' | 'resolved' | 'closed';
type RiskLevel = 'low' | 'medium' | 'high' | 'critical';
type SignatureMeaning = 'authored' | 'reviewed' | 'approved' | 'verified' | 'rejected';
type GapStatus = 'covered' | 'partial' | 'missing';
type UserRole = 'admin' | 'qa_manager' | 'qa_engineer' | 'auditor' | 'reviewer';
type LLMPurpose =
| 'all'
| 'test_generation'
| 'gap_analysis'
| 'risk_classification'
| 'report_narrative'
| 'requirement_decomp'
| 'capa'
| 'quality_check';
type ProjectType =
| 'software'
| 'embedded'
| 'quality_system'
| 'validation'
| 'clinical'
| 'compliance'
| 'supplier_quality'
| 'empty';
type IndustryVertical =
| 'pharma'
| 'biotech'
| 'medical_devices'
| 'cro'
| 'clinical_lab'
| 'logistics'
| 'software_it'
| 'cosmetics'
| 'aerospace'
| 'chemical_env';
type AuditAction =
| 'create' | 'update' | 'delete' | 'status_change'
| 'link' | 'unlink' | 'approve' | 'reject' | 'sign'
| 'export' | 'generate_report'
| 'ai_generate' | 'ai_accept' | 'ai_reject'
| 'login' | 'logout' | 'import';
type WebhookEvent =
| 'requirement.created' | 'requirement.updated' | 'requirement.deleted'
| 'test.created' | 'test.updated' | 'test.failed'
| 'capa.created' | 'capa.status_changed'
| 'approval.requested' | 'approval.approved' | 'approval.rejected'
| 'signature.created'
| 'evidence.uploaded';interface QualityIssue {
type: 'vague' | 'untestable' | 'ambiguous' | 'incomplete' | 'duplicate_risk' | 'missing_criteria';
severity: 'error' | 'warning' | 'info';
message: string;
suggestion: string;
}// Complaint Management (Medical Device)
type ComplaintStatus = 'received' | 'investigating' | 'resolved' | 'closed';
type ComplaintSeverity = 'minor' | 'major' | 'critical';
interface Complaint {
id: string;
projectId: string;
title: string;
description: string;
status: ComplaintStatus;
severity: ComplaintSeverity;
product: string;
regulatoryReportable: boolean;
fscaReference?: string;
capaId?: string;
createdAt: string;
resolvedAt?: string;
}
// Supplier Quality Scorecards (Medical Device)
type SupplierStatus = 'approved' | 'conditional' | 'disqualified' | 'pending';
interface Supplier {
id: string;
projectId: string;
name: string;
defectRate: number;
onTimeDelivery: number;
riskScore: number;
status: SupplierStatus;
nextAuditDate?: string;
}
// Electronic Batch Records (Pharma)
type BatchStatus = 'in_progress' | 'pending_review' | 'released' | 'rejected';
interface BatchRecord {
id: string;
projectId: string;
batchNumber: string;
product: string;
status: BatchStatus;
templateId: string;
steps: BatchStep[];
yield?: number;
releasedBy?: string;
releasedAt?: string;
}
// Stability Study (Pharma)
type StabilityCondition = 'long_term' | 'intermediate' | 'accelerated';
interface StabilityStudy {
id: string;
projectId: string;
product: string;
condition: StabilityCondition;
pullSchedule: string[];
readings: StabilityReading[];
oosDetected: boolean;
ootDetected: boolean;
}
// Environmental Monitoring (Pharma)
interface MonitoringPoint {
id: string;
projectId: string;
location: string;
parameter: string;
thresholdMin?: number;
thresholdMax?: number;
alertThreshold?: number;
actionThreshold?: number;
}
// Training Management (Pharma)
type TrainingStatus = 'planned' | 'in_progress' | 'completed' | 'expired';
interface TrainingRecord {
id: string;
userId: string;
courseId: string;
planId: string;
status: TrainingStatus;
completedAt?: string;
expiresAt?: string;
}
// Document Lifecycle (Cross-Vertical)
type DocumentStatus = 'draft' | 'review' | 'approved' | 'effective' | 'superseded' | 'retired';
interface Document {
id: string;
projectId: string;
title: string;
type: string;
status: DocumentStatus;
version: number;
content: string;
history: DocumentVersion[];
}
// Computerized System Inventory (Software/GAMP)
type GAMPCategory = 1 | 3 | 4 | 5;
type ValidationStatus = 'not_validated' | 'in_progress' | 'validated' | 'retired';
interface ComputerizedSystem {
id: string;
projectId: string;
name: string;
gampCategory: GAMPCategory;
validationStatus: ValidationStatus;
riskLevel: RiskLevel;
nextReviewDate?: string;
}
// PMS (Medical Device)
interface PMSEntry {
id: string;
projectId: string;
source: string;
description: string;
reportPeriod: string;
psurIncluded: boolean;
}
// UDI Management (Medical Device)
interface UDIEntry {
id: string;
projectId: string;
deviceIdentifier: string;
productionIdentifier?: string;
deviceName: string;
gudidRegistered: boolean;
eudamedRegistered: boolean;
}
// Audit Management (Cross-Vertical)
type FindingClassification = 'observation' | 'minor' | 'major' | 'critical';
interface AuditRecord {
id: string;
projectId: string;
title: string;
auditType: string;
scheduledDate: string;
findings: AuditFinding[];
}
interface AuditFinding {
id: string;
description: string;
classification: FindingClassification;
capaId?: string;
}See src/types/index.ts for the full set of 80+ type definitions.