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_ID

This 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

FieldTypeDescription
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.

SH

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"
}
FieldTypeDescription
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

StatusCause
400Invalid JSON body, missing required external user header, or invalid type
401Missing Authorization header, or the secret is invalid
403Integration not enabled for the requested capability, plan limit reached, inactive external user, or out-of-scope splatId
404The integration matching the public key was not found
410The integration has been revoked
{}

JSON

{ "status": "error", "message": "Editor session is invalid or expired" }

Example: Next.js route handler

TS

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.

TS

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