Getting Started

/

External Users

External Users

If you embed Moshpit and resell it to your own customers, external users let you give each customer their own storage and scene allocation, keep their scenes private to them, and bill them however you like — while Moshpit bills your Studio account for the total.

Plan access

The external-user API and all /api/v1/* endpoints are available on Free, Pro, and Enterprise. Account quotas and per-customer allocations still apply.

Who is an external user?

An external user is your customer, not a Moshpit login. Picture a digital-twin platform — call it Unreal Twin — that embeds Moshpit and sells it onward. Unreal Twin is the reseller: it holds one integration (mpk_ / msk_). Each company that signs up with Unreal Twin is an external user: a tenant provisioned through that one integration.

  • Moshpit never authenticates your customers. You bring your own customer auth.
  • You identify each customer with your own string in the X-Moshpit-External-User-Id header. Moshpit treats it as opaque.
  • Each customer gets an isolated library: scenes a customer creates are private to that customer.

How billing works

Moshpit bills your Moshpit account for the aggregate storage and scene usage of all your customers, plus any overage allowed by your plan. Moshpit does not bill your customers — you set their prices and bill them yourself, externally.

Per-customer limitBytes (storage) and maxSplats (scenes) are allocation knobs you control. Because billing is on your account total, you can over-allocate: the sum of every customer's allocation may exceed your Enterprise bundle. The per-customer caps simply decide which customer gets blocked when they fill what you gave them.

The end-to-end flow

  1. Provision a customer. PUT /api/v1/external-user with your customer id in the header. This upserts the customer record.
  2. Allocate storage and scenes. Set limitBytes (per-customer storage) and maxSplats (per-customer scene cap) on that same PUT.
  3. Mint a scoped session. Call POST /api/editor/embed-sessions with the X-Moshpit-External-User-Id header so the viewer/editor session is scoped to that customer. Scenes the customer creates are private to them.
  4. Caps enforce automatically. Uploads past limitBytes get 413; scenes past maxSplats get 403. These are hard per-customer caps — they fire even when your Studio account still has room.
  5. Raise a limit when a customer hits it. PUT again with a higher limitBytes or maxSplats to unblock them.
  6. Read per-customer usage. GET /api/v1/external-user/usage to drive a usage meter in your own dashboard.
  7. Mirror your billing state. When your customer downgrades, cancels, or is suspended, PUT their new caps plus billingState and optional keepSplatIds. Studio locks overflow scenes and owns the retained-archive maintenance.

1. Provision and allocate

A single PUT both creates the customer and sets their allocation. Run it from your backend — the msk_ secret never goes near a browser.

SH

Bash

curl -X PUT https://moshpit.studio/api/v1/external-user \
  -H "Authorization: Bearer msk_YOUR_SECRET_KEY" \
  -H "X-Moshpit-Public-Key: mpk_YOUR_PUBLIC_KEY" \
  -H "X-Moshpit-External-User-Id: customer_42" \
  -H "Content-Type: application/json" \
  -d '{
    "limitBytes": 5368709120,
    "maxSplats": 10,
    "displayName": "Acme Robotics"
  }'

This gives customer_42 a 5 GB storage allocation and a cap of 10 scenes.

Defaults and partial updates

Every field is optional. Omit a field on a later PUT to leave it untouched. If you omit limitBytes on a brand-new customer, they default to your Moshpit account storage bundle. Set maxSplats to null for an uncapped customer.

2. Mint a session scoped to the customer

When that customer opens your app, mint a session with their id in the header. The session is scoped to the customer, so scenes they create are private to them, and their uploads count against their allocation.

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: customer_42" \
  -H "Content-Type: application/json" \
  -d '{ "publicKey": "mpk_YOUR_PUBLIC_KEY", "type": "editor" }'

In practice you forward the header from your own session endpoint. For example, a Next.js route handler:

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(() => ({}));
  // Resolve the signed-in customer from YOUR auth, not from the client.
  const customerId = await getCurrentCustomerId(request);
 
  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 });
}

Always resolve the customer id on your server

Derive X-Moshpit-External-User-Id from your own authenticated session — never trust a customer id sent from the browser. Otherwise one customer could mint a session scoped to another.

3. Per-customer caps in action — a worked example

Say customer_42 has the 5 GB / 10-scene allocation from step 1, and they fill it up.

They hit the storage cap. Their next upload would cross 5 GB, so the commit is rejected with 413 — even though your Moshpit account still has plenty of storage:

{}

JSON

{
  "status": "error",
  "code": "STORAGE_QUOTA_EXCEEDED",
  "usedBytes": 5368709120,
  "limitBytes": 5368709120,
  "remainingBytes": 0,
  "requestedBytes": 268435456
}

They hit the scene cap. Creating an 11th scene is rejected with 403:

{}

JSON

{
  "status": "error",
  "code": "plan_limit",
  "dimension": "splatCount",
  "limit": 10,
  "current": 10,
  "message": "This customer's scene limit (10) has been reached.",
  "upgradeUrl": "/account/billing?upgrade=plan"
}

(upgradeUrl comes from the shared plan-limit envelope and points at your own Moshpit billing — for a per-customer cap, just raise the customer's maxSplats.)

You raise the limit. Bump the allocation with another PUT — say to 20 GB and 25 scenes — and the customer is immediately unblocked:

SH

Bash

curl -X PUT https://moshpit.studio/api/v1/external-user \
  -H "Authorization: Bearer msk_YOUR_SECRET_KEY" \
  -H "X-Moshpit-Public-Key: mpk_YOUR_PUBLIC_KEY" \
  -H "X-Moshpit-External-User-Id: customer_42" \
  -H "Content-Type: application/json" \
  -d '{ "limitBytes": 21474836480, "maxSplats": 25 }'

This is the natural hook for your own upsell: when a customer hits a cap, charge them on your side, then raise their allocation.

4. Read per-customer usage

Drive a usage meter for each customer from your dashboard:

SH

Bash

curl "https://moshpit.studio/api/v1/external-user/usage" \
  -H "Authorization: Bearer msk_YOUR_SECRET_KEY" \
  -H "X-Moshpit-Public-Key: mpk_YOUR_PUBLIC_KEY" \
  -H "X-Moshpit-External-User-Id: customer_42"
{}

JSON

{
  "status": "success",
  "usage": {
    "storage": {
      "usedBytes": 4294967296,
      "limitBytes": 5368709120,
      "remainingBytes": 1073741824,
      "percentUsed": 80
    },
    "splats": { "used": 7, "limit": 10 }
  }
}

storage.limitBytes and splats.limit are this customer's allocation — splats.limit is null for an uncapped customer.

5. Handle customer downgrade or cancellation

When your customer changes plan in your product, Studio should receive the same state so hosted assets stay in sync with your billing system.

For a downgrade, send the lower caps and optionally the scenes the customer chose to keep active:

SH

Bash

curl -X PUT https://moshpit.studio/api/v1/external-user \
  -H "Authorization: Bearer msk_YOUR_SECRET_KEY" \
  -H "X-Moshpit-Public-Key: mpk_YOUR_PUBLIC_KEY" \
  -H "X-Moshpit-External-User-Id: customer_42" \
  -H "Content-Type: application/json" \
  -d '{
    "billingState": "active",
    "limitBytes": 1073741824,
    "maxSplats": 3,
    "keepSplatIds": ["65f1a2b3c4d5e6f7a8b9c0d1"]
  }'

For a cancellation where your free state has no quota, set zero caps:

SH

Bash

curl -X PUT https://moshpit.studio/api/v1/external-user \
  -H "Authorization: Bearer msk_YOUR_SECRET_KEY" \
  -H "X-Moshpit-Public-Key: mpk_YOUR_PUBLIC_KEY" \
  -H "X-Moshpit-External-User-Id: customer_42" \
  -H "Content-Type: application/json" \
  -d '{
    "billingState": "canceled",
    "limitBytes": 0,
    "maxSplats": 0
  }'

Scenes outside the keeper/cap set become private and non-embeddable retained archive. They disappear from normal product surfaces and are released from user-facing storage counters while hosted assets remain stored.

Suspending and deleting a customer

  • Temporarily suspend without retained archive: PUT with "status": "inactive". An inactive customer cannot mint scoped sessions or upload; their active scenes remain as-is.
  • Billing suspension/cancellation: use billingState so Studio applies retained archive rules.
  • Delete entirely: DELETE /api/v1/external-user soft-deletes the customer and cascade-deletes all of their scenes, returning deletedSplatCount. This is the explicit destructive path, not the normal subscription-cancel path.

A note on capabilities

Whether a session can view or edit is decided per integration, not per customer. Per-customer allocation governs storage and scene counts; it does not change what a session is allowed to do.

What's next