Let users choose a canvas
Use the Public API to list or search canvases, then create an embed session for the selected canvas. A canvas in either response is aCanvasItem. The title field is name (not title), and neither endpoint returns a thumbnail — render the picker from name/category/updated_at, or open the canvas to get a preview.
CanvasItem
data, search is a flat list under canvases:
List canvases — { data, next_cursor }
Search canvases — { canvases }
next_cursor until it comes back null:
Build a canvas picker
Recommended flow:- User opens your “Choose design” screen.
- Your backend calls
GET /v1/canvasesorGET /v1/canvases/search. - Your frontend shows the result list.
- User selects a canvas.
- Your backend creates an embed session for that canvas.
- Your frontend loads the returned
embed_url.
Restrict canvases in an internal tool
Your app should remain the policy decision point. Moda can pin an embed session to one canvas, one mode, and one set of allowed browser origins, but your backend should decide which internal users may see or edit each canvas before creating the session. Recommended pattern:- Store the mapping between your internal users, teams, projects, or roles and the Moda canvas IDs they may access.
- When a user opens your picker, call the Moda Public API from your backend and filter the results against that mapping before returning them to the browser.
- When the browser asks to open a canvas, do not trust the submitted
canvas_id. Re-check the current internal user against your access mapping on the backend. - Create the embed session only after that check passes.
- Set
modefrom your app’s authorization result. For example, reviewers getview-no-exportorview; editors getedit. - Set
external_user.idto your stable internal user ID for attribution and audit trails. - Revoke active sessions when the user signs out, changes role, or loses access to the underlying project.
Backend authorization sketch
Save and autosave
Edit-mode embeds autosave after user edits. You can still call manual save before an important transition:canvas:saved before closing a modal or advancing your workflow:
Use your own chat UI
Create sessions with:chat.stop to cancel the active agent turn.
Insert user-uploaded images
If the user picks an image in your app, send theFile to the iframe. The iframe uploads it through the embed image endpoint and inserts a real Moda image node.
Embed uploads currently accept PNG, JPEG, GIF, and WebP files up to 25 MB. If you need SVG or larger asset handling, upload through your own workflow and pass a browser-renderable HTTPS URL to insertImage.
Insert server-generated images
If your backend generates an image, prefer one of these:
Example with a URL:
Export from the iframe
Use iframe export when the user needs the file immediately in the browser.export:completed includes a job envelope — jobId, downloadUrl and warnings — for every format; the artifact is built on Moda’s servers and your page fetches it from the URL. The SDK still waits for the job with a ~120s completion timeout.
Use Public API export when your backend needs a durable export URL, background polling, caching, a server-side workflow, or whenever the document is large or multi-page:
Refresh long-running sessions
Sessions can last up to 3600 seconds. For long editing sessions:- Track
expires_atfrom session creation. - Set a timer to refresh from your backend before expiry (for example, 60s ahead).
- Send the new token to the iframe with
session.refresh; the iframe repliessession:refreshed { expiresAt }.
Recover from full expiry — create, don’t refresh
A refresh loop only works while the session is alive. If your refresh cadence ever stops — your job runner pauses, the user’s laptop sleeps, a background tab gets suspended — the session expires for good (sessions cap at 3600 seconds), and the embedded tab that later wakes up cannot be recovered by refreshing:POST /v1/embed/sessions/{session_id}/refreshreturns404once the session record is gone (or409while an expired/revoked record lingers). Calling it on a timer forever is the classic broken integration: the iframe stays dead while your backend 404s. Keep it up on onesession_idand the API answers410 Gone(embed_session_gone) — a stop, with noRetry-After, because the id is gone for good and waiting changes nothing.- The iframe emits
session:expired(once) and stops: live sync is closed with a terminal code and not retried, so edits made in that tab are not being saved until you act.
session:refresh_requested → refresh; session:expired /
session:revoked → recover as above. The handler branches on scope: a
'browser_token' expiry tries the edit-preserving refresh first and only creates once
the backend proves the session dead (404/409); a 'session' expiry goes straight to
create. Keep the recovery idempotent either way — both terminal events can fire for the
same dead session — and see the
annotated variant
in the Browser SDK page for the full rationale.
Edits made in a tab after its session fully expired are not persisted —
the session that authorized writes no longer exists, and they are not carried
into the replacement session. A healthy refresh loop is what protects unsaved
work; the create-on-expired handler is the backstop that stops a dead tab from
failing silently.
Revoke on logout or close
Call revoke when access should end:Should you build an API wrapper?
Most teams do best with two small clients:
Avoid a browser SDK that exposes general Moda API calls. It would either leak your API key or require your backend to proxy every possible operation.
A backend wrapper is useful if your app uses several Public API endpoints: