Skip to main content
Foundations

Tenant isolation

Your token is scoped to exactly one (org, entity). Any URL with a different :orgId or :entityId returns 403 tenant_scope_violation — even if you have a token for the other tenant.

Why it matters

HelloBooks is multi-tenant: a single user can belong to multiple organizations, and each organization can run multiple entities (e.g. legal entities, branches). When a marketplace app is installed, it is installed into one specific (OrgRefId, EntityRefId) pair.

The access token you receive is bound to that pair. There is no "global" token — every install you do mints a separate token for the new tenant.

How the guard works

The requireTenantParamMatch middleware is wired onto every tenant-scoped route. It looks for orgId and entityId in (in order): URL params, query string, request body. If a value is found AND it doesn't match the install's OrgRefId/EntityRefId, the request is rejected:

HTTP/1.1 403 Forbidden
{ "error": "tenant_scope_violation", "message": "Token not authorized for this org." }

(or ...this entity. for the entity-side mismatch.)

Practical client patterns

  1. **Read your scope from /me** at install time and store it alongside your local install record. Don't let the user pick the org/entity at runtime — it's already fixed.
  2. Always template URLs from the stored scope:

``js const url = https://api.hellobooks.com/public/v1/orgs/${install.orgRefId}/entities/${install.entityRefId}/...; ``

  1. Don't cross tokens. If your service holds installs for two tenants, never accidentally use Tenant A's token against Tenant B's URL — the response will be 403, but you should make this structurally impossible in your code.
    HelloBooks Public API — Tenant Isolation