Skip to content

API Reference

RadicalMuffinMan edited this page Nov 12, 2026 · 16 revisions

API Reference

Every endpoint Moonbase exposes on the server. This page is for developers and for admins debugging an integration. Normal use of Moonfin never needs it.

Paths are absolute and served by both the Jellyfin and Emby plugins unless noted.

Auth column:

  • Yes requires any valid Jellyfin or Emby session.
  • Admin requires an elevated (administrator) session.
  • Session uses a stored Seerr session rather than the platform token.
  • No is anonymous (used for web assets, discovery, and the inbound Seerr webhook, which has its own shared secret).

Some areas are gated by config: the Settings and public Themes endpoints return 503 when settings sync is disabled, and the Games endpoints return empty results or 404 when the game library feature is turned off.

Status and settings

Method Path Auth Description
GET /Moonfin/Ping Yes Plugin status: version, whether sync is enabled, Seerr/MDBList/TMDB availability, admin defaults, and feature flags so older clients can hide the features they lack. An optional profile query (tv, desktop or mobile) returns defaultSettings with that device's admin defaults layered over the Global ones. messagesSupported is on both servers. recommendationsSupported follows the Use Moonfin Recommends for similar items switch. clientLogSupported is on Emby, Silo and Plex and follows Enable client log upload. socketRelay is Jellyfin only and follows SyncPlay relay for Roku. arrQueueAvailable says whether the download queue can be read: Seerr turned on with its URL set, or on Plex, a Sonarr or Radarr set on the admin page. pinSupported, tmdbThumbsSupported, tmdbDetailsSupported, tmdbPersonLinksSupported, tmdbReviewsSupported and extendedCompleteShowingsSupported are on both servers, incompleteRecordingsSupported follows Detect incomplete recordings, and smartSearchEnabled follows Enable smart search
GET /Moonfin/Defaults Yes The admin-configured default settings profile
GET /Moonfin/Settings Yes The current user's full settings envelope
POST /Moonfin/Settings Yes Save the current user's settings (merge or replace via body)
DELETE /Moonfin/Settings Yes Delete all of the current user's settings
HEAD /Moonfin/Settings Yes Check whether the current user has stored settings (200 or 404)
GET /Moonfin/Settings/Stream Yes Server-sent events stream of settings changes, theme changes, and admin messages. On Emby it answers { "supported": false, "transport": "websocket" } and the same events ride the session websocket instead. Moonbase for Plex holds the stream open too, and only sends calendar reminders on it
GET, POST /Moonfin/Settings/{userId} Admin Get or save another user's settings
GET /Moonfin/Settings/Resolved/{profile} Yes The resolved flat values for a profile (global, desktop, mobile, tv)
POST, DELETE /Moonfin/Settings/Profile/{profile} Yes Save or delete one device profile's overrides (global can't be deleted)
GET, POST /Moonfin/Settings/detailsScreenBlur and /detailsScreenBlur/{profile} Yes Get or save the details-screen backdrop blur
GET, POST /Moonfin/Settings/detailsScreenOpacity and /detailsScreenOpacity/{profile} Yes Get or save the details-screen backdrop opacity

See Settings Sync for the envelope shape and the full list of synced settings.

Sign-in PINs

A user's sign-in PIN, stored on the server so it follows them to every device. A PIN is four digits. Only a salted PBKDF2-SHA256 hash is kept, and the PIN is never sent back. Moonbase for Plex serves these routes too. Moonbase for Silo doesn't, since Silo keeps PINs on its profiles, so they answer 404 there and the apps use Silo's own.

Method Path Auth Description
GET /Moonfin/Pin Yes Whether the calling user has a PIN
PUT /Moonfin/Pin Yes Set the calling user's PIN. Body { "pin", "currentPin" }, where currentPin is required when a PIN already exists, and a wrong one is a 403 that counts as a guess
DELETE /Moonfin/Pin Yes Remove the calling user's PIN. Takes the same body, with currentPin
POST /Moonfin/Pin/Verify Yes Check a PIN. Body { "pin" }. Answers 404 when the user has no PIN

The first five wrong guesses are free. After that each one makes the user wait 30 seconds longer, up to 15 minutes, and while they wait every guess, even the right one, gets a 429 with Retry-After and retryAfterSeconds. The count survives a restart, and a correct PIN or an admin setting a new one clears it.

Admin

Method Path Auth Description
POST /Moonfin/Admin/ApplyDefaults Admin Push admin defaults onto users' stored settings. Body { "overwrite", "userIds", "profiles", "keys" }. userIds null means everyone, profiles empty means all four, keys null means every set default, otherwise only those settings by JSON name. overwrite resets each user to the defaults alone and can't take keys. Returns usersAffected and orphansDeleted
POST /Moonfin/Admin/CopySettings Admin Copy one user's saved profiles onto others. Body { "fromUserId", "toUserIds", "profiles", "keys", "replace" }. replace makes each profile an exact copy and can't take keys. API keys and hidden items are never copied. Answers 404 when the source has no settings
POST /Moonfin/Admin/PushDefaults Admin Older route. Applies every profile's defaults to all users (overwrite=true resets everyone, otherwise merges)
POST /Moonfin/Admin/PushDefaults/{userId} Admin Older route. Applies every profile's defaults to a single user
POST /Moonfin/Broadcast Admin Send an announcement to all users, over the SSE stream for connected clients and as a push to registered devices
GET /Moonfin/Libraries/CheckWriteAccess Admin Test whether the server can write metadata to each library, and report failing paths
GET /Moonfin/Admin/Pins Admin Every user with whether they have a PIN, administrators first, for the Accounts tab
PUT, DELETE /Moonfin/Users/{userId}/Pin Admin Set or remove one user's PIN. Setting a PIN replaces the old one without asking for it and clears the wrong-guess count, which is how a forgotten PIN is reset
GET /Moonfin/Admin/ConfigBackup Admin Whether the plugin settings look like they were reset while a backup with real settings survives: { "looksReset": bool, "backupPath": "..." }. The config page uses it to offer a restore
POST /Moonfin/Admin/RestoreConfig Admin Copy the backup over the live plugin settings. Returns 404 when there's no backup. Restart the server afterwards, since it only reads the file at startup

