Security
You are about to trust us with your invoicing, your guests' details and your clients' addresses. This page says what is done and, at the end, what is missing — because the second list tells you more about us than the first.
Isolation
Most platforms put every client in the same tables and separate them with a column. One query written in a hurry, missing the filter, and one client sees another's data. Here that query has nothing to find.
The usual way
WHERE tenant_id = ?
Every client in the same tables. The separation is a filter someone has to remember to write, on every query, forever. Forgetting once is enough.
Here
USE ajja_tenant_…
Each client in their own database. The query missing its filter still returns only what is yours, because the connection was pointed at your side before the query existed.
ajja_tenant_<slug>
DROP DATABASE
who you are · what you pay for · which modules
Accounts and access
Every one of these checks runs on the server. None of them depends on what the browser decides to show or hide.
Two-factor authentication with the app you already use. The secret only takes effect once you confirm a code generated from it, so nobody gets locked out by a misread QR. You get 8 recovery codes, and both they and the secret are encrypted in the database.
Between the right password and the code on your phone sits a temporary ticket that lasts five minutes. After that you start over — no half-finished login is left waiting.
The login is capped by email and by address at the same time: five attempts a minute for the same pair, twenty a minute for the same address. Counting the email alone would let a stranger lock you out of your own account by failing on purpose. Two-factor, signup and the public check-in and booking forms are capped too. The authenticated API is limited to 120 requests a minute per user, not per address — two colleagues in one office do not share each other's budget.
Hiding a menu item is not security. Every address passes two separate checks — the module is active for this client, and your role can see that module — and answers 403 even to someone typing the address by hand.
A valid token aimed at another client's name reads nothing. And the answer is 404, not 403: someone guessing client names doesn't get to learn which ones exist.
Guests and owners come in through a link with its own key, which opens one thing and can carry an expiry date. There is no account to steal from them and no password to reuse somewhere else.
One request, start to finish
This is what happens between you pressing a button and the result appearing. The order is fixed at application boot, not left to how each route happened to be written — because a check that runs too late has already let the request start working.
The custom domain, the subdomain or the request header says which client it belongs to. Unknown name, it ends here.
A suspended workspace is not served, not even for reading.
The database connection is switched to that client's own. Everything after this, mistakes included, can only reach this side.
The token is verified. Without a valid one there is no step five.
120 a minute, counted per user. Above that the server says enough.
The token's user and the request's client have to be the same. This is the join that stops a legitimate token from reading the house next door.
The module has to be active for the client and visible to your role. Two checks, not one.
Only after those seven do the numbers in the address turn into records — and always out of the database step three picked.
What gets recorded
A system that records nothing forces you to take our word for it. We would rather you could check.
actor · action · subject · before/after
Administrative actions with weight — suspending a client, changing a plan's price, deactivating a coupon — record who did it, to what, and the value before and the value after. It is written at each use on purpose: what a machine does on its own, like a payment confirming itself, is nobody's action and does not belong on this list.
tenant.impersonated
Support can enter your account to solve a problem. That is recorded with a name and a time, and while it lasts you get a bar at the top of the screen saying it is happening. It is not silent.
type · payload · status · error
Every payment event received keeps its type, its payload, its status and the error message if it failed. The same event arriving twice is recognised and not counted twice.
no request body
When something breaks we keep the message, the file, the line and the address that failed. The request body is not stored — investigating an error does not require keeping the invoice you were about to save.
The data is yours
A platform that makes leaving hard is admitting it can't hold you any other way.
one request, one file
no penalty
What comes out
What we don't have yet
None of this is promised or contracted. It is here so you know what you are buying today.
Exporting is done. Deleting one individual's data while keeping the rest is still manual work — the only automatic deletion is of an entire client.
If you are about to trust us with other people's data, you are owed concrete answers before you sign. Create an account and ask, or ask first.
14 days free, with your card checked without a charge. Cancel before the 15th day after you subscribe and you pay nothing.