Authentication
The cloud engine authenticates every request with a bearer JWT. Your backend mints these tokens; the engine never holds your tenant secret and never signs anything itself. The CloudPDF server is the verifier of record — the SDK only decodes a token (unverified) to learn which document to route to.
Where the token comes from#
Set a token on the engine, on an individual open(), or both. A token may be a
string or a function that returns a fresh one — the engine calls it on every
request, so rotation is transparent.
const engine = createCloudEngine({
baseUrl: 'https://pdf.your-app.com',
token: () => getTenantToken(), // engine-level default bearer
});Mint tokens on your server, never in the browser, and keep them short-lived. A doc-scoped token is pinned to a single document, so an exfiltrated token can’t be replayed against other documents.
Three ways to open#
The cloud engine accepts exactly three OpenInput shapes. Pick by where the
credential comes from: your backend mints one (token), your frontend holds a
tenant session (id), or there is no backend at all (share).
Open by token#
Use this when your backend mints doc-scoped JWTs — for example a logged-in
reviewer authorized for exactly one document. The token’s doc_id claim
identifies the document. Each open({ kind: 'token' }) is independent — one
engine can hold many handles, each carrying its own per-document token.
const doc = await engine.open({
kind: 'token',
token: () => fetchDocToken(documentId),
});The handle and all of its RPCs carry this token, not the engine-level one.
Open by id#
Use this when your frontend has a tenant session (for example a JWT minted at login by your auth backend) and just needs to open one of many documents the tenant owns.
const doc = await engine.open({
kind: 'id',
id: 'doc_proposal_2026',
layerName: 'default', // optional; omit for the server default layer
token: () => fetchTenantToken(), // optional per-open override
});Without a per-open token, the engine uses the token supplied at construction.
Open by share token#
Use this for the no-backend embed flow: a public share token (shr_…) from
the dashboard’s embed snippet. A share token is a reference to a stored grant
on the server, not a credential — the engine exchanges it for a short-lived
doc-scoped session JWT and silently re-exchanges near expiry, so revoking or
editing the share retargets every embedded copy at the next renewal.
const engine = createCloudEngine({ baseUrl: 'https://pdf.your-app.com' });
const doc = await engine.open({
kind: 'share',
shareToken: 'shr_9f2k…',
sharePassword: 'open-sesame', // only for passphrase-protected grants
});sharePassword is the grant’s passphrase, checked at the exchange; password
remains the PDF’s own encryption password, the same slot every other kind uses.
A protected grant opened without its passphrase rejects with
EngineErrorCode.SharePasswordRequired — prompt and retry.
The exchange endpoint checks the browser’s Origin header against the grant’s
allowlist, and the capabilities the handle gets are the ones the grant carries.
For custom flows (pre-exchange to inspect a docId, hand-rolled prompts), the
primitives exchangeShareToken and shareSessionSource remain exported.
Scope and identity live on the server#
Authorization — what the caller may do (doc.text.copy, doc.annotate.read,
annotations:update:self, …) and who they are — is carried inside the JWT
and enforced by the server. The engine reads them from the token; it does not
accept them as client options.
The local engine takes scope and identity through
open() options because there’s no JWT involved. The cloud engine
intentionally ignores those options — the token is the
authority. This is what keeps the same SDK code portable between local and
cloud. Read the resolved grants from
doc.security.effectiveScope.
Anonymous engines#
If every open() supplies its own credential — a doc-scoped token or a public
share token — the engine itself needs none: omit token at construction.
Requests then go out without an Authorization header until a per-open
credential is provided.
const engine = createCloudEngine({ baseUrl: 'https://pdf.your-app.com' });
const doc = await engine.open({ kind: 'share', shareToken: 'shr_9f2k…' });What failures look like#
Auth problems surface as typed EngineErrors: a 401 becomes
Unauthenticated, a 403 becomes Forbidden. See
Async & errors for the full list
and handling patterns.