Server messages

Admin messages are a stored, targeted successor to the one-shot broadcast: a message is saved on the server, filtered to the audience it was meant for, and delivered to connected clients over the SSE stream (as a messagesChanged event) and as a push to registered devices. Visibility is decided on the server, so a message meant for one user is never sent to another.

Method Path Auth Description
GET /Moonfin/Messages Yes The messages the calling user should see right now, already filtered to their audience and any start or end date. Returns 503 when settings sync is disabled
GET /Moonfin/Admin/Messages Admin Every stored message, including scheduled and expired ones, for the admin panel
POST /Moonfin/Admin/Messages Admin Create a message, or replace one in place when the ID already exists. Emits a messagesChanged event and, for a new interrupting message, a push
POST /Moonfin/Admin/Messages/Order Admin Reorder the stored messages to match the given list of IDs. The app shows them in this order, so this is what the up and down arrows drive
DELETE /Moonfin/Admin/Messages/{messageId} Admin Delete one message by ID

A message carries a title, a Markdown body, a colour, a delivery mode (an unread badge on the menu button, or the window opening on its own), an audience (everyone, a set of users, or admins only), optional start and end dates, and an optional link button. Interrupting messages push to devices that don't have the app open, while inbox messages wait for the user to open it. The old /Moonfin/Broadcast still works for a quick one-off announcement that isn't stored.

Media bar, genres, and themes

Method Path Auth Description
GET /Moonfin/MediaBar Yes The resolved media bar items for the current user (profile query, default global)
GET /Moonfin/Genres Yes All genre names and ids across the user's movie and series libraries. Moonbase for Silo and Plex give each genre the id the app itself uses on that server, and answer 502 on a Silo build that can't list library items
GET /Moonfin/Themes Yes List uploaded custom themes
GET /Moonfin/Themes/{themeId} Yes Get one custom theme by id
GET /Moonfin/Admin/Themes Admin Theme metadata index for the admin panel
POST /Moonfin/Admin/Themes Admin Upload or replace a theme payload
DELETE /Moonfin/Admin/Themes/{themeId} Admin Delete a theme

See Themes for the editor and upload flow.

Recommendations

Jellyfin, Emby and Moonbase for Plex. Emby and Plex can't use Moonfin's scoring for their own similar items, so only Moonfin apps get it there, through this route. On Plex the items come back as Plex's own metadata.

Method Path Auth Description
GET /Moonfin/Items/{id}/Similar Yes Similar items for a movie or series, scored by Moonfin on the server. Optional limit (default 20, capped at 200), excludeItemIds, and userId (defaults to the caller). Results are filtered to the libraries the user can see and come back as { "Items": [...], "TotalRecordCount": n }, in the same card shape as the media bar with the user's UserData. Returns 404 for an unknown item, and an empty list when Use Moonfin Recommends for similar items is off

This endpoint works on every supported Jellyfin version. On Jellyfin 12 Moonbase also plugs into Jellyfin's own similar items system, so the stock /Items/{id}/Similar returns Moonfin's scoring to every client, with Jellyfin's own providers only filling empty slots. Add bypass=moonfin (or engine=jellyfin) to that stock request to get Jellyfin's own results for one call.

Seasonal row

Method Path Auth Description
GET /Moonfin/Seasonal/Row Yes The seasonal Home row for the calling user: holiday movies they own, plus Seerr suggestions. Optional country is the viewer's ISO alpha-2 code, ZZ for someone who picked Other, or empty to fall back to the server's. Optional allYear=true also answers outside the holiday windows, with one of the user's shown holidays picked for the day, whatever its country. Answers { "holiday", "country", "items", "suggestions" }, with holiday null when no holiday window is open and allYear isn't set. items are the same cards the browse endpoints return, and suggestions use the custom row item shape with a rating attached. On a failure it answers an empty row rather than an error

Anime markers

Filler, canon and recap markers come from the cache the Moonfin Anime Markers Sync task fills. Subbed and Dubbed are read from the audio tracks in the files.

Moonbase for Plex serves these routes too, with Plex ratingKeys as ids, and runs the sync every night at 4:00. It also has GET /Moonfin/AnimeMarkers/Search (admin), which finds a series by name for the Preview tool, since its admin page can't search Plex.

