Files
open-safetylog-ehs/docs/API.md
T
jayjennings c69eccb5c1
CI / Lint, typecheck, test, build (push) Canceled after 0s
CI / End-to-end tests (push) Canceled after 0s
feat: implement incident reporting and management system
- Add incident reporting routes and logic to handle creation, updating, and retrieval of incidents.
- Implement visibility rules for users based on roles (reporters, supervisors).
- Create tests for incident reporting functionality to ensure correctness and reliability.
- Introduce site and area management routes, including validation for area hierarchy.
- Establish user management routes with role-based access control for creating and updating users.
- Add setup routes for initializing the application with an organization and admin user.
- Implement shared enums and schemas for consistent validation and data handling across the application.
- Create a test helper for setting up an in-memory database and API client for testing.
- Add TypeScript configuration for server-side code with strict type checking and module resolution.
2026-10-06 10:51:18 -07:00

4.0 KiB
Raw Blame History

API reference

JSON over HTTP under /api. Authentication uses a session cookie (safetylog_session, http-only) set by setup, sign-in and password change.

Request bodies are validated against the schemas in shared/schemas.ts. Errors look like { "error": "message" }; validation errors (400) also include issues: [{ path, message }].

Roles, from least to most privileged: reporter < supervisor < safety_manager < admin. A role can do everything the roles below it can.

Setup and authentication

Method Path Who Notes
GET /api/health anyone { ok: true }
GET /api/setup anyone { needsSetup }: true until the first admin exists
POST /api/setup anyone, once { organizationName, name, email, password }. Creates the organization and an admin, signs them in. 409 afterwards
POST /api/auth/login anyone { email, password }. 401 on bad credentials, 429 after 10 attempts per email in 15 minutes
POST /api/auth/logout anyone Ends the session. 204
GET /api/auth/me signed in { user, organization }
POST /api/auth/password signed in { currentPassword, newPassword }. Signs out all other sessions. 204

Passwords need at least 10 characters.

Users

Method Path Who Notes
GET /api/users safety_manager All users in the organization
POST /api/users admin { name, email, role, password }. 409 if the email exists
PATCH /api/users/:id admin { name?, role?, active? }. Deactivating signs the user out. The last active admin can't be demoted or deactivated

Sites and areas

Method Path Who Notes
GET /api/sites signed in Sites with their areas (flat list; build the tree from parentId). ?includeArchived=true to include archived
POST /api/sites safety_manager { name, address?, timezone? }
PATCH /api/sites/:id safety_manager { name?, address?, timezone?, archived? }
POST /api/sites/:id/areas safety_manager { name, kind?, parentId? }. kind: building, area (default), line, other. Parent must be in the same site
PATCH /api/areas/:id safety_manager { name?, kind?, archived? }

Incidents

Incidents are addressed by their number (/api/incidents/42 is INC-0042). Numbers are sequential per organization with no gaps.

Method Path Who Notes
GET /api/incidents signed in Paginated { items, page, pageSize, total }, newest first. Reporters see only their own reports
POST /api/incidents signed in Create a report (below). 201
GET /api/incidents/:number signed in 404 if it doesn't exist or isn't visible to you
PATCH /api/incidents/:number supervisor Any create field plus status. Closing sets closedAt; reopening clears it. Moving to another site clears the area unless one is given

List query parameters: page (default 1), pageSize (1–100, default 25), q (searches title, description and location; INC-0042 or 42 matches the number), type, severity, status, siteId.

Create body:

{
  "title": "Forklift reversed into pallet stack",
  "type": "near_miss",
  "severity": "high",
  "occurredAt": "2026-10-06T08:15:00-05:00",
  "siteId": "…",
  "areaId": "…",
  "locationDetail": "Loading bay C",
  "description": "…",
  "immediateAction": "Area cordoned off",
  "anonymous": false
}
  • type: near_miss, hazard, injury, illness, property_damage, environmental
  • severity: low, medium, high, critical
  • status: reported, triage, investigation, actions, verification, closed
  • occurredAt must include a time-zone offset and can't be in the future.
  • Anonymous reports don't record the reporter at all, so they won't appear in the reporter's own list.

Every create and update is recorded in the audit log (audit_log table) with who made it and what changed.