Embed sessions
An embed session is the credential the widget runs on. It is minted server-side with your partner key, lives for a limited time, and can only touch one workspace.
Endpoint
Section titled “Endpoint”POST /v1/embed-sessions
| Field | Type | Default | Notes |
|---|---|---|---|
workspace |
string |
required | The workspace the widget will act on |
surfaces |
string[] |
all four | Which of composer, calendar, inbox, accounts the session may open. all shows only the allowed ones |
expires_in |
number |
3600 |
Seconds until the token stops working. Maximum 86400 (24 hours) |
theme |
object |
dark, purple | { mode?: "dark" or "light", accent?: "#6d5efc", radius?: "12px" }. See Theming |
locale |
string |
en |
en or nl |
curl -X POST https://social.missless.tel/v1/embed-sessions \ -H "Authorization: Bearer $MISSLESS_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "workspace": "ws7k2m9p4q1r8t3", "surfaces": ["composer", "calendar", "inbox", "accounts"], "expires_in": 3600, "theme": { "mode": "dark", "accent": "#6d5efc", "radius": "12px" }, "locale": "en" }'Response 201:
{ "token": "mle_5c1e9a7b3d2f8e4a6c0b9d1f7e3a5c2b", "expires_at": "2026-08-25T10:30:00Z", "urls": { "all": "https://social.missless.tel/embed/all?session=mle_5c1e9a7b3d2f8e4a6c0b9d1f7e3a5c2b", "composer": "https://social.missless.tel/embed/composer?session=mle_5c1e9a7b3d2f8e4a6c0b9d1f7e3a5c2b", "calendar": "https://social.missless.tel/embed/calendar?session=mle_5c1e9a7b3d2f8e4a6c0b9d1f7e3a5c2b", "inbox": "https://social.missless.tel/embed/inbox?session=mle_5c1e9a7b3d2f8e4a6c0b9d1f7e3a5c2b", "accounts": "https://social.missless.tel/embed/accounts?session=mle_5c1e9a7b3d2f8e4a6c0b9d1f7e3a5c2b" }}urls are ready to use as iframe src. token is what the loader takes in data-session.
Server-side only
Section titled “Server-side only”The call needs your partner key, which must never reach a browser. The pattern is a small authenticated route in your backend:
import { NextResponse } from 'next/server';
export async function POST(req: Request) { const user = await requireUser(req); // your auth const workspace = await findWorkspaceFor(user.id); // by external_id, see the Next.js guide if (!workspace) return NextResponse.json({ error: 'no workspace' }, { status: 404 });
const res = await fetch('https://social.missless.tel/v1/embed-sessions', { method: 'POST', headers: { Authorization: `Bearer ${process.env.MISSLESS_API_KEY}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ workspace: workspace.id, surfaces: ['composer', 'calendar', 'inbox', 'accounts'], theme: { mode: user.prefersDark ? 'dark' : 'light', accent: '#6d5efc', radius: '12px' }, locale: user.locale === 'nl' ? 'nl' : 'en', }), }); const session = await res.json(); return NextResponse.json({ token: session.token, urls: session.urls, expires_at: session.expires_at });}Only mint a session for a workspace the signed-in user owns. The session is that user’s power over the workspace, so the check is yours to make.
Expiry
Section titled “Expiry”Sessions expire after expires_in seconds and cannot be extended. When the token expires the widget shows a “session expired, reload” notice and stops making calls. Two ways to handle it:
- Mint per page load with the default hour. Most sessions end long before that.
- For long-lived screens, mint a new session and swap the iframe
src(or re-render the loader element) shortly beforeexpires_at.
Keep expires_in short. A leaked token is worth at most that many seconds of access to one workspace.
Restricting surfaces
Section titled “Restricting surfaces”surfaces is a permission, not a layout hint. A session minted with ["inbox"] cannot open the composer even if someone edits the iframe URL. Mint the narrowest session the screen needs: an inbox page gets ["inbox"], a settings page gets ["accounts"].