Method Path Auth Description
GET /Moonfin/AnimeMarkers/Series Yes Markers for every episode of one series (seriesId). Reads the cache only and never goes to the internet, so clients can call it on every episode list. Answers enabled: false when both marker features are off, and pending: true for a matched series that hasn't been fetched yet. Returns 400 for a missing or malformed id and 404 for an unknown series
GET /Moonfin/AnimeMarkers/Items Yes Subbed or Dubbed for up to 200 comma-separated item ids (ids). Series and folders are skipped, since their episodes and seasons come from the Series endpoint. Answers enabled: false when audio markers are off
GET /Moonfin/AnimeMarkers/Diagnostics Admin Match counts for the admin page: how many series matched a show, how many are waiting on a fetch, and which matched nothing
GET /Moonfin/AnimeMarkers/Preview Admin One series' episode list with its markers (seriesId), for the Preview a series tool
POST /Moonfin/AnimeMarkers/Refresh Admin Fetch one series right away instead of waiting for the nightly task (seriesId)
GET /Moonfin/AnimeMarkers/Log Admin The tail of the marker debug log (lines, default 200, up to 2000)
POST /Moonfin/AnimeMarkers/ClearLog Admin Empty the marker debug log
POST /Moonfin/AnimeMarkers/ClearCache Admin Drop every cached show so the next sync fetches from scratch

A Series response looks like this:

{
  "enabled": true,
  "matched": true,
  "slug": "naruto-shippuden",
  "title": "Naruto Shippuden",
  "pending": false,
  "recapKnown": true,
  "episodes": {
    "<episodeId>": { "kind": "Filler", "recap": false, "audio": "Subbed" }
  },
  "seasons": {
    "<seasonId>": { "audio": "Dubbed" }
  },
  "placement": "below"
}

kind is Filler, Mixed, MangaCanon, AnimeCanon, or null when there's no marker or that pill is turned off. audio is Subbed, Dubbed, or SubbedAndDubbed when dual audio is shown separately. placement is the admin's pill placement (below, beside, or thumbnail). The Items endpoint answers { "enabled": true, "items": { "<itemId>": { "audio": "Subbed" } }, "placement": "below" }.

On Jellyfin the episode and season keys are item ids without hyphens. On Emby they're Emby's numeric item ids, the Items endpoint keys each answer by the id you sent, and fields with no value are left out instead of being sent as null.

Live TV recordings

Incomplete recording detection and re-recording. The settings are server-wide and set on the admin page's Live TV tab.

Method Path Auth Description
GET /Moonfin/LiveTv/Recordings/Incomplete Live TV access The incomplete state of each recording listed in itemIds, or of every incomplete recording when none are listed. Recordings that aren't incomplete are left out
POST /Moonfin/LiveTv/Recordings/{itemId}/Dismiss Live TV management Keep as Complete. Clears the recording's incomplete state for everyone and removes its DVRIncompleteRecording tag
POST /Moonfin/LiveTv/CompleteShowing Live TV management Ask for a later showing of a program's episode or movie to be recorded. Repeating the call for the same one joins the same request, so it's safe to retry
GET /Moonfin/LiveTv/CompleteShowing/Options Live TV management For a program that's already airing (programId), whether a later showing can be searched for and the one found: name, description, start, channel and alreadyScheduled. Only channels the caller can see are searched
POST /Moonfin/LiveTv/CompleteShowing/Suggestion Live TV management Record the later showing that Options suggested. Answers 204, or 409 when that airing changed, was canceled or can't be seen
GET /Moonfin/LiveTv/Settings/IncompleteRecordings Live TV access The server-wide settings
POST /Moonfin/LiveTv/Settings/IncompleteRecordings Admin Change the server-wide settings

Smart search

Answers 404 while Enable smart search is off. See Smart Search.

Method Path Auth Description
GET /Moonfin/SmartSearch Yes Library items and Seerr titles matching a plain sentence (query), up to limit of each (20 by default). Answers Ready, Items in the same trimmed card shape as the other Moonfin browse endpoints, and Seerr in the shape of Seerr's own search results. Items follows the user's library access and parental limit, and Seerr is empty unless Seerr is turned on. Before the first index is built it answers Ready: false with empty lists and starts the build. debug=true adds each result's score and how the query was read

TMDB

These use the calling user's TMDB key, or the server-wide one, and answer 404 without either.

Method Path Auth Description
GET /Moonfin/Tmdb/Thumb Yes A 302 to the TMDB backdrop with the title on it for a title the server doesn't hold (type is movie or tv, plus tmdbId, or imdbId when there's no TMDB id). It prefers the backdrop in the server's metadata language, then TMDB's main one. scheme=http asks for a plain http image, for clients that can't load https. Paths are cached for 7 days
GET /Moonfin/Tmdb/Details Yes Overview, rating, genres, runTimeTicks, a textless backdrop, a logo and a YouTube trailer for a title the server doesn't hold (type, tmdbId or imdbId), in the server's metadata language. Shares Thumb's cache
GET /Moonfin/Tmdb/PersonLinks Yes A person's IMDb id, website, and Instagram, X, Facebook, TikTok and YouTube handles (tmdbId). Cached for 24 hours
GET /Moonfin/Tmdb/Reviews Yes TMDB user reviews for a title (tmdbId, type, language), up to 20, with the HTML turned into light Markdown. Asks for English when the requested language has none. Cached for 24 hours per title and language

Ratings (MDBList and TMDB)

