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
- **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. - Always template URLs from the stored scope:
``js const url = https://api.hellobooks.com/public/v1/orgs/${install.orgRefId}/entities/${install.entityRefId}/...; ``
- 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.