Every REST endpoint exposed by the EHS Pulse backend, generated directly from the route
definitions in backend/src/routes/. All endpoints are served under the API's base
URL (e.g. http://localhost:9000 in the default Docker setup) and, unless marked
Public, require an Authorization: Bearer <token> header obtained from
POST /api/auth/login. Tenant scoping is automatic — the token determines which
tenant's data a request can see; a Super Admin token may target another tenant by sending an
X-Tenant-Id header (surfaced in-app via the tenant switcher in the topbar).
Updates a tenant's branding, plan, storage allocation, and password policy.
RequestAny subset of tenant fields
Response{ message }
POST/api/tenants/:id/activateSuper Admin
Reactivates a suspended tenant.
Request—
Response{ message }
POST/api/tenants/:id/deactivateSuper Admin
Suspends a tenant (blocks login for all its users).
Request—
Response{ message }
Users /api/users
Tenant user management. All endpoints require the manage_users permission and are scoped to the caller's tenant (except Super Admin, who may target other tenants via X-Tenant-Id) — except /lookup, deliberately open to any authenticated tenant user.
GET/api/users/lookupBearer token
Lightweight user list ({id, name, email, department} for active users) — backs user_lookup fields ('Inspector', 'Assigned To') so anyone filling a form can search real users, without needing the admin-only manage_users permission GET / below requires.
Request—
Response{id, name, email, department}[]
GET/api/users/site-optionsBearer token
Backs the Assigned Site dropdown on the user edit form — the actual value/label options from whichever master data set(s) are currently configured as a site-restriction gate (module_fields.site_restricted), so it always reflects real configuration rather than a guessed set.
Request—
Response{value, label}[]
GET/api/users/manage_users
Lists every user in the tenant along with their assigned roles.
Request—
ResponseUser[]
POST/api/users/manage_users
Creates a user with a random temporary password; sends an activation email if SMTP is configured and active.
RBAC configuration — roles, module/global permissions, field-level access, and single-level role inheritance (editable after creation, not just set at creation).
GET/api/roles/permission-keysBearer token
Returns the fixed list of permission key constants the UI can assign (view, create, edit, delete, approve, export, print, manage_users, ...).
Request—
Responsestring[]
GET/api/roles/manage_roles
Lists roles for the tenant with their permission sets and inherited-role name.
Request—
ResponseRole[]
POST/api/roles/manage_roles
Creates a role, optionally inheriting another role's allow permissions.
Replaces a role's field-level access (view/edit/hide) for one module.
Request{ moduleId, fields: [{fieldKey, access}] }
Response{ message }
Modules /api/modules
The Dynamic Module Builder — define and version a module's field schema, including cross-module record_lookup fields, archive/reactivate, duplication, and a printable blank-form PDF export.
GET/api/modules/Bearer token
Lists modules for the tenant with a live record count per module. Archived (status='inactive') modules are excluded by default — pass ?status=all to include them (used by the Module Builder's module picker, so an archived module can still be found and reactivated).
RequestQuery: status?
ResponseModule[]
GET/api/modules/:idBearer token
Gets one module with its ordered field schema.
Request—
ResponseModule + fields[]
GET/api/modules/:id/child-modulesBearer token
Which other module(s) hold "child" records of this one — i.e. have a record_lookup field whose target is this module. Schema-level version of GET /records/:id/children below — used by the Guided Checklist flow to find where its per-item answers belong without hardcoding a module id.
Updates module metadata (name/description/icon/category/status); bumps the version number. Also how a module is reactivated (status: 'active').
RequestAny subset of module fields
Response{ message }
PUT/api/modules/:id/fieldsmanage_modules
Replaces the field schema. Existing record data is never touched by a schema change.
Request{ fields: Field[] }
Response{ message }
DELETE/api/modules/:idmanage_modules
Archives a module (status → 'inactive'); soft delete. Existing records and any dashboards/reports built on it stay fully intact — this only hides it from the sidebar nav.
Request—
Response{ message }
GET/api/modules/:id/exportBearer token
Exports a module's structure as portable JSON — name/description/icon/category plus the full field schema. A master_lookup/record_lookup field's dependency is resolved to that master data set's/module's *code* rather than its internal database id, so the file means the same thing on a different tenant or a fresh install, not just as a same-tenant backup.
Creates a new module from the JSON structure the export endpoint above produces (or a hand-written equivalent). The requested code is auto-suffixed on collision rather than rejected outright. A master_lookup/record_lookup field's masterDataSetCode/targetModuleCode is resolved against this tenant's own master data sets/modules; if nothing matches, the field is still created without that link configured and reported back as a warning rather than failing the whole import.
Generates a printable blank paper form — every field laid out with somewhere to write by hand, grouped by section, master_lookup options printed as checkboxes, signature fields get a boxed sign-and-date line.
Request—
ResponsePDF file
Records /api/records
Generic CRUD for data stored against any dynamic module, validated against that module's live field schema — including conditional-visibility-aware required-field enforcement.
GET/api/records/module/:moduleIdview
Lists records for a module — paginated, with optional status filter and free-text search.
RequestQuery: page, pageSize, status?, search?
Response{ rows[], total, page, pageSize }
GET/api/records/:idview
Gets one record with its full workflow history.
Request—
ResponseRecord + history[]
GET/api/records/:id/childrenview
Reverse lookup — every other record, in any module, whose record_lookup field points at this one (e.g. every Audit Checklist Item belonging to a Safety Audit & Inspection header, or every Corrective Action raised against a checklist item). Generic, not hardcoded to one relationship; grouped by the field that references this record.
Bulk-imports up to 500 rows at once (used by CSV import and the Guided Checklist flow), validating each row independently.
Request{ rows: object[] }
Response{ created, failed: [{row, reasons}] }
POST/api/records/module/:moduleIdcreate
Creates one record; auto-generates its record number and initial workflow stage if the module has an active workflow. For any master_lookup field pointing at a hierarchical master-data set, if the submitted value is a child (e.g. an Area under a Department), also derives and stores its top-level ancestor under `${field_key}_parent` — so dashboards can group by either the specific value picked or the rolled-up ancestor.
Request{ data: object }
Response{ id, recordNo }
PUT/api/records/:idedit
Updates a record (partial merge with existing data), re-validated against the schema.
Request{ data: object }
Response{ message }
DELETE/api/records/:iddelete
Permanently deletes a record.
Request—
Response{ message }
Workflows /api/workflows
No-code workflow builder and execution engine — stages, role-based transitions, field-value conditions, and the resulting record-level action history.
GET/api/workflows/my-tasksBearer token
Powers the homepage ‘My Pending Tasks’ widget — aggregates across every module with an active workflow: records sitting in a stage whose outgoing transition is assigned to a role the current user holds, and records with a user_lookup field (e.g. Assigned To) pointing at the current user. Scoped to non-final stages only.
Changes a workflow's status (active / inactive / draft).
Request{ status }
Response{ message }
POST/api/workflows/records/:recordId/actionedit
Executes a transition on a record — validates the action is legal from its current stage and that any field-value conditions on the transition are met, then applies it and sends any matching notification.
Returns the actions currently valid for a record, pre-filtered to ones whose conditions already pass — so the UI never shows a button that would fail.
Request—
Response{ action_name, to_stage }[]
Dashboards /api/dashboards
Dashboard builder — widgets computed live from module data, with count or sum/avg/min/max aggregation per group, drag-and-drop ordering, and optional auto-refresh.
GET/api/dashboards/Bearer token
Lists dashboards visible to the caller (own, department, or organization-wide).
Request—
ResponseDashboard[]
POST/api/dashboards/Bearer token
Creates a dashboard.
Request{ name, visibility?, refreshSeconds? }
Response{ id }
PUT/api/dashboards/:idBearer token
Updates a dashboard's name, visibility, or auto-refresh interval.
Request{ name?, visibility?, refreshSeconds? }
Response{ message }
POST/api/dashboards/:id/widgetsBearer token
Adds a widget to a dashboard. widgetType includes 'funnel' (how many records have ever reached each of the module's workflow stages, via workflow_history — not just the current-moment distribution). config supports an optional filter ({field, operator, value}, operators: equals/not_equals/gt/lt/gte/lte/in) narrowing which records are counted or grouped, and an optional dateTrunc: 'month' on a date groupBy field for month-bucketed trends.
Bulk-updates widget display order after a drag-and-drop reorder.
Request{ order: [{id, order}] }
Response{ message }
GET/api/dashboards/:idBearer token
Gets a dashboard with every widget's data computed live at request time.
Request—
ResponseDashboard + widgets[] (each with .data)
DELETE/api/dashboards/:idBearer token
Deletes a dashboard and its widgets.
Request—
Response{ message }
Reports /api/reports
Custom report builder — field selection, filters, grouping with sum/avg/min/max aggregation, CSV/PDF export, and scheduled email delivery (handled by the background scheduler).
GET/api/reports/Bearer token
Lists report definitions for the tenant.
Request—
ResponseReport[]
POST/api/reports/export
Creates a report definition — fields, filters, optional grouping/aggregation, and an optional daily/weekly/monthly email schedule.
Executes a report immediately and returns the resulting rows (grouped/aggregated if configured).
Request—
Response{ rows[], count }
GET/api/reports/:id/export.csvexport
Downloads the report's current results as a CSV file.
Request—
ResponseCSV file
GET/api/reports/:id/export.pdfexport
Downloads the report's current results as a simple tabular PDF.
Request—
ResponsePDF file
DELETE/api/reports/:idexport
Deletes a report definition.
Request—
Response{ message }
Module Notifications /api/module-notifications
Configurable notification and reminder emails — a fully customizable sibling to the fixed six BRD notification triggers and the Schedule feature. Two trigger types: on_create fires once immediately with record-specific merge tokens; recurring fires on a schedule with an optional end date.
GET/api/module-notifications/Bearer token
Lists notifications — scoped to one module with ?moduleId=, or every notification in the tenant otherwise.
RequestQuery: moduleId?
ResponseModuleNotification[]
POST/api/module-notifications/manage_modules
Creates a notification. For triggerType=recurring, frequency is required; endDate is optional and, once past, the notification stops firing on its own without needing to be paused. Subject/body support {{record_no}} and {{field_key}} merge tokens, resolved for on_create only (a recurring reminder has no single record to merge against).
Live preview of a subject/body as currently drafted, before saving. For triggerType=on_create, merge tokens are resolved against the module's single most recently created record (a real one, not placeholder text) — user_lookup fields resolve to the person's name. A recurring reminder has no per-record context, so it returns the raw text unchanged, matching actual send behavior.
Request{ moduleId, triggerType, subject, body }
Response{ subject, body, usedRecord, note? }
PUT/api/module-notifications/:idmanage_modules
Pauses or resumes a notification.
Request{ isActive }
Response{ message }
DELETE/api/module-notifications/:idmanage_modules
Deletes a notification.
Request—
Response{ message }
Inspection Schedules /api/schedules
Recurring inspection reminders (Mitti's "Create schedule" template action) — can't auto-generate inspection data since answering a checklist needs a human, so this reminds someone instead: a daily/weekly/monthly email, sent by the same background scheduler that handles report delivery and SLA escalation.
GET/api/schedules/Bearer token
Lists reminder schedules — scoped to one module with ?moduleId=, or every schedule in the tenant otherwise.
RequestQuery: moduleId?
ResponseSchedule[]
POST/api/schedules/manage_modules
Creates a recurring reminder schedule for a module.
Shared named lookup lists — back Master Data Lookup module fields, and double as the source for the Guided Checklist flow's predefined checklists (a set coded checklist_<type>).
GET/api/master-data/Bearer token
Lists master data sets for the tenant with a value count per set.
Request—
ResponseMasterDataSet[]
GET/api/master-data/:idBearer token
Gets one set with its full ordered value list.
Request—
ResponseMasterDataSet + values[]
GET/api/master-data/:id/valuesBearer token
Lightweight value list only — what data-entry dropdowns actually fetch.
Request—
Response{ value, label }[]
POST/api/master-data/manage_modules
Creates a new master data set.
Request{ name, code, description? }
Response{ id }
PUT/api/master-data/:idmanage_modules
Updates a set's name/description.
Request{ name?, description? }
Response{ message }
DELETE/api/master-data/:idmanage_modules
Deletes a set (fields linked to it lose their option list).
Request—
Response{ message }
PUT/api/master-data/:id/valuesmanage_modules
Replaces the full value list for a set in one save.
Request{ values: [{value, label}] }
Response{ message }
POST/api/master-data/:id/values/addmanage_modules
Adds one value, optionally with a parent (for a two-level hierarchy, e.g. Department -> Area) and a head user.
Bulk imports values from Excel/CSV — the safe alternative to the destructive bulk-replace endpoint above for a set with hierarchy, since every row is an individual create-or-update rather than a delete-everything-then-reinsert. parent and headEmail are human-editable references (a value code, an email address), not raw ids — a parent referenced by a later row resolves correctly as long as it appears earlier in the same file. Limited to 500 rows.
Deletes one value. Any values nested under it (parent_value_id pointing at it) are deleted too.
Request—
Response{ message }
Files /api/files
File upload backing the File / Image / Signature field types, storing tenant-scoped attachments on a persistent Docker volume.
POST/api/files/uploadBearer token
Uploads one file (multipart/form-data, field name "file"). 15MB limit; blocks executable file types. Also how a drawn signature is saved — the canvas is captured as a PNG and uploaded through this same endpoint.