Tenancy, Isolation, and the Ledger Core

If you're evaluating Paprel as the accounting layer under your product, the architecture question usually comes in two halves: how is my customers' data kept apart? and what actually guarantees the books are right? This article answers both, layer by layer — tenancy, the ledger engine, the workflow layer on top of it, and the reporting layer that reads it all back.

None of this is aspirational. It's the same structure described on the embedded accounting page, running in production today.

Multi-tenancy: a company is the boundary

Everything in Paprel belongs to a company. The company is simultaneously the tenant boundary, the set of books, and the reporting entity. In an embedded setup, you typically create one company per customer of your platform — one API call, and that customer has isolated books of their own.

Two properties matter here:

Isolation is enforced at the data layer, not in application code. "Remember to add the tenant filter to every query" is not a security model; it's a bug that hasn't happened yet. Paprel's tenant isolation lives below the application — every read and write is scoped to the company at the data layer, so a missing WHERE clause in application code cannot leak one tenant's journals into another's report. Access on top of that uses OAuth 2.0/2.1 scoped tokens: tenant-scoped, with per-route grants, and the same model powers the dashboard, machine-to-machine clients, and MCP agents alike.

A company is born with working books. Provisioning seeds each company with its own ledger, document numbering, and a default chart of accounts based on a standard taxonomy — assets, liabilities, equity, income, expenses. You extend that tree rather than designing one from scratch, and because every later posting lands on an account in this tree, the reports in the last section work with zero reporting configuration on your side.

The ledger engine: append-only, balanced, or rejected

Underneath everything is a double-entry ledger with three load-bearing rules.

Journals are the primitive. Everything in Paprel — an invoice posting revenue, a payment clearing a receivable, a manual adjustment — ultimately compiles down to journal entries: balanced sets of debit and credit lines against accounts in the chart. There is no second bookkeeping mechanism off to the side. If a number appears in a report, there is a journal line behind it.

Balanced or rejected, at the API boundary. Debits must equal credits, and the check happens at write time. There is no code path that lets an unbalanced entry through and no "fix it in a nightly job" reconciliation step — an unbalanced posting is rejected before it exists.

Append-only, with versioning instead of mutation. Posted journal lines are not edited in place; corrections are new entries. Documents (the layer above, covered next) are versioned: every edit creates a new version while a stable anchor_id survives across all of them. The anchor is what you store, link to, and pay against; the version history is what an auditor walks. Combined with the signed audit log — retained for seven years and exportable as CSV/JSON — "what did the books say, and who changed them, and when" is a query, not an investigation.

The write path is idempotent throughout: retried a request because of a timeout? Resubmitting returns the original result instead of double-posting.

The workflow layer: documents that generate journals

Most teams don't want to hand-write journals, and most of the time they shouldn't have to. The workflow layer is where invoices, bills, credit notes, and expenses live — business documents whose state changes generate the journals for you.

Post an invoice and a balanced journal debits receivables and credits the revenue account the line items map to. Record the payment and a second journal moves the money from receivables to cash — the invoice flips to paid, the customer's balance drops, and revenue stays where it was earned. You never wrote either journal; the document did, through account mappings decided once by whoever understood the books.

The lifecycle around those state changes is configurable. Document statuses and approval gates — draft → reviewed → approved → sent, or whatever your operators need — are defined per company via the status settings, and every transition lands in the same audit history as the postings themselves. So the workflow layer is not a convenience wrapper that bypasses the ledger's guarantees; it is a compiler into them.

The reporting layer: derived, never reconciled

Reports in Paprel — P&L, balance sheet, trial balance, GL export — are derived live from the journal rows. They are not a separate datastore that gets synced with the ledger and periodically reconciled against it; they are views over the same rows the engine wrote.

This is why the trial balance always ties to zero. It isn't a health check that usually passes — it's arithmetic that cannot fail, because every row it sums was balanced at write time by the engine above. The same property means there is no end-of-day batch to wait for: post an invoice, and the P&L reflects it on the next read.

The three tiers, end to end

Text
┌─────────────────────────────────────────────────┐
│  YOUR PLATFORM                                  │
│  orders · wallets · billing · custom events     │
└───────────────────────┬─────────────────────────┘
                        │  POST · idempotent
                        ▼
┌─────────────────────────────────────────────────┐
│  PAPREL CORE              (isolated per tenant) │
│                                                 │
│   ingest ──▶ validate ──▶ post (double-entry)   │
│                                                 │
│   ledger (append-only)    audit log (signed,    │
│                           7yr retention)        │
└───────────────────────┬─────────────────────────┘
                        │  GET · webhooks (signed) · export
                        ▼
┌─────────────────────────────────────────────────┐
│  READ SURFACES                                  │
│  P&L · balance sheet · trial balance ·          │
│  GL export · audit trail · signed webhooks      │
└─────────────────────────────────────────────────┘

Your application emits events over idempotent REST (or the SDK, or MCP). The core validates them against the schema and the chart of accounts, posts balanced journals into the append-only ledger, and records everything in the signed audit log. Read surfaces — reports, CSV/JSON exports, signed webhooks carrying content hashes so downstream systems can verify exactly what happened — all pull from that single source of truth. The infrastructure underneath runs on certified providers (Google Cloud, Cloudflare), with tenant-pinned data residency across US, EU, and APAC regions.

What this buys you

The layering is the point. Tenancy means one API call gives each of your customers their own books, isolated below your code. The engine means those books cannot go out of balance. The workflow layer means your product speaks in invoices and payments, not debits and credits. And the reporting layer means the numbers your customers' accountants see are the ledger — not a copy of it.

Where to go next:

Evaluate Paprel

Build in sandbox, launch with a production trial

Use sandbox for developer testing with no billing. When you are ready for real workflows, start production on a monthly plan with a 14-day free trial.

API-First Delivery
Audit-Ready Controls
Sandbox And Guided Rollout
We use cookies to improve your experience. Manage preferences or accept all.