Skip to content
OpsGrids
Platform

The rules under every product.

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

  • 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 itself
    BEGIN;
      SELECT set_config('app.organization_id', $1, true);  -- true = this transaction only
    
      SELECT id, number, total_minor, currency
        FROM finance.invoices
       WHERE organization_id = $1;          -- filtered anyway, on purpose
    COMMIT;

    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.

  • 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”.

    1. OWNOnly the records this person owns.LEVEL 1
    2. TEAMTheir team, down the reporting line.LEVEL 2
    3. LOCATIONEverything at a site or branch.LEVEL 3
    4. 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 scope
    scope = 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.

  • 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.

    GET/api/v1/crm/contacts/{id}crm.contacts.read
    // 200: the default shape, for everyone
    {
      "contact_id": "3c90f1a7-…",
      "name": "R. Iyer",
      "phone_masked": "+91 ••••• •7568",
      "phone_revealable": true,
      "messaging_target": "tgt_9f2b…"
    }

    NOTERevealing the number is a separate call against a separate permission, and it writes an audit row whether or not anybody ever reads it.

  • 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.

    POST/api/v1/finance/invoices/{id}/credit-notefinance.invoices.write
    // 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.

    What Finance covers on top of this

More detail.

Seven guarantees, and what each one prevents

SCROLL SIDEWAYS FOR THE REST OF EACH ROW

Each platform guarantee, what it means in practice, and the failure it prevents.
GUARANTEEWHAT IT MEANSWHAT IT PREVENTS
Organisation on every tableEvery 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 transactionThe 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 permissionsA 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 defaultPersonal 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 unitsWhole 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 recordsInvoices, 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 keyExactly 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.

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.

Talk to our team