Method Path Auth Description
GET /Moonfin/MdbList/Ratings Yes MDBList ratings for a title (type = movie or show, tmdbId), filtered to the user's chosen sources. An optional profile query (desktop, mobile, or tv) resolves the sources against that device profile, anything else falls back to global
GET /Moonfin/MdbList/KeyInfo Admin Validate an MDBList API key and return the account's plan and remaining quota (backs the Test Key button, optional key query tests an unsaved key, otherwise the server-wide key is used)
POST /Moonfin/MdbList/ClearCache Admin Drop every cached MDBList rating so lookups refetch from the API
GET /Moonfin/MdbList/Lists Yes The cached catalog of MDBList official lists
GET /Moonfin/MdbList/Lists/{slug}/Items Yes Cached items for one official list (optional mediatype). Moonbase for Plex serves both list routes too, fetching each list the first time it's asked for instead of on a schedule
GET /Moonfin/MdbList/WatchingNow Yes What MDBList's users are watching right now, for the Watching on MDBList row, with the user's own MDBList key or the server-wide one (optional profile). One answer is shared by everyone for a few minutes. Without a key it answers an error instead. Served on all four servers
GET /Moonfin/Tmdb/EpisodeRating Yes TMDB rating for one episode (tmdbId, season, episode)
GET /Moonfin/Tmdb/SeasonRatings Yes TMDB ratings for every episode in a season (tmdbId, season)
GET /Moonfin/Tmdb/NextEpisode Yes The next episode to air for a series (tmdbId), so clients can show an air date without Sonarr. Answers { "success", "airDate", "seasonNumber", "episodeNumber", "name" }, or success: false with an error when there's no TMDB key or nothing is scheduled. Uses the user's TMDB key or the server-wide one, and caches each series for 6 hours for all users
GET /Moonfin/Tmdb/ProductionCompanies Yes TMDB studios for a title (tmdbId, type), each flagged for logo availability
GET /Moonfin/Tmdb/StudioImage/{companyId} Yes A cached studio logo PNG (re-downloaded from TMDB if evicted)
GET /Moonfin/Tmdb/StudioLogos Yes Every cached studio and network that has a logo, for the Studios row. A studio item carries no TMDB id, so apps match it to an entry here by name. Jellyfin and Emby
GET /Moonfin/Assets/{fileName} No Bundled rating provider icons and the flags poster labels can use
GET /Moonfin/UserRatings/Mine Yes Every personal rating the calling user has set, in one response, so a client can sort a library by My Rating and filter it by liked or disliked. Neither server has a user rating sort of its own
POST, DELETE /Moonfin/UserRatings/{itemId} Yes Emby only. Set the calling user's rating for an item (rating out of ten, or likes for a thumb) or clear it. Emby's own rating route answers 200 but stores nothing, so Moonfin writes through here

Custom rows and collections

Method Path Auth Description
GET /Moonfin/CustomRows/Items Yes Resolve an external home row (source, type, params) into items, cached server-side for 24 hours. Items carry officialRating (the TMDB certification), overview, genres and runTimeTicks, and the response is filtered by the calling user's parental limit, where a title with no known rating is left out under a limit. A row fetched with the user's own TMDB or MDBList key is cached for that user only. An optional language (a BCP 47 tag like fr or pt-BR) fetches MDBList, TMDB and Letterboxd rows in that language and caches each language apart. IMDb rows stay in English. Items without an overview get the overview, genres and runtime from TMDB in the server's metadata language. On Jellyfin a resolve keeps running if the client disconnects, and a repeat request joins the one in progress
GET /Moonfin/Collections/{collectionId}/Order Yes The current user's saved item order for a collection
POST /Moonfin/Collections/{collectionId}/Order Yes Save the user's item order for a collection (empty array clears it)

Moonbase for Plex serves CustomRows/Items too. It doesn't filter outside titles by a managed Plex user's rating limits.

Audiobook bookmarks and notes

Jellyfin and Emby.

Method Path Auth Description
GET /Moonfin/Bookmarks Yes Every bookmark the calling user has, across items
GET /Moonfin/Bookmarks/{itemId} Yes One item's bookmarks and notes for the calling user
POST /Moonfin/Bookmarks/{itemId} Yes Save the item's bookmarks for the calling user
POST /Moonfin/Bookmarks/{itemId}/Notes Yes Save the item's notes for the calling user

Clients work from local storage first and reconcile with these, so nothing waits on the network to open a book.

Retro games

All game endpoints require the game library feature to be enabled. See Retro Games for the full guide.

A library backed by RomM answers the same endpoints. Its game ids look like romm-12 and its BIOS ids like romm-fw-3, and the ROM and BIOS streams come from RomM through the server. For a user who linked their RomM account, those calls use that user's own RomM key, and the saves of RomM's games go to their RomM account. Save states are keyed ejs-<core>-romm-<id> or lr-<core>-romm-<id>, and battery saves (kind=sram) use the bare game id.

Moonbase for Plex serves these routes too, for Plex libraries whose folders it reads itself. There, the artwork protocol, arcade compatibility, Core, Cores/Install and Romm/Account answer 404, and the apps fall back to the plain Thumb artwork.

The EmulatorJS player page takes an optional sram URL next to save. It loads that battery save when the game starts and skips it when save is given, since a save state already holds it. window.moonfinGetSram() returns the battery save as base64 for the app to upload on exit.

