Owner's Note: If you are creating tools around MultiMuse please let us know in the support Discord! We would love to know what people are building.
Example of something built with MultiMuse API : https://github.com/Tea0S/multimuse-tracker
MultiMuse HTTP API (v1)
Public reference for building tools against MultiMuse (bots, dashboards, desktop apps, and browser backends).
Base URL: https://api.multimuse.app
Every path below is under /api/v1 and requires Authorization: Bearer <your_api_key> unless noted.
Getting started
-
In Discord DM with MultiMuse, run
/api generateand store the key securely (shown once). -
Call
GET /api/v1/auth/meto confirm which Discord user owns the key. -
Use that user’s ID in query/body fields when reading or writing on their behalf.
Do not embed API keys in public front-end code. Browser apps should proxy requests through your own backend.
Authentication
Authorization: Bearer <your_api_key>
Discord commands for keys
-
/api generate [name]— Create a new key (shown once) -
/api list— List your keys -
/api revoke <key_id>— Deactivate a key -
/api delete <key_id>— Permanently delete a key -
/api consent list— Keys you allowed to act on your account -
/api consent revoke <key_id>— Remove that permission
Who owns this key?
GET /api/v1/auth/me
{
"status": "ok",
"user_id": "123456789012345678"
}
CORS: Cross-origin browser calls are allowed (Access-Control-Allow-Origin: *). CLI and server tools are unaffected.
Authorization
Reads
Any valid API key may read public data when you pass a target Discord user ID (for example ?user_id=). That supports shared server tools and dashboards.
Private muses are never returned unless the key owner owns the muse or has an accepted share. See Private muses.
Writes (mutations)
Writes use a user_id in the JSON body (muse owner or account being updated).
-
Key owner is the target
user_id— Allowed immediately. -
Key owner is someone else — Target user must approve in Discord (unless a first-party service key; see below).
-
Allow always grant for this key — Allowed until revoked.
-
Allow once grant — One successful mutation, then consent is required again.
When consent is required, the API returns 403 with:
{
"status": "error",
"code": "consent_required",
"message": "Consent required: the target user must approve this API key in Discord.",
"request_id": "abc123...",
"target_user_id": "987654321098765432",
"key_id": "your_key_id"
}
The target user gets a MultiMuse DM with Allow once, Allow always, or Deny. Each key has separate consent; grants do not carry over between keys.
Endpoints that require mutation consent (when key owner ≠ body user_id):
-
POST /api/v1/threads/track -
POST /api/v1/messages/post -
Legacy Obsidian-oriented scene routes (not listed in this public doc)
Endpoints that do not use mutation consent (any valid key):
POST /api/v1/scenes/end— Onlythread_idin the body; ends tracking for everyone on that thread
First-party service keys (not for integrators)
MultiMuse, StageHand, and the website dashboard use long-lived service bearer tokens configured on the server (not created with /api generate). Those keys skip consent and may call internal routes.
If you are building a third-party integration, you only need a user key from /api generate. You do not need STAGEHAND_API_KEY or MULTIMUSE_SERVICE_API_KEY.
Private muses
A muse marked private in MultiMuse is hidden from API reads unless:
-
The API key owner owns the muse, or
-
The key owner has an accepted share for that muse.
Applies to:
-
GET /api/v1/muses/list -
GET /api/v1/muses/card -
GET /api/v1/guilds/{guild_id}/muses -
GET /api/v1/muses/wrappers/resolve
Querying ?user_id= for another member returns only their public muses plus muses they shared with you. Private muses never appear in lists or cards for other owners.
Rate limits
Exceeded limits return 429 with an error message.
Limits apply per API key (after authentication).
General (all routes except message post)
-
GET (and non-POST methods): 50 requests per minute, 200 per hour
-
POST (except
messages/postbelow): 30 per minute, 120 per hour
Message post only
POST /api/v1/messages/post uses a separate bucket per user_id in the body:
-
10 posts per minute
-
50 posts per hour
Calls that hit Discord are also subject to Discord’s own rate limits.
Endpoints
GET /api/v1/auth/me
Returns the Discord user ID that owns the bearer key. See Authentication.
Tracked threads (read)
GET /api/v1/guilds/{guild_id}/threads/tracked
Active thread tracks in one server.
Response: { "guild_id": "...", "threads": [ ... ] }
Each thread object may include:
-
thread_id— Discord thread ID (string) -
guild_id— Server ID (string) -
thread_name— Resolved from Discord when possible -
last_reply_at— ISO timestamp -
participants— Expected participant count -
muse_names— Muses on this thread -
user_ids— Discord user IDs with active rows
GET /api/v1/threads/tracked?user_id=<discord_user_id>
Active tracks for one user across servers.
Response: { "threads": [ ... ] }
Each row includes thread_id, guild_id, muse_name, participants, thread_name, last_reply_at, and optionally muse_names when multiple muses share a thread.
POST /api/v1/threads/track
Create or update Discord-side thread tracking.
Mutation consent applies when the key owner ≠ body user_id.
Body (JSON):
{
"thread_id": 1234567890123456789,
"user_id": 987654321098765432,
"muse_name": "My Muse",
"guild_id": 111111111111111111,
"participants": 2
}
-
thread_id(required) — Discord thread ID -
user_id(required) — Muse owner / tracker user -
muse_name(required) — Display name of the muse -
guild_id(optional) — Inferred from the thread if omitted -
participants(optional) — Default2
Success: { "status": "ok" }
Muses (read)
GET /api/v1/muses/list?user_id=<id>
List muses for a user (own + shared). Comma-separated user_ids=123,456 is supported. Respects private muses.
Response: { "muses": [ { "muse_id", "name", "trigger", "tags", "owner_id", "is_shared" }, ... ] }
GET /api/v1/muses/card?name=<partial>&guild_id=<id>&user_id=<id>
Resolve a muse card by partial name (case-insensitive).
-
name(required) -
guild_id(optional) — Restrict to the server’s default tag when set -
user_id(optional) — Restrict to one owner
Single match: { "status": "ok", "muse": { ... } } (includes private, owner_id, tags, avatar, etc.)
Multiple matches: { "status": "multiple_matches", "matches": [ ... ] }
Inaccessible private muses are omitted; you may get 404 if nothing visible matches.
GET /api/v1/guilds/{guild_id}/muses?user_id=<id>
Muse names available to a user in a guild (respects server default tag). Accepts user_ids=123,456. Respects private muses.
Response: { "muses": [ { "name", "owner_id" }, ... ], "guild_id": "..." }
GET /api/v1/guilds/{guild_id}/members
Member list for mention helpers. Bots excluded.
Response: { "members": [ { "id", "username", "display_name" }, ... ] }
GET /api/v1/guilds/{guild_id}/dice_presets
Dice preset definitions pushed from StageHand (read-only for integrators).
GET /api/v1/muses/wrappers/resolve?thread_id=&user_id=&muse_name=
Resolve header/footer wrapper text for posting as a muse in a thread. Requires muse_id or muse_name. Respects private muses.
Response: { "header": "...", "footer": "...", "muse_id": "..." }
POST /api/v1/messages/post
Post a message as a muse via webhook.
Mutation consent applies when the key owner ≠ body user_id.
Body (JSON):
{
"thread_id": 1234567890123456789,
"user_id": 987654321098765432,
"muse_name": "My Muse",
"content": "Hello from the API!"
}
-
thread_id(required) — Target thread or channel -
user_id(required) — Muse owner (or sharee with access) -
muse_nameormuse_id(one required) -
content(required) — Message text (Discord limits apply) -
fast(optional) — Iftrue, returns 202 and delivers in the background
Success: { "status": "ok", ... } or { "status": "accepted", ... } with 202 when fast is set.
See Rate limits for post-specific caps.
POST /api/v1/scenes/end
End scene tracking for a Discord thread (deactivates registry rows and removes thread tracking). Open to any integrator with a valid API key. No mutation consent and no user_id in the body.
Body (JSON):
{
"thread_id": 1234567890123456789
}
thread_id(required)
Success: { "status": "ok" }
Third parties may build similar scene workflows; consent rules for other scene routes do not apply to this endpoint.
Error responses
Most errors:
{
"status": "error",
"message": "Human-readable explanation"
}
HTTP status codes:
-
400 — Invalid or missing parameters
-
401 — Missing or invalid API key
-
403 — Forbidden (consent required, muse not accessible, service-only route, etc.)
-
404 — Resource not found
-
429 — Rate limit exceeded
-
500 — Server error
Consent denials include "code": "consent_required".
Not part of this public API
The following exist on the server but are not for third-party integrators:
-
GET /api/v1/health— Operations only (unauthenticated) -
Obsidian plugin routes —
scenes/register,scenes/create,scenes/query, and related legacy paths -
POST /api/v1/muses/sync— Internal proxy name sync -
POST /api/v1/guilds/{guild_id}/dice_presets— StageHand push only (X-StageHand-Keyor service bearer) -
POST /api/v1/proxies/invalidate-cache— MultiMuse / dashboard service keys only (user keys receive 403)
Integration patterns
Your own account
-
/api generatein Discord DM -
GET /api/v1/auth/me -
Read/write with your user ID — no consent DM
Server bot or shared tool
-
Tool holder generates an API key
-
When acting on another user’s tracking or posts, that user gets a consent DM
-
After Allow always, retries work until
/api consent revoke
Desktop or CLI
Store the key locally; send Authorization: Bearer ... on each request.
Browser app
Proxy API calls through your backend; never ship keys to the client.
Quick reference
-
Who am I? —
GET /api/v1/auth/me -
Tracks in a server —
GET /api/v1/guilds/{guild_id}/threads/tracked -
Tracks for a user —
GET /api/v1/threads/tracked?user_id= -
Start/update tracking —
POST /api/v1/threads/track -
List muses —
GET /api/v1/muses/list?user_id= -
Muse card —
GET /api/v1/muses/card?name= -
Guild muse names —
GET /api/v1/guilds/{guild_id}/muses?user_id= -
Post as muse —
POST /api/v1/messages/post -
End scene / stop tracking —
POST /api/v1/scenes/end -
Revoke a tool —
/api consent revokein Discord DM