Skip to content

Tenancy, trust & security

The platform holds content for more than one business at once, on one shared set of infrastructure — the way Microsoft 365 or Google Workspace serve many companies from the same systems. The wall between those businesses is the single most important guarantee the platform makes: one business must never see, search, or touch another's content. This page explains how that wall works, and how each individual request is checked once it is safely inside it.

Two ideas do the work. The first is the tenant: a hard boundary around one business's entire estate — its Works, Resources, Contracts, and Products. Every entity belongs to exactly one tenant, and nothing reaches across the boundary. The second is the token: the signed credential every request carries. The token names the tenant it is allowed to act on, and every service checks that name before doing any work. Because the tenant is baked into the credential rather than asked for in the request, a caller cannot reach another tenant's data even by trying — the attempt is refused before it starts.

Underneath the tenant wall, a second question still has to be answered on every request: is this particular caller allowed to do this particular thing? That is where the three authorisation layers come in — scope, access grant, and contract rights — three gates a request passes through in turn. Isolation says which estate a request may touch; authorisation says what it may do within it.

The rest of this page walks through both, in plain terms. Where it matters to engineers or integrators, the deeper mechanics sit in collapsible sections you can open, but you should not need them to understand how your content is protected.

What a tenant is

A tenant is a business. Packt is a tenant. So is Expert Edge, and so is CVP. Each has its own users, its own content, its own contracts, and its own products, and none of them can see any of the others'.

The strong recommendation is one tenant per business — a single tenant that holds all of that business's content, rather than splitting it across several. This is not an arbitrary rule. The value of the platform grows the more content lives together in it: the shared Knowledge Graph, the cross-content search, the ability to reuse a chapter written for one product in another — all of these get stronger the larger and more connected the estate is. Fragmenting a business across many tenants throws that away. Each shard becomes a smaller, thinner graph that knows less about itself, and content in one shard can never be found, cited, or reused from another. Keeping everything in one tenant is what lets one manuscript flow into dozens of products and surfaces.

Isolation is the boundary, not the organiser

The tenant wall separates businesses, not teams or projects inside a business. Within one tenant you organise and control visibility with tags, access grants, and contract terms — not by minting extra tenants. A review-status tag can keep drafts out of a public blog; an access grant can limit a Resource to one team. Reach for those before you ever reach for a second tenant.

Token-scoped isolation — how the wall holds

Every request that arrives at the platform carries a token issued by WorkOS, the platform's identity provider (through two sign-in pools — one for customers and authors, one for staff and partner businesses — described in Sign-in & identity pools). That token is signed, short-lived, and — the part that matters here — scoped to exactly one tenant. It carries a claim naming the tenant it is allowed to act on. When you log in, your token names your business. When a partner application calls on your behalf, its token names your business. When one internal service calls another, it too holds a token scoped to the single tenant it is working on.

Every service, on every request, checks that the tenant named in the request matches the tenant baked into the token. If they do not match, the request is refused. If a request names no tenant at all, it is refused. There is no way to widen a token to a second tenant, and no back door for internal traffic — a service calling another service is checked exactly like an outside caller.

There are no API keys, and no tenant header

Isolation rests entirely on the signed token. There are no API keys and no X-Packt-Tenant-Id header — an older design that let the tenant be asserted in a plain request header has been retired. A header can be forged or copied; a signed token's tenant claim cannot. Because the tenant is inside the credential rather than alongside it, cross-tenant access is architecturally impossible, not merely forbidden by convention.

This same rule holds not just at the front door but everywhere content lives — in storage, in the search indexes, and on the internal event streams — so a forgotten check in one place cannot open a gap, because the tenant boundary is built into the shape of the data itself.

flowchart LR
    U["Caller<br/>(person, app, or service)"] -->|token names one tenant| E["Platform edge"]
    E -->|token travels unchanged| S["Any service"]
    S -->|tenant in request<br/>must match token| G{"Match?"}
    G -->|yes| OK["Request proceeds"]
    G -->|no, or no tenant named| NO["Refused"]
