Separate customer data, access that fits the role, masked personal data and an append-only ledger, enforced in the database and the API.
The four rules
tenancy
Your data stays separate
The database itself keeps one organisation's records out of another's.
How it works›
Every table carries the organisation and runs under FORCE ROW LEVEL SECURITY, so the policy applies to the table’s owner too. The tenant is bound to the transaction, not the connection, because a pool hands the same connection to the next request.
SQLper-request tenant bindingtransaction-local
-- every request opens a transaction, and binds itselfBEGIN;
SELECT set_config('app.organization_id', $1, true); -- true = this transaction onlySELECT id, number, total_minor, currency
FROM finance.invoices
WHERE organization_id = $1; -- filtered anyway, on purposeCOMMIT;
NOTEThe third argument is what makes it transaction-local. Without it the setting outlives the request on a pooled connection, and the next caller inherits the wrong organisation.
permissions
Access that fits the role
People see their own records, their team, a location or the whole organisation, and no more.
How it works›
The same “read invoices” grant means four different things depending on how far it reaches, and the scope is injected into the query rather than checked after the rows come back. A product’s administrator role is built from that product’s own permissions, never from “all permissions”.
OWNOnly the records this person owns.LEVEL 1
TEAMTheir team, down the reporting line.LEVEL 2
LOCATIONEverything at a site or branch.LEVEL 3
ORGThe whole organisation.WIDEST
Authorisation is server-side. A hidden button is presentation; every endpoint carries its own negative-authorisation test.
GET/api/v1/finance/invoicesfinance.invoices.read
# the route declares its permission, the selector applies the scopescope = ctx.scope_for("finance.invoices.read")
query = ctx.apply(select(Invoice)) # org filter + scope, before execution
NOTEThe scope narrows the query before it runs. Nothing is fetched and then filtered in the application, so there is no window in which the wrong rows exist in memory.
privacy
Personal data stays masked
Revealing a number takes its own permission, and every reveal is logged.
How it works›
Personal data is encrypted at rest and returned masked by default. Revealing a number takes its own permission and writes an audit row every time: who, what, when. A WhatsApp or email notification can still reach that contact without anyone holding the plaintext number.
NOTERevealing the number is a separate call against a separate permission, and it writes an audit row whether or not anybody ever reads it.
money
A ledger nobody quietly edits
Invoices are append-only. A correction is a new entry, so the history stays true.
How it works›
Every amount is a whole number of minor units with an ISO currency beside it. ₹18,462.00 is stored, moved and totalled as 1846200. Invoices, usage events and the credit ledger are append-only, and a correction is a new row.
// 201: a correction is a new row, never an edit
{
"credit_note_id": "c41b7e02-…",
"corrects_invoice_id": "8f21c4de-…",
"amount_minor": -246200,
"currency": "INR",
"original_unchanged": true
}
NOTENothing was updated. The original invoice row is exactly as it was issued, and the correction stands beside it with its own identity.
Each platform guarantee, what it means in practice, and the failure it prevents.
GUARANTEE
WHAT IT MEANS
WHAT IT PREVENTS
Organisation on every table
Every table carries the organisation column, and every table has FORCE ROW LEVEL SECURITY on it, including for the table's owner.
A query that forgets the filter returns nothing, rather than another customer's rows.
Tenant bound per transaction
The organisation is set on the transaction, not on the connection, because the pool hands the same connection to the next request.
The classic connection-pool leak, where request two inherits request one's tenant.
Scoped permissions
A grant names what and how far: own records, the team, a location, or the organisation.
A product administrator quietly holding a blanket grant over every other product you own.
PII masked by default
Personal data is encrypted and returned masked. Unmasking needs its own permission and writes an audit row every single time.
A reveal nobody can account for afterwards, and a support tool that quietly decrypts a whole table.
Money as integer minor units
Whole minor units with an ISO currency. No floats, and no two-decimal numeric standing in for one.
The half-paisa that reconciles on screen and not in the ledger.
Append-only records
Invoices, usage events and the credit ledger are append-only. A correction is a new row.
A history that changed shape between the close and the audit.
One signing key
Exactly one service holds a private signing key. Every other service verifies through JWKS and holds nothing secret.
A key copied into nine services, where rotating it means nine deploys and one forgotten.
Failure behaviour: an outage is never resolved into a refusal›
When a check cannot run, the answer is “we couldn’t check” with a retry, not a no, an empty list or a zero. The rule holds in every product. Background work is enqueued only after the transaction commits, so nothing is dispatched for a write that was rolled back.
200Delivery in 25 minutesWe deliver to 400072 · ₹29 delivery · free over ₹499
200We don't deliver to 422001 yetWe've recorded the request. We'll let you know when we get there.
503We couldn't check right nowThat's a connection problem, not a no. Try again in a moment.Try again
Where it runs.
No uptime number or certification is published here, because neither is signed yet.
DATA REGION
AWS us-east-1 (N. Virginia, USA)
Where customer data is stored and processed.
Bring your security reviewer to the call.
We will walk the tenancy model, permissions and audit trail at whatever depth they want, and say plainly which certifications we do not hold yet.