Method Path Auth Description
GET /Moonfin/Games/Libraries Yes Libraries treated as game (ROM) libraries
GET /Moonfin/Games/{libraryId}/Systems Yes Top-level system folders in a game library
GET /Moonfin/Games/{libraryId}/Games Yes Games in a library, optionally filtered by system
GET /Moonfin/Games/{libraryId}/Games/{gameId} Yes One game's full detail (ROM, core, BIOS)
GET /Moonfin/Games/{libraryId}/Thumb/{gameId} Yes Stream a game's box art or screenshot (type query), downloaded and cached on first request
GET, HEAD /Moonfin/Games/{libraryId}/Rom/{token} Yes Stream a ROM file, auto-extracting archives, with range support
GET, HEAD /Moonfin/Games/{libraryId}/Rom/{token}/{fileName} Yes The same stream with an explicit file name, which some cores need to identify the ROM
GET, HEAD /Moonfin/Games/{libraryId}/Bios/{token} Yes Stream a BIOS file a core needs
GET /Moonfin/Games/Cores/Status Yes Whether the self-hosted emulator cores are installed or downloading
POST /Moonfin/Games/Cores/Install Admin Download and install the cores bundle in the background
POST /Moonfin/Games/Cores/Upload Admin Install cores from an uploaded zip
GET /Moonfin/Games/Saves/{gameId} Yes Download a saved game blob for the current user (kind is state, sram or settings, default state)
PUT /Moonfin/Games/Saves/{gameId} Yes Upload or overwrite a saved game blob (max 32 MB)
GET /Moonfin/Games/Romm/Account Yes The current user's RomM link. While a link waits for approval, each call also checks with RomM. Returns {available, linked, username, pending, error}
POST /Moonfin/Games/Romm/Account Yes Start linking the current user's RomM account. pending holds userCode, verificationUrl, verificationUrlComplete, expiresInSeconds and intervalSeconds
DELETE /Moonfin/Games/Romm/Account Yes Unlink the current user's RomM account
GET /Moonfin/EmulatorJS/player.html No The in-browser player shell
GET /Moonfin/EmulatorJS/data/{**path} No Self-hosted EmulatorJS runtime files, with a CDN fallback when not installed
GET /Moonfin/Games/Debug Admin Diagnostics for game library detection
POST /Moonfin/Games/Romm/Test Admin Check a RomM address and token from the admin page before saving them. Body {"url", "token"}
PUT /Moonfin/Games/{libraryId}/Games/{gameId}/Core Yes Jellyfin and Emby. Pin one game to a specific core
PUT /Moonfin/Games/{libraryId}/Games/{gameId}/Backend Yes Pin one game to the native or EmulatorJS backend

Artwork

Jellyfin and Emby.

Method Path Auth Description
GET /Moonfin/Games/ArtworkCapabilities Yes Advertises the manifest-driven artwork protocol, so older clients keep the old path
GET /Moonfin/Games/{libraryId}/ArtworkManifest Yes The cataloged artwork state for one system, without scheduling any provider work. Returns 304 when nothing changed
POST /Moonfin/Games/{libraryId}/ArtworkPriority Yes Promotes already cataloged artwork into the order the client actually needs it
GET /Moonfin/Games/{libraryId}/Artwork/{gameId}/{role}/{revision} Yes One versioned artwork file, served locally
POST /Moonfin/Games/{libraryId}/Artwork/{gameId}/Refresh Admin Repair path that reopens and reschedules one artifact
POST /Moonfin/Games/{libraryId}/Artwork/Refresh Admin Library-wide repair, optionally for one system. Checks each system's game database for updates, then reopens artwork that never found an original. Answers 202 and runs in the background, or 409 when a refresh is already running

Manifest entries carry each image's width and height, and ArtworkCapabilities answers dimensions: true, so a client can shape a system's grid around its box art.

Arcade compatibility

Jellyfin and Emby.

Method Path Auth Description
GET /Moonfin/Games/ArcadeCompatibility/Status Admin Whether the FBNeo and MAME data files are installed, and what they cover
PUT /Moonfin/Games/ArcadeCompatibility/{core}/Dat Admin Replace one core's data file with an uploaded XML, up to 512 MB

Active downloads

Method Path Auth Description
GET /Moonfin/Transcodes/Active Admin Active transcode and download jobs with progress and session info (includePlayback adds HLS playback jobs)
DELETE /Moonfin/Transcodes/Active/{jobId} Admin Cancel an active job
GET /Moonfin/Transcodes/Mine Yes Jellyfin and Emby. The caller's own transcode jobs, scoped by the device id on its token, so a client can show progress and an ETA for a download it started

Client logs

Emby, and Moonbase for Silo and Plex. Jellyfin has POST /ClientLog/Document of its own and Moonfin uses that there. Emby has no equivalent anywhere in its API, so the plugin provides one, which is what makes the app's Send report to server button work on Emby. Silo and Plex have none either, so Moonbase for them takes reports the same way and writes each one to its own log, since neither server gives it a log folder.

Method Path Auth Description
POST /Moonfin/ClientLog/Document Yes Upload a diagnostic or crash report as text/plain. The report is written into the server's log folder as upload_<client>_<version>_<utc>.txt, matching the .txt Emby writes its own logs as, so it appears under Logs beside them. The response names the file: { "FileName": "..." }

Not admin-only on purpose: crash reports come from whichever user hit the bug. The guards are elsewhere. The body is capped at 1 MB (413 past it), each user gets 10 uploads an hour (429 past that), the file name is built entirely on the server from the authenticated session rather than anything the client sends, and the whole endpoint returns 503 when Enable client log upload is off in the plugin settings. Clients redact server addresses, IP addresses and access tokens before sending.

Seerr

