Isolation by construction
Every application table carries the organisation it belongs to, and every query filters on it before anything else. A client's portal session is authorised per connection: the guarantee that holds over HTTP holds over the live socket too.
A correctness property, not a feature you switch on
- Every table
- Carries an organisation id
- Every query
- Filters on it first
- Client contacts
- A separate, closed-by-default record, not a staff role with things removed
- Live updates
- Authorised per websocket connection, same rule as HTTP
Their work, and nothing else
Each client sees their own projects, files and invoices. There is no query in the app that can return another client's rows, because there is no query without the scope on it.
WHERE organisation_id = … AND client_id = …Fewer of them, on purpose
Fonts are shipped with the app rather than fetched from Google at build time. Small thing, but it's the same instinct: a product that handles your clients' work shouldn't quietly add third parties to the pipeline.
Sub-processors for the managed service
Providers to confirm before launch.
| Sub-processor | Purpose | Data | Location |
|---|---|---|---|
| [Hosting provider] | Servers and the Postgres database | All application data | [UK / EU] |
| [Object storage provider] | Files and generated PDFs | Uploaded files, invoices, quotes | [UK / EU] |
| [Email provider] | Invitations, invoices, reminders | Recipient email, message content | [Region] |
| Stripe | Online invoice payment | Payer and payment details | Per provider |
| GoCardless | Direct debit payment | Payer and payment details | Per provider |
Self-hosted installs choose their own storage, email and payment providers; this list applies to the managed service only.
Yours to take, yours to end
- Export
- Your data comes out without a support ticket
- Deletion
- If you stop paying, your data stays exportable, then is deleted on a published schedule of [00] days