> ## Documentation Index
> Fetch the complete documentation index at: https://docs.moda.app/llms.txt
> Use this file to discover all available pages before exploring further.

# Session API

> Create, refresh, revoke, and configure short-lived canvas embed sessions.

Embed sessions are short-lived sessions scoped to one canvas. Your backend creates them with a Moda API key, then passes the returned `embed_url` to your frontend. The URL contains a short-lived boot token; the iframe exchanges it for a narrower browser credential before calling canvas, asset, export, or chat endpoints.

## Create an embed session

```http theme={null}
POST https://api.moda.app/v1/embed/sessions
Authorization: Bearer moda_live_...
Moda-Version: 2026-05-01
Content-Type: application/json
```

<Note>
  `Moda-Version: 2026-05-01` is the REST API version. It is **not** the same as the browser protocol `version: 2026-05-27` you send in `postMessage` envelopes — see the [Browser SDK](/canvas-embed/browser-sdk) version note. They version independently.
</Note>

```json title="Request" theme={null}
{
  "canvas_id": "cvs_01HT9WK8N3M2J4A5Z6P7Q8R9TV",
  "mode": "edit",
  "external_user": {
    "id": "customer-user-123",
    "email": "alice@example.com",
    "name": "Alice"
  },
  "allowed_origins": ["https://app.example.com"],
  "ui": {
    "chrome": "minimal",
    "chat": "external",
    "toolbar": "secondary",
    "speaker_notes": "hidden",
    "theme": "light"
  },
  "expires_in_seconds": 3600
}
```

```json title="Response" theme={null}
{
  "session_id": "emb_4f03f61c2f1d46ec85bbd979ec0fba7d",
  "embed_url": "https://app.moda.app/embed/canvas/emb_4f03f61c2f1d46ec85bbd979ec0fba7d?token=...",
  "expires_at": "2026-05-27T18:30:00Z"
}
```

Edit-mode session creation also verifies that the API key owner can edit the canvas. If not, the API returns `403`.

## Request fields

| Field                | Type                             | Notes                                                        |
| -------------------- | -------------------------------- | ------------------------------------------------------------ |
| `canvas_id`          | `string`                         | Prefixed canvas ID, for example `cvs_...`                    |
| `mode`               | `edit`, `view`, `view-no-export` | Controls whether the iframe can mutate and export            |
| `external_user`      | object                           | Your user identity for attribution and UI display            |
| `allowed_origins`    | `string[]`                       | Exact browser origins allowed to communicate with the iframe |
| `ui`                 | object                           | Chrome, toolbar, chat, notes, and theme settings             |
| `expires_in_seconds` | number                           | 60 to 3600 seconds                                           |

## Modes

| Mode             | Behavior                                                           |
| ---------------- | ------------------------------------------------------------------ |
| `edit`           | User can edit, autosave, upload images, export, and use agent chat |
| `view`           | User can view and export, but cannot edit                          |
| `view-no-export` | User can view only; export commands fail                           |

## UI options

```ts theme={null}
type EmbedSessionUi = {
  chrome?: 'minimal';
  chat?: 'hidden' | 'inside' | 'external';
  toolbar?: 'hidden' | 'secondary';
  speaker_notes?: 'hidden' | 'visible';
  theme?: 'light' | 'dark' | 'system';
  accent_color?: string;
};
```

`chat` controls where the Moda agent chat appears:

* `"hidden"` disables agent chat for the embed session.
* `"inside"` renders a Moda-hosted chat panel inside the iframe.
* `"external"` enables the agent runtime but leaves the chat UI to your application through postMessage commands and events.

### Recommended presets

| Use case                           | UI                                                       |
| ---------------------------------- | -------------------------------------------------------- |
| Customer editor with your own chat | `{ "toolbar": "secondary", "chat": "external" }`         |
| Self-contained editor              | `{ "toolbar": "secondary", "chat": "inside" }`           |
| Read-only preview                  | `{ "toolbar": "hidden", "chat": "hidden" }`              |
| Slides with notes                  | `{ "toolbar": "secondary", "speaker_notes": "visible" }` |

## Origin validation

`allowed_origins` must contain origins only: scheme, host, and optional port. Do not include paths.

```json theme={null}
{
  "allowed_origins": ["https://app.example.com", "http://localhost:4000"]
}
```

If the iframe receives a message from an origin that is not in this list, it ignores it.