Method Path Auth Description
GET /Moonfin/Seerr/Config Yes Seerr config for the client (see the response shape below)
POST /Moonfin/Seerr/Login Yes Sign in to Seerr with Jellyfin/Emby or local credentials and store the session
GET /Moonfin/Seerr/Status Yes Whether Seerr is enabled and the current user's session status
GET /Moonfin/Seerr/Validate Yes Confirm the stored session is still valid
DELETE /Moonfin/Seerr/Logout Yes Log out and clear the stored session
GET, POST, PUT, DELETE /Moonfin/Seerr/Api/{**path} Session Authenticated proxy to the Seerr API using the stored session. For a user with a parental limit, titles above it and unrated ones are left out of discover, search, recommendations, credits and collection results, and opening, requesting or watchlisting one answers 403. The rating comes from TMDB, so without a TMDB key such a user gets nothing

Moonbase for Plex serves these routes too. Its Login takes {"authType": "plex", "authToken": "<plex.tv token>"} or local credentials, and refuses a Plex sign-in whose Seerr account belongs to someone else. It has no parental filter and no webhook.

See Reverse Proxy and Seerr for proxy and single sign-on notes.

Seerr config response

{
  "enabled": true,
  "url": "https://seerr.example.com",
  "displayName": "Seerr",
  "variant": "seerr",
  "userEnabled": true
}
Field Type Description
enabled bool Whether Seerr is enabled by the admin
url string The Seerr server URL used for proxying
displayName string The name shown in the client (admin override, defaults to Seerr)
variant string Always seerr on current versions
userEnabled bool Whether the user has Seerr enabled in their own settings

Calendar

How the feature works for admins is on Calendar. Moonbase for Plex answers the two calendar routes from the Sonarr and Radarr set on its admin page, and serves the reminder routes too. It checks Plex for arrivals every 2 minutes while anyone has a reminder.

Method Path Auth Description
GET /Moonfin/Seerr/Radarr/Calendar Yes The Radarr calendar from start to end (yyyy-MM-dd), trimmed to the Days back and Days ahead window, with optional unmonitored=true. The server reads the Radarr connection from Seerr through a stored Seerr admin session and calls Radarr itself, so the API key stays on the server and remote clients still see it. Each movie in one of the caller's libraries gains moonfinItemId and moonfinLibraryIds. One answer from Radarr is reused for Cache minutes. Returns 403 when Who can see the calendar is Admins only and the caller isn't an admin, 503 when Seerr is off or no Seerr admin has signed in through Moonfin, and 404 when Seerr has no Radarr server. It uses Seerr's regular (non-4K) default Radarr, and when Seerr's internal address for it can't be reached it tries the External URL set in Seerr, but only over https or when that address is on the local network
GET /Moonfin/Seerr/Sonarr/Calendar Yes The same for Sonarr, with each episode's series and images included. An episode whose series is in the caller's libraries gains moonfinItemId (the series) and moonfinLibraryIds, plus moonfinEpisodeId when the episode itself is on the server. Clients also use it for a series' next air date and fall back to TMDB
GET /Moonfin/Calendar/Reminders Yes The caller's reminders as {"reminders": [...]}. Each has a key, kind (episode or movie), tvdbId, tmdbId, imdbId, seasonNumber, episodeNumber, title, subtitle and createdAt
POST /Moonfin/Calendar/Reminders Yes Saves a reminder from those fields and returns it with its key (episode:<tvdb>:<season>:<episode>, movie:<tmdb> or movie:imdb:<id>). Saving the same title again keeps the first createdAt. Returns 400 when the fields can't make a key or the caller already has 500
DELETE /Moonfin/Calendar/Reminders/{key} Yes Removes one reminder. A reminder is also removed when it fires, which happens as a seerrNotification on the live stream plus a push when its title lands in the library

The Ping answer carries calendarAvailable (false when Seerr is off, or the caller is turned away by Admins only), calendarDaysBack, calendarDaysAhead and calendarReminders.

Download queue

Why a Radarr or Sonarr download for a request is stuck, and the admin action behind Get a different release. On Jellyfin and Emby these read the same Radarr and Sonarr as the calendar, through a stored Seerr admin session, and answer 503 while Seerr is off. Moonbase for Plex reads the Sonarr and Radarr set on its admin page, and answers 404 for one that isn't set. See Seerr Notifications.

Method Path Auth Description
GET /Moonfin/Seerr/Radarr/Queue Yes A movie's Radarr queue, looked up by tmdbId. Answers service, inArr, canRemove (whether the caller is an admin), monitored, hasFile, isAvailable and items, each with Radarr's own status, trackedDownloadState, trackedDownloadStatus, size, time left, errorMessage and statusMessages. An empty items with inArr true means Radarr has the movie and nothing is downloading. Server paths in the messages are cut down for anyone but an admin, and only admins get outputPath. Returns 400 without a positive id
GET /Moonfin/Seerr/Sonarr/Queue Yes The same for a series in Sonarr, looked up by tvdbId, with each entry's episodeId, seasonNumber and episodeNumber
DELETE /Moonfin/Seerr/Radarr/Queue/{id} Admin Remove a queue entry from the download client, blocklist its release and let Radarr search again. Answers {"removed": [...]}, or 404 when it's no longer in the queue
DELETE /Moonfin/Seerr/Sonarr/Queue/{id} Admin The same in Sonarr. Every entry that shares the download, such as the episodes of a season pack, goes with it

Watch Parties