What a token actually contains

Every platform token is a JWT — a signed, tamper-evident bundle of claims. Services verify the signature against WorkOS's published keys, then read the claims below. The token is short-lived (at most one hour) and travels unchanged through every internal hop — no service re-issues or rewrites it.

Claim What it says
iss Who issued the token (which WorkOS pool).
sub The principal — the u_ user or a_ application acting.
tenant_id The single tenant this token may act on. This is the isolation boundary.
scopes The permissions the token carries, as registry names (e.g. content-lake.read). The first authorisation gate.
client_id The application the token was obtained through.
auth_time When the person last actively signed in (human tokens only) — checked by step-up-gated operations.
exp / iat / jti Expiry, issue time, and a unique token ID.

Notably, group membership is not in the token. It is resolved freshly on each request, so a change to who is in a team takes effect immediately rather than waiting for a token to expire. See Authentication for the full contract.

The three gates: scope, access grant, contract rights

Clearing the tenant wall proves which estate a request may touch. It does not prove the caller may do the specific thing they are asking for. That is settled by three further, independent checks — three gates every request passes through in order. A request must clear all three; failing any one refuses it.

flowchart TD
    R["Authenticated request<br/>(already inside the tenant wall)"] --> S{"Gate 1 — Scope<br/>May this application do<br/>this class of thing at all?"}
    S -->|no| X1["Refused"]
    S -->|yes| A{"Gate 2 — Access grant<br/>May this caller see this<br/>specific Resource?"}
    A -->|no| X2["Refused"]
    A -->|yes| C{"Gate 3 — Contract rights<br/>May this content be<br/>used this way?"}
    C -->|no| X3["Refused"]
    C -->|yes| OK["Action permitted"]

Each gate answers a different question, and they narrow the request as it descends:

  • Gate 1 — Scope. May this application do this class of thing at all? A scope is a coarse permission carried on the token, such as content-lake.read or contracts.commercial.read. It says what an application is allowed to ask for — read content, publish a product, see commercial terms. It is decided when the application is registered and (for apps acting for a person) when the user consents; it is never something the request itself can widen.
  • Gate 2 — Access grant. May this caller see this specific Resource? Holding a scope to read content does not grant sight of every Resource. An access grant is a fine-grained entry on an individual Resource saying which people or apps may see or change that one thing.
  • Gate 3 — Contract rights. May this content be used this way? Even with the scope and the access grant, what may actually be done with the content is governed by its Contract. A contract might permit an AI assistant to retrieve and cite a Work (AI_RETRIEVAL) while prohibiting its use to train a model (AI_TRAINING) — and the platform enforces that difference at the point of use.

A wider gate never overrides a narrower one. A broad scope does not grant sight of a Resource an access grant withholds, and neither can loosen a term the contract prohibits. See the Scope registry and Contracts & Rights for the detail behind gates one and three.

Isolated environments

Shared infrastructure is the right choice for almost every tenant: it keeps the platform's cost and operations independent of how many businesses it serves, and it is what makes the shared Knowledge Graph and cross-content reuse possible. The tenant wall already makes cross-tenant access impossible, so sharing infrastructure does not share data.

For a small number of clients that require even stronger guarantees — their content physically separated onto dedicated infrastructure rather than logically separated on shared infrastructure — the platform can provide a dedicated, isolated operating environment. Because a dedicated environment carries its own operational and cost overhead, it is offered to clients above $1m of committed annual spend.

  • Authentication — the token contract, scopes, and how identity propagates through the platform.
  • Sign-in & identity pools — who signs in where, and the wall between customer and staff identity.
  • Principles — the zero-trust and fail-closed stances this page rests on.
  • Core concepts — where the tenant sits among the other platform ideas.
  • Glossary — one-line definitions for every term used here.
  • Explorer — Chapter I — see the platform, visualised.