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
Production data lives with Scaleway in Paris (fr-par). 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