How the feature works for admins is on Watch Parties. Moonbase for Plex serves the same routes from the companion, with the Plex server token as X-Plex-Token. The Silo build doesn't serve them.

  • Room calls also send X-Moonfin-Room-Token, the token from the create or join answer. It belongs to one user on one device, and a token that isn't the caller's answers 403.
  • JSON keys are snake_case, positions are seconds as doubles, and times are ISO 8601 UTC with milliseconds. The room and message shapes follow Silo's watch-together protocol, with a few additions such as member status, anchor_at, has_pin, invite_url and the chat, reaction and notice messages.
  • Errors answer {"code", "message"}. The codes are disabled, pin_required, wrong_pin, no_access, not_host and not_allowed (403), not_found (404), full (409), too_many_attempts (429, with retry_after_seconds and a Retry-After header), rate_limited (429) and bad_request (400).
  • Every route but /Config answers 403 disabled while Turn on Watch Parties is off, so a 404 on /Config means a Moonbase without Watch Parties.
Method Path Auth Description
GET /Moonfin/WatchParty/Config Yes {"enabled", "chat", "reactions", "alerts", "everyone_control_allowed", "max_members", "version"}, answered whether Watch Parties are on or off
GET /Moonfin/WatchParty/Time Yes {"server_time"}, the clock players line up against
GET /Moonfin/WatchParty/Rooms Yes {"rooms": [...]}, the open rooms the caller can watch. A summary has no code, so a listed room is joined by room_id. Rooms whose item the caller can't play are left out, and lobbies with nothing picked are listed
POST /Moonfin/WatchParty/Rooms Yes Create a room. Body {"content_id"?, "pin"?, "guest_control_policy"?}. Answers {"room", "token"}. It closes any other room the caller hosts
POST /Moonfin/WatchParty/Join Yes Join by code (case doesn't matter, spaces and dashes are ignored) or room_id, plus pin when the room has one. Answers {"room", "token"}
POST /Moonfin/WatchParty/Rooms/{id}/Stage Host Pick the item (content_id) and end voting
POST /Moonfin/WatchParty/Rooms/{id}/Start Host Start from position_seconds (default 0). Everyone loads the item, and once every connected member is ready, or after 10 seconds, a 3 second countdown starts playback
POST /Moonfin/WatchParty/Rooms/{id}/Stop Host Back to the lobby with the same item picked. A run also returns to the lobby on its own when the item ends
POST /Moonfin/WatchParty/Rooms/{id}/Policy Host guest_control_policy: host_only, guests_can_pause or everyone. everyone answers not_allowed when the admin turned it off
POST /Moonfin/WatchParty/Rooms/{id}/Pin Host Set a room PIN of 4 to 8 digits, or null to remove it
POST /Moonfin/WatchParty/Rooms/{id}/Voting Host Turn the vote on what to watch on or off (voting)
GET /Moonfin/WatchParty/Rooms/{id}/Suggestions Member The titles up for a vote, each {"id", "content_id", "title", "subtitle", "vote_count", "voted_by_me", "created_at"}
POST /Moonfin/WatchParty/Rooms/{id}/Suggestions Host Add a title to the vote (content_id). Answers 204
POST /Moonfin/WatchParty/Rooms/{id}/Suggestions/{sid}/Vote Member Vote, or take the vote back, with voted. Answers 204
DELETE /Moonfin/WatchParty/Rooms/{id}/Suggestions/{sid} Host Take a title out of the vote. Answers 204
POST /Moonfin/WatchParty/Rooms/{id}/Promote Host Hand the room to another member (member_id)
POST /Moonfin/WatchParty/Rooms/{id}/Remove Host Take a member out (member_id). Answers 204
POST /Moonfin/WatchParty/Rooms/{id}/Close Host End the room for everyone. Answers 204
POST /Moonfin/WatchParty/Rooms/{id}/Leave Member Leave. A leaving host hands the room to the earliest member who joined. Answers 204
GET /Moonfin/WatchParty/Rooms/{id}/Events Member The long poll. cursor is the last cursor seen and wait the seconds to hold, capped at 25. Answers {"cursor", "messages", "reset"?} as soon as there's something newer. The first call (no cursor, or 0) answers at once with a snapshot and the last 50 chat messages. A cursor older than the last 200 messages gets reset: true and a fresh snapshot
POST /Moonfin/WatchParty/Rooms/{id}/Messages Member Send {"messages": [...]}: ping, transport_request, state_report, buffering, attach_session, chat or reaction. Answers {"messages": [...]} with any replies, such as the pong

In the Auth column, Member needs the caller's room token, and Host also needs the caller to be the room's host, otherwise it answers not_host. Under everyone, guests may also play, pause and seek through transport_request. Events sends snapshot (rendered for each member), transport_command (with execute_at on the server clock and the name of whoever asked), suggestions_update, chat, chat_history, reaction (love, haha, wow, sad, fire or popcorn), notice (joined, left, host_changed, policy_changed, switched or no_access), error, removed and room_closed. A member is shown as away after 35 seconds without an Events call, and removed after 120. Chat is capped at 500 characters and one message a second with bursts of 5, reactions at 4 a second, and transport requests at 10 a second.

The admin routes, used by the Live rooms list:

Method Path Auth Description
GET /Moonfin/Admin/WatchParty/Rooms Admin {"rooms": [...]}, every open room with room_id, code, host_name, phase, playback_state, position_seconds, content_title, content_label, has_pin, created_at and its members with their user ids, connected and status
POST /Moonfin/Admin/WatchParty/Rooms/{id}/Close Admin Close a room for everyone
POST /Moonfin/Admin/WatchParty/Rooms/{id}/Remove Admin Take a member out. Body {"member_id"}

The invite page:

Method Path Auth Description
GET /Moonfin/Party/{code} No The invite page, as HTML with Open Graph tags for link previews. It never looks the code up, so it shows no title, host or members, only the server's name and address, the code, and an Open in Moonfin button that opens moonfin://party?server=<server id>&code=<code>&address=<public address>. Anything that can't be a room code answers 404
GET /Moonfin/Party/preview.png No The 1200x630 link preview image

Moonbase for Silo keeps what Silo's own Watch Party rooms don't, by Silo room id:

Method Path Auth Description
GET /Moonfin/WatchParty/{roomId} Yes The room's subtitle choice and its members' avatars, or {} for a room it doesn't know. Rooms are forgotten after 24 hours
PUT /Moonfin/WatchParty/{roomId} Yes Set the room's subtitle choice, {"subtitle": {...}} for a track, {"subtitle": {"off": true}} for none, or {"subtitle": null} to leave it to each member. The first profile to set it owns the room, and anyone else gets 403
PUT /Moonfin/WatchParty/{roomId}/Avatar Yes Set the caller's own avatar link for the room, {"url"}, an http(s) link or a path on the server
DELETE /Moonfin/WatchParty/{roomId} Yes Forget the room when its party ends, so it stops counting toward the account's 10 stored rooms. The profile that set its subtitles can, or the account that stored it when nobody did. Answers 204

SyncPlay relay for Roku

Jellyfin only. Roku can't open Jellyfin's websocket on a server reached over https, so Moonbase holds the socket for it and passes SyncPlay messages on through a long poll. The relay is always the caller's own user, device and token. It follows SyncPlay relay for Roku on the Watch Parties tab, and the Ping answer's socketRelay says whether it's on.

Method Path Auth Description
POST /Moonfin/SocketRelay/Open Yes Open the caller's relay, or keep using the one already open. Answers active, created, state and cursor. 429 past 10 relays for one user or 200 in all, and 503 while the relay is off
GET /Moonfin/SocketRelay/Poll Yes The long poll. cursor is the last cursor seen and waitMs how long to hold, up to 25000. Answers cursor, messages, state and reset. 404 when no relay is open, and 410 when it's gone or the relay is off, so the app goes back to Open
POST /Moonfin/SocketRelay/Close Yes Close the caller's relay. Answers 204

After Hours scoreboards

The top 3 for each of the apps' hidden After Hours games, shared by everyone on the server. Each user keeps one row per game, their best, and a tie goes to whoever got there first. Served on all four servers. The boards are stored in minigames/scores.json, see Data Locations.

Method Path Auth Description
GET /Moonfin/MiniGames/{gameId}/Scores Yes The board, best first, each row {"userName", "score", "at"}. gameId is buffer-run, fast-forward, cable-crawl, case-stack or pixel-purge, and anything else answers 404
POST /Moonfin/MiniGames/{gameId}/Scores Yes Record a finished run, {"score"}, a whole number from 0 to 9,999,999. Answers {"rank"}, its place on the board, or null when it isn't the user's new best or misses the top 3. An API key without a user gets 401

Poster labels

The labels the admin sets up on the Labels tab, which the apps draw over posters. Jellyfin, Emby and Moonbase for Plex. Labels are read from each file's own tracks, so a viewer only gets them for items they can see.

Method Path Auth Description
GET /Moonfin/PosterBadges/Items Yes The labels for up to 200 comma-separated item ids (ids), for the caller's device (profile is tv, desktop or mobile). Answers enabled, version, maxPerCorner and items keyed by the id sent, leaving out items with no label. enabled: false means this viewer gets no labels at all. Without ids it only answers whether they get any
POST /Moonfin/PosterBadges/Test Admin Try the rules the admin page holds, saved or not, on one movie or episode. Body {"itemId", "rules", "defaults"}. Answers which labels show, why the others don't, and the track values the conditions read
GET /Moonfin/PosterBadges/Presets Admin The built-in labels, each with id, label, group, color and description
GET /Moonfin/PosterBadges/Search Admin Moonbase for Plex only. Finds a movie or episode by name (term) for the Test a file tool, since its admin page can't search Plex

Notifications (Seerr and new media)

Method Path Auth Description
GET, POST /Moonfin/Notifications/Prefs Yes Get or save the caller's notification preferences, including the new media opt-in (notifyOnNewMedia)
POST, DELETE /Moonfin/Notifications/Register Yes Register or remove a push token for the caller's device
GET /Moonfin/Notifications/WebhookInfo Admin The Seerr webhook URL, secret, supported types, and provisioning status
POST /Moonfin/Notifications/Reprovision Admin Re-register Moonfin's webhook in Seerr immediately
POST /Moonfin/Seerr/Webhook No (shared secret) Inbound Seerr webhook, authenticated by the secret query or X-Moonfin-Webhook-Secret header

Moonbase for Plex only serves Notifications/Register, for calendar reminder pushes.

Web hosting and discovery

Method Path Auth Description
GET /Moonfin/Web/{**path} No Serve the Moonfin web app static files with SPA fallback
GET /Moonfin/Web/config.json No Runtime config for web plugin mode
GET /Moonfin/Web/loader.js No The loader bridge script for the stock Jellyfin header button
GET /Moonfin/Discovery and /Moonfin/Discovery/discover No Same-origin server metadata for web discovery

Clone this wiki locally