API Reference

/

External Users API

External Users API

Resellers embed Moshpit and serve their own customers. Each of those customers is an external user — a tenant you provision through your integration, give a per-customer storage and scene allocation, and bill yourself. These four endpoints manage that lifecycle. For the conceptual model and a worked walkthrough, start with the reseller / multi-tenant guide.

REST API access

REST API access is included on Free, Pro, and Enterprise. Storage, Scene counts, and integration limits still apply.

Auth

Every external-user endpoint requires all three headers. The X-Moshpit-External-User-Id header is mandatory here — it names the customer the request operates on, and it is your own opaque identifier for that customer, not a Moshpit login:

Authorization: Bearer msk_YOUR_SECRET_KEY
X-Moshpit-Public-Key: mpk_YOUR_PUBLIC_KEY
X-Moshpit-External-User-Id: YOUR_CUSTOMER_ID

Requests missing X-Moshpit-External-User-Id return 400. The value is your own string (up to 256 characters, no line breaks); Moshpit treats it as opaque and scopes the customer record to { integration, externalUserId }.

The secret stays on your backend

These endpoints authenticate with the msk_ secret. Call them from your server — provisioning, raising allocations, and reading per-customer usage are back-office operations. Never expose the secret to a browser.

The externalUser object

PUT and GET return the customer record under externalUser. DELETE returns the final state of the record (or null).

FieldTypeDescription
id
string

The Moshpit record id (MongoDB ObjectId) for this customer.

integrationId
string

The embed integration this customer belongs to.

externalUserId
string

Your own identifier for the customer — the value you sent in `X-Moshpit-External-User-Id`.

productSlug
string

The integration's product boundary (its saved Site domain).

displayName
string | null

Optional human label you set for the customer.

avatarUrl
string | null

Optional avatar URL you set for the customer.

emailHash
string | null

Optional opaque hash you set (Moshpit never stores customer emails in clear text).

metadata
object | null

Arbitrary key/value bag you control (≤ 20 keys, ≤ 4 KB serialized).

status
"active" | "inactive"

Inactive customers cannot mint scoped sessions or upload. Soft-deleted customers report `deleted` internally.

billingState
"active" | "suspended" | "canceled"

`active` enforces the stored caps. `suspended` or `canceled` makes the customer inactive and moves scenes outside the supplied keeper/cap set into retained archive.

usedBytes
number

The customer's current storage consumption, in bytes.

limitBytes
number

The customer's storage allocation, in bytes. This is the hard cap enforced on upload.

maxSplats
number | null

The customer's scene cap. `null` means no per-customer cap.

remainingBytes
number

`max(0, limitBytes − usedBytes)`.

percentUsed
number

`usedBytes / limitBytes`, clamped to 0–100. `0` when the limit is 0.

deletedAt
string | null

ISO 8601 timestamp set when the customer is soft-deleted.

createdAt
string | null

ISO 8601 creation timestamp.

updatedAt
string | null

ISO 8601 last-update timestamp.

Over-allocation is allowed by design

You can hand out more storage and scenes across your customers than your account bundle contains. Per-customer limitBytes and maxSplats are hard caps on each customer; Moshpit bills your account for the aggregate usage and applies your plan's overage rules. See Billing.

PUT /api/v1/external-user

Provision a new customer or update an existing one. This is an upsert keyed on the integration plus X-Moshpit-External-User-Id.

PUT

https://moshpit.studio/api/v1/external-user

Body

Every field is optional. Omitted fields are left unchanged on an existing customer; on a brand-new customer, omitting limitBytes defaults the allocation to your Studio account storage bundle.

FieldTypeDescription
limitBytes
number (int ≥ 0)

The customer's storage allocation, in bytes. Omit to leave unchanged, or — on a new customer — to default to the account bundle. Raise this to unblock a customer that hit their cap.

maxSplats
number (int ≥ 0) | null

The customer's scene cap. `null` removes the per-customer cap. Omit to leave unchanged.

status
"active" | "inactive"
default unchanged (`active` on create)

Set `inactive` to suspend a customer without deleting it. Omit to leave an existing customer unchanged.

billingState
"active" | "suspended" | "canceled"
default unchanged (`active` on create)

Use this to mirror your product billing state. `suspended` and `canceled` force the customer inactive; omitted caps default to `0` for those states. Omit to leave an existing customer unchanged.

keepSplatIds
string[]

Optional scene ids to keep active when lowering caps or changing billing state. Selected scenes must fit `maxSplats` and `limitBytes`; all other scenes become private, non-embeddable retained archive.

displayName
string

Optional label, 1–120 characters.

avatarUrl
string (URL)

Optional avatar URL, up to 2048 characters.

emailHash
string

Optional opaque hash, 8–128 characters.

metadata
object

Arbitrary key/value bag. String, number, boolean, or null values. ≤ 20 keys and ≤ 4 KB serialized.

Example request

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",
    "status": "active"
  }'

Success response — 200 OK

{}

JSON

{
  "status": "success",
  "externalUser": {
    "id": "65f1a2b3c4d5e6f7a8b9c0d1",
    "integrationId": "65e0b1c2d3e4f5a6b7c8d9e0",
    "externalUserId": "customer_42",
    "productSlug": "app.example.com",
    "displayName": "Acme Robotics",
    "avatarUrl": null,
    "emailHash": null,
    "metadata": null,
    "status": "active",
    "billingState": "active",
    "usedBytes": 0,
    "limitBytes": 5368709120,
    "maxSplats": 10,
    "remainingBytes": 5368709120,
    "percentUsed": 0,
    "deletedAt": null,
    "createdAt": "2026-06-18T10:00:00.000Z",
    "updatedAt": "2026-06-18T10:00:00.000Z"
  }
}

