Workspace isolation
Every workspace gets its own dedicated database schema. There is no shared "data" table behind a workspace_id filter — a query in workspace A literally cannot see workspace B's rows because the schema isn't on its search path.
Schema-per-workspace · enforced at the database connection
Encrypted at rest
Integration access tokens, signup payloads and other sensitive fields are encrypted with AES-128-CBC + HMAC-SHA256 using authenticated symmetric encryption. The keys live outside the database — even a stolen DB dump is inert.
AES-128 + HMAC-SHA256 · key rotation supported
Server-side permissions
Permissions are checked on the server, not in the UI. Every protected endpoint filters its data by who's asking, what they're doing and whose data they're touching — on every request. The web client mirrors the rules for UX, but a forbidden API call never reaches the row.
Server-enforced on every query · permissions checked per request
EU hosting
Data lives in EU regions (Belgium / Frankfurt). Backups, replicas, and worker queues never leave the EU. The application clock is Europe/Brussels and currency is € — both intentional, both relevant for procurement.
Belgian-incorporated · EU data residency · no US sub-processors for storage
Audit history
Every change to tasks, comments, time entries, attachments and tags is recorded in an immutable history table. Queryable for compliance, exportable on Business. Tag adds / removes and viewer changes get their own audit events — not just diffs of the parent row.
retention: 90 d (Starter) · 1 y (Pro) · 3 y (Business)
Account safety
Passwords hashed with Argon2 — the current state-of-the-art, winner of the Password Hashing Competition. Resets use a 6-digit one-time code with a 15-minute lifetime and a hard 5-attempt cap. Brute-force protection through repeated-failure lockout; signup endpoints rate-limited per IP and per email domain.
Argon2 · brute-force lockout · 15-min code lifetime · 5-attempt limit
Last-admin safeguard
The API refuses to deactivate the workspace's last admin, demote the last admin's role, or delete the last admin user — returning a clear error instead. The org-locking accident that takes down a workspace at 4 PM on a Friday literally cannot happen.
Enforced server-side · covered by automated tests
Documented API
Every endpoint is described by an auto-generated OpenAPI 3 schema at /api/schema/ with interactive Swagger UI and ReDoc. Our own TypeScript clients regenerate from the same schema, so the docs can't drift from the code.
Public OpenAPI schema · Swagger UI & ReDoc · auto-generated typed clients