React embed SDK
@spreadspace/react embeds a SpreadSpace surface in your React application
through a secure, token-isolated <iframe>. The <SpreadSpaceEmbed /> component
renders the surface and wires the host side of the embed protocol: auto-resize,
navigation callbacks, theme sync, and token refresh that never exposes a token
to your page.
react and react-dom are peer dependencies (18 or 19).
Install
Quickstart
Render <SpreadSpaceEmbed /> with a getHandle callback. getHandle calls
your backend, which mints a single-use embed handle server-side and returns
the signed_url from the response. The SDK loads that URL as the iframe source.
The callback runs on the initial load and again on every token refresh, so your
page never holds a long-lived credential.
Your backend endpoint is a thin proxy that mints the handle. With
@spreadspace/embed:
To keep the viewing user’s boards, view settings, and memo templates across
sessions, include an external_user_id in the mint body. Derive it from your
backend’s own session rather than the browser request, so one user cannot
open another’s saved state. The Node embed SDK page covers the
field’s contract.
Props
The component forwards a ref exposing { reload(), iframe }. Call reload()
to mint a fresh handle and reload the frame, for example after the user
re-authenticates.
The security model
Your page never holds a SpreadSpace token. You give the SDK a getHandle
function that calls your backend, which mints a short-lived, single-use handle
and returns a signed_url. The SDK loads that URL as the iframe source. Inside
the iframe, which is same-origin to SpreadSpace, the handle is exchanged for the
real embed token, which stays inside the iframe. When the token nears expiry or
is rejected, the iframe asks your page over postMessage for a fresh handle,
the SDK calls getHandle again, and the iframe re-exchanges it. A handle is
worthless off SpreadSpace’s origin and expires in seconds; the token never
crosses the iframe boundary into your page.
The minted token is capped to the scopes you request (read-only by default,
with documents:write / extractions:write available only as explicit
per-mint opt-ins that never inherit from the key) and locked to the single
loan_id the handle was minted for. On plans without spreads access, the
spreads scope is dropped server-side, so the spreading surface falls back to
documents only. Inspect the returned scopes if your integration depends on it.
Scopes
The API key your backend uses to mint handles needs the embed:write scope.
Issue a key from the dashboard at Settings → Live API (or Settings →
Sandbox API for a test key) and grant it embed:write. The browser-facing token minted from that handle never carries
embed:write, so a leaked browser token cannot mint further tokens or widen
its own scope.
Advanced: the protocol module
The typed message envelope and both messengers are exported, also as
@spreadspace/react/postmessage, for integrators who want to drive a
hand-rolled iframe:
createHostMessenger verifies the frame origin, pins inbound messages to your
iframe’s contentWindow, correlates each token-refresh request and response by
id, and replies with a handle. The response payload is type-branded so that
including a token field is a compile error.