## Refresh a session

Refresh rotates the session boot token for an existing session and returns a fresh browser token for the iframe. Call this from your backend before expiry, then pass the new values to the iframe with `session.refresh`.

```http theme={null}
POST https://api.moda.app/v1/embed/sessions/{session_id}/refresh?expires_in_seconds=3600
Authorization: Bearer moda_live_...
Moda-Version: 2026-05-01
```

```json title="Response" theme={null}
{
  "session_id": "emb_4f03f61c2f1d46ec85bbd979ec0fba7d",
  "embed_url": "https://app.moda.app/embed/canvas/emb_4f03f61c2f1d46ec85bbd979ec0fba7d?token=...",
  "expires_at": "2026-05-27T19:30:00Z",
  "browser_token": "...",
  "browser_token_expires_at": "2026-05-27T18:35:00Z",
  "capabilities": ["canvas:read", "canvas:write", "assets:read", "assets:write", "export", "agent"]
}
```

Then tell the iframe:

```ts theme={null}
post('session.refresh', {
  token: new URL(refreshed.embed_url).searchParams.get('token'),
  expiresAt: refreshed.expires_at,
  browserToken: refreshed.browser_token,
  browserTokenExpiresAt: refreshed.browser_token_expires_at,
});
```

The iframe acknowledges a successful refresh by posting `session:refreshed` with `{ expiresAt }`. The iframe also **proactively warns you before expiry** by posting [`session:refresh_requested`](/canvas-embed/browser-sdk#events) (by default 60 seconds before expiry), and posts [`session:expired`](/canvas-embed/browser-sdk#events) if the token lapses — refresh in response to the warning (see [Refresh long-running sessions](/canvas-embed/common-workflows#refresh-long-running-sessions)).

## Revoke a session

Call revoke when the user closes the embedded editor, signs out, or loses access in your product.

```http theme={null}
DELETE https://api.moda.app/v1/embed/sessions/{session_id}
Authorization: Bearer moda_live_...
Moda-Version: 2026-05-01
```

```json theme={null}
{
  "session_id": "emb_4f03f61c2f1d46ec85bbd979ec0fba7d",
  "revoked": true
}
```

## Error codes

Every public API error — including session create/refresh/revoke and the internal iframe endpoints — returns the same envelope:

```json theme={null}
{
  "error": {
    "type": "authentication",
    "code": "authentication",
    "message": "Embed session expired",
    "doc_url": "https://docs.moda.app/api-reference/errors",
    "request_id": "req_..."
  }
}
```

Session failures map to these HTTP statuses and `code` categories:

| HTTP  | `code`           | When                                                                                                     |
| ----- | ---------------- | -------------------------------------------------------------------------------------------------------- |
| `401` | `authentication` | Session expired, revoked, or token invalid                                                               |
| `403` | `permission`     | Browser token does not grant the required capability (for example, export on a `view-no-export` session) |
| `404` | `not_found`      | Session ID does not exist                                                                                |
| `503` | `upstream_error` | Embed session store temporarily unavailable                                                              |

<Note>
  Expired, revoked, and invalid-token failures all surface as `401` / `authentication`; today they are distinguished only by the human-readable `message` (`"Embed session expired"`, `"Embed session revoked"`, `"Invalid embed browser token"`). Branch on `status` for recovery (re-mint on `401`, surface a permission error on `403`), and treat `message` as non-stable display text. Finer-grained machine codes are a known beta limitation.
</Note>

When one of these failures happens inside the loaded iframe (for example, the browser token expires mid-session), the iframe forwards it to your page as a [`load:error`](/canvas-embed/browser-sdk#events) event carrying the same `code` and `status`.

## Internal iframe endpoints

The iframe exchanges the boot URL token for a short-lived embed browser credential, then uses that credential for internal browser-hit endpoints:

* `GET /v1/embed/sessions/{session_id}/canvas`
* `PATCH /v1/embed/sessions/{session_id}/canvas`
* `POST /v1/embed/sessions/{session_id}/images`
* `GET /v1/embed/sessions/{session_id}/images/{file_id}`

These calls use `Authorization: Bearer <embed_browser_token>`.

You normally do not call these directly. Use the iframe and browser SDK commands instead.

## Related Public API docs

* [API Authentication](/api-reference/authentication)
* [API Versioning](/api-reference/versioning)
* [Usage Limits](/api-reference/usage-limits)
