> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.spreadspace.app/embed/overview/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.spreadspace.app/_mcp/server.
# Embed
> Embed the SpreadSpace spreading workspace or document review surface in your application with server-minted, token-isolated sessions.
Embed a SpreadSpace surface directly in your own application. Your users work
with the spreading workspace or the document review panel without leaving your
product, and without your page ever holding a SpreadSpace token.
A browser package renders the surface, React or plain JavaScript, and the
SpreadSpace SDK for your server's language mints the credential that
authorizes it:
| Package | Registry | Runs | Purpose |
| -------------------- | -------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------- |
| `@spreadspace/react` | npm | Browser | Renders the embedded surface via ``. |
| `@spreadspace/embed` | npm | Browser | Renders the embedded surface with `mount()` in any page, as a module or a script tag. `@spreadspace/react` is a thin layer over it. |
| `@spreadspace/sdk` | npm | Server | Mints the short-lived session or handle that authorizes the embed, from TypeScript or Node. |
| `spreadspace` | PyPI | Server | The same mints from Python. |
| `SpreadSpace` | NuGet | Server | The same mints from C#. |
## How it fits together
1. Your backend uses a SpreadSpace SDK (or a direct API call) with an API key
that holds the **`embed:write`** scope to mint a single-use handle or a
loan-scoped session. Every sample reads that key from
`SPREADSPACE_EMBED_API_KEY`; the Backend key stays in `SPREADSPACE_API_KEY`.
2. Your app renders `` from `@spreadspace/react`, or
calls `mount()` from `@spreadspace/embed` in a page without React, and
gives it a `getHandle` callback that fetches that handle from your backend.
3. The browser package loads the returned `signed_url` into an iframe served from
SpreadSpace's origin. Inside the iframe, the handle is exchanged for a
short-lived, loan-bound embed token that stays in the iframe and never
reaches your page.
## Install
**`React (browser)`**
```bash title="React (browser)"
npm i @spreadspace/react
```
**`Plain JavaScript (browser)`**
```bash title="Plain JavaScript (browser)"
npm i @spreadspace/embed
```
**`TypeScript (server)`**
```bash title="TypeScript (server)"
npm i @spreadspace/sdk
```
**`Python (server)`**
```bash title="Python (server)"
pip install spreadspace
```
**`C# (server)`**
```bash title="C# (server)"
dotnet add package SpreadSpace
```
## The security model
Your page never holds a SpreadSpace token. The token is minted server-side,
exchanged inside a same-origin iframe, and kept there. A handle is single-use,
expires in seconds, and is worthless off SpreadSpace's origin. The browser-facing
token holds the scopes the mint names and is locked to a single loan. Read
scopes are granted by mint authority; a write scope is granted only when the
mint names it and only if the minting key holds it. The only grantable
writes are the explicit per-mint upload, review and spread scopes
(`documents:write`, `extractions:write`, `extractions:edit`, `spreads:write`), which stay bound to
that same loan. A leaked token
therefore cannot reach other loans or mint further tokens. The `embed:write` scope, held
only by your server-side API key, is the control point: revoking it immediately severs every
issued token and in-flight handle.
## Persistent user state
Pass an optional `external_user_id` (your app's opaque identifier for the
viewing user, up to 256 characters) when minting a session or iframe handle.
Sessions minted with the same id share server-kept state for that user: board
arrangements, view preferences, and saved memo templates survive across that
user's sessions and devices. Omit it and each session starts from defaults.
The mint response says which mode you got. `persistence` is `user` when an
`external_user_id` was supplied and `session` when it was not, and `session`
means nothing the viewer arranges is ever saved.
The id also comes back to you: when that user finalizes a spread, the
[attribute snapshot](/api/spreads) carries it as
`finalized_by_external_user_id`, so your backend can say who finalized from
its own user table. SpreadSpace never has to hand a person's name to your
service key.
You can also pass an optional `display_name` (up to 120 characters) beside
the id. The workspace shows it as that user's name on the spreads they save
and finalize, on their comments and in live presence. It requires
`external_user_id` in the same request.
## How the numbers reach your backend
The analyst finishes the spread inside the embed and finalizes it there. Your
backend receives a [`spread.finalized` webhook](/api/webhooks) naming the loan
and the snapshot it just created, then reads that snapshot, the figures as
the analyst approved them, by id with
`GET /api/loans/{loanId}/attributes/{snapshotId}` (the same
[attribute snapshot](/api/spreads) the workspace produces) straight into your
own fields. The snapshot contains the figures the analyst approved. If the
analyst later reopens the spread to revise
it, `spread.reopened` names that same snapshot so you can mark your copy stale
until the next finalize arrives.
A session whose user is expected to finalize must be minted with
**`spreads:write`** (or the wider `extractions:write`). The SDK's
[webhook receiver](/api/webhooks#receive-events-with-the-sdk) reads the
snapshot the event names for you on `spread.finalized`, so the handler you
write is the half that stores it.
## Quick starts
* [Embed integration guide](/get-started/embed-walkthrough): mint a handle
server-side, render the workspace, and receive finalized spreads.
* [React embed SDK](/embed/react): render `` with a
`getHandle` callback.
* [Plain JavaScript loader](/embed/loader): call `mount()` with a `getHandle`
callback from any page, as a module or a script tag.
* [Mint from your server](/embed/server): mint an iframe handle or an embed
session in TypeScript, Python, C# or cURL.
> Embed the SpreadSpace spreading workspace or document review surface in your application with server-minted, token-isolated sessions.