Skip to content

Get started

Authentication

People sign in with a browser session; code uses a workspace API key. Workspaces, roles, seats and keys.

Lumorig has two kinds of credentials. People sign in to the studio with a browser session. Code (your server, the SDK, the CLI, MCP clients) uses an API key. Both act on a workspace: the unit that owns characters, credits, keys and webhooks.

Browser sessionAPI key
WhoA signed-in personYour code, with no person attached
HowhttpOnly cookie, set by signing in at /loginAuthorization: Bearer lr_sk_…
WorkspaceThe active one (switcher), or the Lumorig-Workspace header (the older MascotForge-Workspace spelling also works)The key's workspace, always
PowersYour role in that workspaceAdmin: content, credits, keys, webhooks. Not people or billing.
Lifetime30 days from last use; revocableUntil revoked

Signing in#

There are no passwords. Sign in at /login with a one-time email link, or with Google or GitHub where the server has them configured. The first sign-in creates your account and a personal workspace on the Free plan.

  • Email links work once and expire after 15 minutes. Opening one shows a confirm button, so mail scanners that prefetch links can't use them up. Requests are rate-limited per address and per network.
  • Google and GitHub sign you in to the account with the same verified email address, and can be connected or disconnected under Settings → Account. An unverified provider address never signs in to an existing account.
  • Sessions are random tokens stored only as hashes. Each lasts 30 days from its last use. Settings → Account lists every signed-in browser, and you can revoke one or sign out everywhere.

Workspaces, roles and seats#

Everything you make belongs to a workspace, and everyone in it shares its characters and credit balance. You can belong to several (your personal one plus teams) and switch between them in the studio's top bar.

RoleCan
OwnerEverything an admin can, plus change the plan, transfer ownership and delete the workspace. Exactly one per workspace.
AdminInvite and remove people, change roles, create and revoke API keys, manage webhooks, make characters.
MemberMake, edit, animate and export characters, and spend the workspace's credits. Can't see or manage keys, webhooks or people.

Seats cap how many people a workspace holds: Free 1, Maker 1, Studio 3. A pending invite holds a seat until it's accepted, revoked or expires (7 days). Inviting beyond the limit returns 402 with code seat_limit:

402 Payment Required
{
  "error": {
    "code": "seat_limit",
    "message": "Studio includes 3 seats and all are taken (2 members and 1 pending invite). Remove a member or revoke an invite to free one.",
    "details": { "seats": 3, "used": 3, "members": 2, "pending": 1 }
  }
}

Invites are sent by email and can only be accepted by someone signed in with the invited address (or a Google/GitHub account with that verified address). Managing people needs a signed-in person: API keys get 403 session_required on these endpoints.

EndpointDoes
GET/workspaceThe workspace this request acts on: name, plan, your role, and seats. Works with keys.
GET/workspace/membersEveryone in the workspace, owner first. Works with keys.
POST/workspace/invitesInvite { "email", "role": "member" | "admin" }. Admins; session only.
PATCH/workspace/members/{user_id}Change a role. Admins; only the owner changes another admin.
DELETE/workspace/members/{user_id}Remove someone, or leave (your own id). Owners transfer first.
POST/workspace/transferMake another member the owner; you become an admin. Owner only.

API keys#

A key belongs to one workspace and acts with admin powers there: create and edit mascots, spend its credits, manage webhooks and other keys. It isn't tied to the person who made it (the key list shows who did), so it keeps working when they leave. Keys look like lr_sk_ followed by 32 random characters. Keys created before the rename start with mf_sk_ and keep working. We store only a hash, so the full key is shown exactly once, when you create it.

Admins and the owner create keys in the studio under Settings → API keys. After that you can also create keys from code (below).

Sending the key#

Send it as a Bearer token in the Authorization header on every request to /api/v1. A missing or unknown key returns 401 with code unauthorized.

curl https://lumorig.com/api/v1/mascots \
  -H "Authorization: Bearer $LUMORIG_API_KEY"

MCP clients that cannot set headers may pass the key as a query parameter instead: https://lumorig.com/api/mcp?key=lr_sk_…. Prefer the header where you can; URLs end up in logs.

Managing keys#

Keys can create, list and revoke other keys in the same workspace. Listing never returns the secret, only its first 12 characters.

EndpointDoes
POST/keysCreate a key. Body: { "name": "CI" } (optional). Returns { id, key, prefix }; key is shown once.
GET/keysList active keys: id, name, prefix, created_at, last_used_at.
DELETE/keys/{id}Revoke a key. Requests with it return 401 from then on.
curl
curl -X POST https://lumorig.com/api/v1/keys \
  -H "Authorization: Bearer $LUMORIG_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "name": "CI" }'
# 201 → { "id": "key_…", "key": "lr_sk_…", "prefix": "lr_sk_AbCdEf" }

Public and shared bundles#

Two read-only endpoints are built to be called from browsers without a key: the player bundle and the still preview. They serve a mascot in two cases.

Public mascots#

Set public: true on a mascot (PATCH /mascots/{id}) and its /bundle and /preview work for anyone who has the id. The bundle is served with open CORS so <lumorig-player> can load it from any origin.

For a private mascot, POST /mascots/{id}/share returns a token and a ready bundle_url with ?token= attached. The token is an HMAC-signed mascot id plus expiry; ttl_seconds defaults to 3600 and is capped at 30 days. An expired or mismatched token returns 403 forbidden.

curl
curl -X POST https://lumorig.com/api/v1/mascots/msc_…/share \
  -H "Authorization: Bearer $LUMORIG_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "ttl_seconds": 604800 }'
# → { "token": "…", "expires_at": "2026-10-03T12:00:00.000Z", "bundle_url": "/api/v1/mascots/msc_…/bundle?token=…" }

Environment variables#

The SDK, the CLI and the local MCP server read the same two variables.

VariableUsed byMeaning
LUMORIG_API_KEYSDK, CLI, MCP (stdio)Your lr_sk_… key. For the CLI it overrides the key saved by lumorig login.
LUMORIG_URLSDK, CLI, MCP (stdio)API origin. Defaults to https://lumorig.com; set it for a self-hosted server.
LUMORIG_HOMECLIWhere lumorig keeps its config and companion state. Defaults to ~/.config/lumorig.

Names from before the rename (MASCOTFORGE_API_KEY, MASCOTFORGE_URL, MASCOTFORGE_HOME) are still read as fallbacks.

Self-hosting#

A server started without MF_LOCAL=0 runs in local mode: /login offers Continue locally, which signs in as the local owner of the workspace that holds everything created before accounts existed. Set MF_LOCAL=0 in production, plus MF_PUBLIC_URL, RESEND_API_KEY and MAIL_FROM for email, and GOOGLE_CLIENT_ID/GOOGLE_CLIENT_SECRET and GITHUB_CLIENT_ID/GITHUB_CLIENT_SECRET to offer those buttons (callback: {MF_PUBLIC_URL}/api/auth/oauth/google/callback, and …/github/callback). Without a mailer, development servers print sign-in links to the console.