Oligon Receipts is in private beta — request access.
Core concepts
Multi-tenancy

Multi-tenancy

Two patterns cover almost every B2B2C build on top of Oligon.

Pattern A — One Oligon org per your customer

Use this when your customers expect their own billing, their own keys, and their own dashboard.

  • Create an Oligon org for each customer via the partner API.
  • Provision an admin key, store it in your secrets store.
  • Optionally invite the customer's email as owner — they can then log in via the portal, see their receipts, and rotate their own keys.
from oligon_receipts import OligonReceipts
 
partner = OligonReceipts(api_key=os.environ["OLIGON_PARTNER_KEY"])
new_org = partner.organizations.create(
    name="Acme Inc.",
    primary_owner_email="founder@acme.com",
    plan="indie",
)

Pros — clean isolation, dashboard out of the box, customers can churn without affecting your other tenants.

Cons — Stripe customer per tenant, quota counted per-tenant.

Pattern B — One Oligon org, tag with metadata

Use this when you are the SaaS, your customers do not need a dashboard, and you want a single Oligon bill.

  • Keep one Oligon org owned by you.
  • Pass metadata: { "tenant_id": "<your-tenant-id>" } on every extract call. The value is stored verbatim and indexed.
  • Filter /v1/receipts by metadata.tenant_id=... to scope reads.
await client.extract({
  file,
  metadata: { tenant_id: "tnt_xy", customer_email: "u@acme.com" },
});
 
// later
for await (const r of client.receipts.list({ "metadata.tenant_id": "tnt_xy" })) {
  ...
}

Pros — minimal moving parts, one bill, fast onboarding.

Cons — no native dashboard for your end-users, you build any UX layer.

What Oligon does under the hood

Whichever pattern you choose, isolation between Oligon orgs is enforced in Postgres via row-level security (opens in a new tab). Every connection sets SET LOCAL app.current_org = '...' and every table has a policy:

CREATE POLICY org_isolation ON receipts
USING (org_id = current_setting('app.current_org')::uuid);

A buggy query that forgets a WHERE org_id = ? clause will return zero rows instead of leaking cross-tenant data.

We run nightly fuzz tests that swap API keys mid-transaction and assert no rows leak. Results are public at trust.oligontech.com (opens in a new tab).