GET /api/v1/external-user

Fetch one customer's record.

GET

https://moshpit.studio/api/v1/external-user

Example request

SH

Bash

curl "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"

Success response — 200 OK

Returns { status: "success", externalUser: { ... } } with the externalUser object. Returns 404 if no customer exists for that id under the integration.

GET /api/v1/external-user/usage

Read a single customer's usage and allocation — handy for rendering a usage meter inside your own dashboard.

GET

https://moshpit.studio/api/v1/external-user/usage

Example request

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"

Success response — 200 OK

{}

JSON

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

The customer's `usedBytes`, `limitBytes`, `remainingBytes`, and `percentUsed`. `limitBytes` is the customer's allocation, not the account bundle.

usage.splats.used
number

Live count of scenes the customer currently owns.

usage.splats.limit
number | null

The customer's `maxSplats`. `null` means the customer has no per-customer scene cap.

Returns 404 if the customer does not exist.

Billing state and retained archive

Use PUT /api/v1/external-user rather than DELETE when your customer cancels, downgrades, or temporarily loses access in your own product. Studio owns the hosted assets and applies the retention lifecycle.

  • billingState: "active" keeps the customer usable and applies the supplied limitBytes / maxSplats.
  • billingState: "suspended" makes the customer inactive and locks scenes outside the supplied keeper/cap set.
  • billingState: "canceled" makes the customer inactive and schedules retained archive.
  • keepSplatIds can name specific scenes to keep active, as long as they fit the supplied caps.
  • protectedSplatIds can name scenes to protect from the retention purge. Protected scenes are still archived like any other overflow, but they are never auto-removed and stay restorable indefinitely.

Retained scenes become private and non-embeddable, disappear from normal product surfaces, and are released from user-facing storage counters. Unprotected retained scenes are permanently removed 44 days after they were archived (14-day grace + 30-day dormancy); until then, raising the customer's caps or setting billingState: "active" restores them automatically up to the caps. Scenes named in protectedSplatIds are exempt from the 44-day removal.

A PUT that only upserts identity fields (no billingState, limitBytes, maxSplats, keepSplatIds, or protectedSplatIds) never re-applies the retention lifecycle — it is safe to call before every session mint.

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
  }'

DELETE /api/v1/external-user

Soft-delete a customer and cascade-delete every scene they own (including the underlying assets and view/comment records). The customer's storage is released. This is the explicit destructive path; do not use it for ordinary subscription cancellation or downgrade.

DELETE

https://moshpit.studio/api/v1/external-user

Example request

SH

Bash

curl -X DELETE "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"

Success response — 200 OK

{}

JSON

{
  "status": "success",
  "deletedSplatCount": 7,
  "externalUser": {
    "id": "65f1a2b3c4d5e6f7a8b9c0d1",
    "externalUserId": "customer_42",
    "status": "deleted",
    "billingState": "canceled",
    "usedBytes": 0,
    "deletedAt": "2026-06-18T11:30:00.000Z"
  }
}
FieldTypeDescription
deletedSplatCount
number

How many of the customer's scenes were removed.

externalUser
object | null

The soft-deleted customer record (`status: "deleted"`, `usedBytes: 0`), or `null`.

Enforcement

Per-customer caps are hard and independent of your account pool — a customer can be blocked even while your account still has room. You unblock a customer by raising their allocation with PUT.

Storage — 413

An upload that would push the customer past their limitBytes is rejected, even if your account still has storage:

{}

JSON

{
  "status": "error",
  "code": "STORAGE_QUOTA_EXCEEDED",
  "scope": "customer",
  "usedBytes": 5368709120,
  "limitBytes": 5368709120,
  "remainingBytes": 0,
  "requestedBytes": 134217728
}

Raise the customer's limitBytes with PUT /api/v1/external-user to unblock the upload.

scope distinguishes the two storage blocks:

  • "customer" — the customer's per-customer allocation (above). Raise their limitBytes.
  • "account" — the upload would exceed your whole Moshpit account and overage isn't configured. With overage on, the account accrues overage and this never fires; with overage off, customer uploads are blocked until you configure overage or free account-wide space. usedBytes / limitBytes here are the account aggregate, not the customer's.

Scenes — 403

Creating a scene beyond the customer's maxSplats is rejected with the shared plan-limit envelope:

{}

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

This is the shared plan-limit envelope, so it also carries upgradeUrl. That deep link targets your own plan upgrade flow and is not meaningful to your customers — for a per-customer cap, ignore it and raise the customer's maxSplats (or set it to null) with PUT to unblock.

Capabilities stay per-integration

Whether a session can view or edit is decided per integration, not per customer. Per-customer allocation governs storage and scene counts only.

Billing

Moshpit bills your Moshpit account for the aggregate of every customer's storage and scene usage, plus any overage allowed by your plan. Per-customer limitBytes and maxSplats are allocation knobs you control for your own tenants — Moshpit does not bill your customers. You price and bill them yourself, externally.

Because the account is billed on the total, you may over-allocate across customers: the sum of every customer's limitBytes can exceed your included bundle.

Error responses

StatusCause
400Invalid JSON body, schema validation failure, or missing external user header
401Missing or invalid Bearer / public-key combination
403REST access disabled for the account, or a per-customer Scene cap was reached
404The customer does not exist under this integration
410The integration has been revoked
413The customer's storage allocation (limitBytes) would be exceeded by an upload

What's next