API Reference
/
POST embed-sessions
POST /api/editor/embed-sessions
Mint a short-lived session token (JWT) that authorizes a single browser session to mount a Viewer Embed or an Editor Embed.
POST
https://moshpit.studio/api/editor/embed-sessions
Auth
Plain
Authorization: Bearer msk_YOUR_SECRET_KEY
X-Moshpit-External-User-Id: YOUR_HOST_USER_IDThis endpoint requires the integration's secret key. Never call it from a browser.
The external user header is required for editor sessions that open an
existing splat or need to save/upload/publish. It is optional only for blank
guest editor sessions (type: "editor", splatId: null) and for viewer
sessions that open a product-public splat. Provision scoped users first with
PUT /api/v1/external-user.
Scoping a session to a customer
X-Moshpit-External-User-Id names one of your own customers (an external user),
not a Moshpit login. When you pass it, the minted session is scoped to that
customer: the editor reads and writes only scenes that customer owns, new scenes
the customer creates are private to them, and their uploads count against that
customer's storage and scene allocation. This is how resellers serve
isolated, per-customer libraries from one integration. See the
reseller guide for the full multi-tenant
flow.
Plan access
All plans can mint Viewer and Editor Embed sessions and use the REST API. Session mints and REST calls are not metered. Storage, Scene counts, and integration limits still apply. Enterprise supports pay-as-you-go overage.
Request body
| Field | Type | Description |
|---|---|---|
publicKey Required | string | The integration public key. Must start with `mpk_` and match the integration that owns this secret. |
type | "viewer" | "editor" default "editor" | Which capability the session should authorize. All plans may request `viewer` or `editor`, provided the integration has that capability. |
splatId | string | null | Optional 24-hex MongoDB ObjectId. If set, the splat must be owned by the integration owner. The viewer enforces this when loading; the editor uses it to choose which scene to open. |
user | object | null | Optional profile of the end user this session is minted for, signed into the session token. The viewer shows it in the multiplayer widget instead of a "Guest" identity. Omit for anonymous visitors — they join multiplayer as editable guests. |
user.name Required | string | Display name shown in the multiplayer widget (1–64 characters; longer names are truncated to 32 in-room). |
user.avatarUrl | string | null | Publicly reachable http(s) URL of the user’s profile picture (max 2048 characters). Shown next to the name; omit to fall back to an initial. |
Bash
curl -X POST https://moshpit.studio/api/editor/embed-sessions \
-H "Authorization: Bearer msk_YOUR_SECRET_KEY" \
-H "X-Moshpit-External-User-Id: host_user_123" \
-H "Content-Type: application/json" \
-d '{
"publicKey": "mpk_YOUR_PUBLIC_KEY",
"type": "viewer",
"splatId": "65f1a2b3c4d5e6f7a8b9c0d1",
"user": {
"name": "Ada Lovelace",
"avatarUrl": "https://your-app.com/avatars/ada.png"
}
}'Success response — 201 Created
JSON
{
"status": "success",
"sessionToken": "eyJhbGciOiJIUzI1NiI...",
"expiresAt": "2026-05-08T13:30:00.000Z"
}| Field | Type | Description |
|---|---|---|
sessionToken | string | The JWT to send to the browser. Pass it to the SDK as `sessionToken`, or stick it in the iframe URL as `?session=`. |
expiresAt | string (ISO 8601) | When the token will be rejected. Approximately 15 minutes after issuance. |
Error responses
| Status | Cause |
|---|---|
| 400 | Invalid JSON body, missing required external user header, or invalid type |
| 401 | Missing Authorization header, or the secret is invalid |
| 403 | Integration not enabled for the requested capability, plan limit reached, inactive external user, or out-of-scope splatId |
| 404 | The integration matching the public key was not found |
| 410 | The integration has been revoked |
JSON
{ "status": "error", "message": "Editor session is invalid or expired" }Example: Next.js route handler
TypeScript
// app/api/moshpit/viewer-session/route.ts
import { NextResponse } from 'next/server';
export async function POST(request: Request) {
const body = await request.json().catch(() => ({}));
const headers: Record<string, string> = {
'Content-Type': 'application/json',
Authorization: `Bearer ${process.env.MOSHPIT_SECRET_KEY}`,
};
if (body.private) {
headers['X-Moshpit-External-User-Id'] = await getCurrentHostUserId(request);
}
const upstream = await fetch(
'https://moshpit.studio/api/editor/embed-sessions',
{
method: 'POST',
headers,
body: JSON.stringify({
publicKey: process.env.MOSHPIT_PUBLIC_KEY,
type: 'viewer',
splatId: body.splatId,
}),
},
);
const data = await upstream.json();
return NextResponse.json(data, { status: upstream.status });
}Blank guest editor sessions on any plan may omit X-Moshpit-External-User-Id,
but they are read-only for cloud actions. The host should listen for the editor
authRequired event, sign the user into the host app, mint a scoped session
with X-Moshpit-External-User-Id, and send it back with updateSession.
Example: customer-scoped editor session (reseller)
Resellers forward the signed-in customer's id so the editor session is scoped to that customer's private library. Resolve the id from your own auth — never trust one sent by the browser.
TypeScript
// app/api/moshpit/editor-session/route.ts
import { NextResponse } from 'next/server';
export async function POST(request: Request) {
const body = await request.json().catch(() => ({}));
const customerId = await getCurrentCustomerId(request); // your auth
const upstream = await fetch(
'https://moshpit.studio/api/editor/embed-sessions',
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${process.env.MOSHPIT_SECRET_KEY}`,
'X-Moshpit-External-User-Id': customerId,
},
body: JSON.stringify({
publicKey: process.env.MOSHPIT_PUBLIC_KEY,
type: 'editor',
splatId: body.splatId ?? null,
}),
},
);
return NextResponse.json(await upstream.json(), { status: upstream.status });
}The customer must already be provisioned and active —
see PUT /api/v1/external-user.
Authorize before minting
Add your own auth checks before calling Moshpit. Only mint a session if the request comes from a logged-in user, includes a CSRF token, etc. Anyone who can hit your session endpoint can get a token.
Multiplayer identity
On multiplayer-enabled scenes the viewer shows a presence widget with each
player's name and avatar. A session minted without user joins as an editable
guest (Guest 3f9a1). Pass user with the profile of your signed-in end
user — resolved from your own auth, never from the browser — and the widget
shows that name and picture read-only instead. The profile travels inside the
signed session token, so page scripts cannot spoof it. user is independent of
X-Moshpit-External-User-Id: the header scopes data access, user only sets
the display identity.
See the walkthrough in Viewer Embed in Next.js → Show your signed-in user in multiplayer.
What's next
- Session Tokens — the full lifecycle.
- GET /v1/splats — find splat IDs to pass as
splatId. - External Users — scope sessions to your own customers and allocate per-customer quotas.