All API routes are mounted under /api unless noted otherwise.
- Auth is cookie-based (HTTP-only JWT cookie). Authorization header is also accepted for backward compatibility.
- When password login is enabled, unauthenticated users can only access login-related public endpoints.
- Visitor role is read-only for most resources.
- API key auth is supported via
X-API-KeyorAuthorization: ApiKey <key>whenapiKeyEnabledis true. - API key auth allows
POST /api/downloadplus a minimal read-only library surface:GET /api/videosGET /api/videos/:idGET /api/mount-video/:idGET /api/collectionsGET /api/system/version- Other API-key-authenticated endpoints return
403.
GET /api/search- Search videos online (YouTube)- Query params:
query(required),limit(optional, default:8),offset(optional, default:1)
- Query params:
POST /api/download- Queue a video download- Body:
{ youtubeUrl: string, downloadAllParts?: boolean, collectionName?: string, downloadCollection?: boolean, collectionInfo?: object, forceDownload?: boolean } - Auth: accepts session cookie/Bearer JWT, or API key (
X-API-Key/Authorization: ApiKey <key>) - Supports: YouTube, Bilibili, Twitch VODs, MissAV and other yt-dlp supported sites
- Body:
GET /api/check-video-download- Check whether a source URL was downloaded before- Query params:
url(required)
- Query params:
GET /api/check-bilibili-parts- Check whether a Bilibili video has multiple parts- Query params:
url(required)
- Query params:
GET /api/check-bilibili-collection- Check whether a Bilibili URL is a collection/series- Query params:
url(required)
- Query params:
GET /api/check-playlist- Check whether a URL is a playlist (YouTube/Bilibili supported)- Query params:
url(required)
- Query params:
GET /api/download-status- Get active and queued downloads
POST /api/upload- Upload one local video file- Multipart form-data:
video(required),title(optional),author(optional) - Notes:
- Content is validated as a supported video before it is accepted.
- Duplicate uploads are skipped by content hash.
- Multipart form-data:
POST /api/upload/batch- Upload multiple local video files in one request- Multipart form-data:
videos(one or more required),title(optional, only used when exactly one file is uploaded),author(optional) - Response data shape:
{ results: Array<{ originalName, status, message, video? }>, summary: { total, uploaded, duplicates, failed } } - Notes:
- Intended for multi-file selection and folder uploads.
- Folder uploads import supported video files only; subdirectory structure is not preserved.
- Duplicate uploads are skipped by content hash.
- Multipart form-data:
GET /api/videos- Get all videos (no server-side pagination/filtering in current implementation)- Auth: accepts session cookie/Bearer JWT, or API key (
X-API-Key/Authorization: ApiKey <key>)
- Auth: accepts session cookie/Bearer JWT, or API key (
GET /api/videos/:id- Get one video by ID- Auth: accepts session cookie/Bearer JWT, or API key (
X-API-Key/Authorization: ApiKey <key>)
- Auth: accepts session cookie/Bearer JWT, or API key (
GET /api/mount-video/:id- Stream a mount-directory video by video ID (supports Range)- Auth: accepts session cookie/Bearer JWT, or API key (
X-API-Key/Authorization: ApiKey <key>)
- Auth: accepts session cookie/Bearer JWT, or API key (
PUT /api/videos/:id- Update video metadata- Body allows:
{ title?, tags?, visibility?, subtitles? }
- Body allows:
POST /api/videos/:id/subtitles- Upload subtitle file for a video- Multipart form-data:
subtitle(required),language(optional) - Supported upload formats:
.vtt,.srt,.ass,.ssa
- Multipart form-data:
DELETE /api/videos/:id- Delete video record and related filesGET /api/videos/:id/comments- Get comments for the video (if available)POST /api/videos/:id/rate- Rate a video- Body:
{ rating: number }where1 <= rating <= 5
- Body:
POST /api/videos/:id/refresh-thumbnail- Refresh thumbnail- Uses a random local video frame when the video file is available locally
- Falls back to re-downloading the original remote thumbnail when the local video file cannot be resolved
POST /api/videos/:id/redownload-thumbnail- Re-download the original remote thumbnail from the source URLPOST /api/videos/:id/view- Increment view countPUT /api/videos/:id/progress- Save playback progress- Body:
{ progress: number }
- Body:
GET /api/videos/author-channel-url- Resolve channel/author URL from source URL- Query params:
sourceUrl(required) - Supports: YouTube, Bilibili and Twitch source URLs
- Query params:
POST /api/downloads/channel-playlists- One-time process: download all playlists from a channel- Body:
{ url: string }
- Body:
POST /api/downloads/cancel/:id- Cancel active downloadDELETE /api/downloads/queue/:id- Remove queued downloadDELETE /api/downloads/queue- Clear download queueGET /api/downloads/history- Get download historyDELETE /api/downloads/history/:id- Delete one history itemDELETE /api/downloads/history- Clear download history
GET /api/collections- Get all collections- Auth: accepts session cookie/Bearer JWT, or API key (
X-API-Key/Authorization: ApiKey <key>)
- Auth: accepts session cookie/Bearer JWT, or API key (
POST /api/collections- Create collection- Body:
{ name: string, videoId?: string }
- Body:
PUT /api/collections/:id- Update collection- Body:
{ name?: string, videoId?: string, action?: "add" | "remove" }
- Body:
DELETE /api/collections/:id- Delete collection- Query params:
deleteVideos=true(optional, also delete videos in the collection)
- Query params:
GET /api/subscriptions- Get all subscriptionsPOST /api/subscriptions- Create subscription- Body:
{ url: string, interval: number, authorName?: string, downloadAllPrevious?: boolean, downloadShorts?: boolean, downloadOrder?: 'dateDesc' | 'dateAsc' | 'viewsDesc' | 'viewsAsc' } downloadOrderis only applied whendownloadAllPreviousistrue; defaults todateDesc- Accepts: YouTube channel URLs, Bilibili space URLs, and Twitch channel URLs
- Twitch notes:
downloadShortsis ignored and persisted as disabled- new Twitch subscriptions poll for published VODs (
archiveandupload), not live streams twitchClientIdandtwitchClientSecretare optional; when they are missing, the backend falls back to yt-dlp polling in best-effort mode- adding Twitch app credentials improves channel resolution and polling reliability
- Body:
PUT /api/subscriptions/:id- Update subscription- Body:
{ interval?: number, retentionDays?: number | null } - At least one field is required; when both are provided, they are updated in one database operation
intervalmust be a positive integer in minutesretentionDaysmust be a positive integer; usenullor an empty string to disable auto-delete- Auto-delete only removes expired videos downloaded by that subscription when no other successful download history references the same local video
- Body:
PUT /api/subscriptions/:id/pause- Pause subscriptionPUT /api/subscriptions/:id/resume- Resume subscriptionDELETE /api/subscriptions/:id- Delete subscriptionPOST /api/subscriptions/playlist- Create playlist subscription- Body:
{ playlistUrl: string, interval: number, collectionName: string, downloadAll?: boolean, collectionInfo?: object }
- Body:
POST /api/subscriptions/channel-playlists- Subscribe all playlists from a channel and create watcher- Body:
{ url: string, interval: number, downloadAllPrevious?: boolean }
- Body:
GET /api/subscriptions/tasks- Get all continuous download tasksPOST /api/subscriptions/tasks/playlist- Create one playlist continuous task- Body:
{ playlistUrl: string, collectionName: string }
- Body:
PUT /api/subscriptions/tasks/:id/pause- Pause taskPUT /api/subscriptions/tasks/:id/resume- Resume taskDELETE /api/subscriptions/tasks/:id- Cancel taskDELETE /api/subscriptions/tasks/:id/delete- Delete task recordDELETE /api/subscriptions/tasks/clear-finished- Clear finished tasks
- RSS management endpoints require an admin session. API key authentication is rejected for these endpoints.
- Management responses return
Cache-Control: no-storebecause token IDs andfeedUrlvalues are bearer secrets. GET /api/rss/tokens- List RSS feed tokens- Response includes
feedUrlfor each token
- Response includes
POST /api/rss/tokens- Create an RSS feed token- Body:
{ label: string, role: "admin" | "visitor", filters?: RssFilters }
- Body:
PUT /api/rss/tokens/:id- Update token label, filters, or active state- Body:
{ label?: string, filters?: RssFilters, isActive?: boolean }
- Body:
DELETE /api/rss/tokens/:id- Delete an RSS feed tokenPOST /api/rss/tokens/:id/reset- Rotate the feed URL while preserving label, role, filters, and active stateRssFilters:{ authors?: string[], channelUrls?: string[], tags?: string[], sources?: string[], dayRange?: number, maxItems?: number }- Supported
sources:youtube,bilibili,twitch,local,missav,cloud - Empty or missing
sourcesmeans all sources, including future or unknown source values maxItemsaccepts1through200; the settings UI exposes a friendlier10through200slider
- Supported
GET /api/settings- Get app settings (password hashes are excluded)- Response may include service configuration for admin users, or for any client when login protection is disabled
- When login protection is enabled, visitor and unauthenticated responses hide service secrets such as
apiKey,openListToken,cloudflaredToken,telegramBotToken,twitchClientId, andtwitchClientSecret
PATCH /api/settings- Partially update settings- Body: partial settings object
- Supports
apiKeyEnabled?: booleanandapiKey?: string - Supports
twitchClientId?: stringandtwitchClientSecret?: string - If
apiKeyEnabledis set totrueandapiKeyis empty/missing, server auto-generates a 64-character hex key
POST /api/settings/migrate- Migrate legacy JSON data to SQLitePOST /api/settings/delete-legacy- Delete legacy JSON data filesPOST /api/settings/format-filenames- Format legacy filenamesGET /api/settings/cloudflared/status- Get Cloudflared tunnel statusPOST /api/settings/tags/rename- Rename tag- Body:
{ oldTag: string, newTag: string }
- Body:
POST /api/settings/telegram/test- Send a test Telegram notification- Body:
{ botToken: string, chatId: string }
- Body:
GET /api/settings/password-enabled- Check whether login/password is enabledGET /api/settings/reset-password-cooldown- Removed- This endpoint was tied to the deprecated public web reset flow and is no longer available.
POST /api/settings/verify-password- Verify password (deprecated, kept for compatibility)- Body:
{ password: string }
- Body:
POST /api/settings/verify-admin-password- Verify admin password- Body:
{ password: string }
- Body:
POST /api/settings/verify-visitor-password- Verify visitor password- Body:
{ password: string }
- Body:
POST /api/settings/reset-password- Removed- Password recovery must be performed from the backend environment via
node dist/scripts/reset-password.js <new-password>or the equivalent Docker command.
- Password recovery must be performed from the backend environment via
POST /api/settings/logout- Clear auth cookie
GET /api/settings/passkeys- Get passkey list (safe fields only)GET /api/settings/passkeys/exists- Check whether passkeys existPOST /api/settings/passkeys/register- Generate passkey registration options- Body:
{ userName?: string }
- Body:
POST /api/settings/passkeys/register/verify- Verify passkey registration- Body:
{ body: object, challenge: string }
- Body:
POST /api/settings/passkeys/authenticate- Generate passkey authentication optionsPOST /api/settings/passkeys/authenticate/verify- Verify passkey authentication and issue auth cookie- Body:
{ body: object, challenge: string }
- Body:
DELETE /api/settings/passkeys- Remove all passkeys
POST /api/settings/upload-cookies- Upload cookie file for yt-dlp- Multipart form-data:
file
- Multipart form-data:
POST /api/settings/delete-cookies- Delete cookie fileGET /api/settings/check-cookies- Check whether cookie file exists
GET /api/settings/hooks/status- Get hook installation statusPOST /api/settings/hooks/:name- Upload hook script- Multipart form-data:
file - Valid
:name:task_before_start,task_success,task_fail,task_cancel
- Multipart form-data:
DELETE /api/settings/hooks/:name- Delete hook script
GET /api/settings/export-database- Download current DB backup filePOST /api/settings/import-database- Import.dbbackup file and overwrite current DB- Multipart form-data:
file
- Multipart form-data:
POST /api/settings/merge-database-preview- Scan uploaded.dbbackup and return merge counts without modifying current DB- Multipart form-data:
file - Response
summaryincludesvideos,collections,collectionLinks,subscriptions,downloadHistory,videoDownloads, andtags; each item contains{ merged, skipped }
- Multipart form-data:
POST /api/settings/merge-database- Merge uploaded.dbbackup into current DB while keeping existing records- Multipart form-data:
file - Response includes the same merge
summaryshape as preview
- Multipart form-data:
GET /api/settings/last-backup-info- Get latest backup metadataPOST /api/settings/restore-from-last-backup- Restore from latest backupPOST /api/settings/cleanup-backup-databases- Cleanup backup DB files
POST /api/scan-files- Scan local uploads video directory and sync with DBPOST /api/scan-mount-directories- Scan configured mount directories and sync with DB- Body:
{ directories: string[] }(non-empty)
- Body:
POST /api/cleanup-temp-files- Remove temporary download files (.part,.ytdl,temp_*)
GET /api/cloud/signed-url- Get cloud signed URL (or cached thumbnail URL)- Query params:
filename(required),type(optional:videoorthumbnail)
- Query params:
POST /api/cloud/sync- Two-way sync local/cloud videos- Response is streamed JSON lines progress events
DELETE /api/cloud/thumbnail-cache- Clear local cloud thumbnail cacheGET /api/cloud/thumbnail-cache/:filename- Serve cached cloud thumbnail file (static route)
GET /api/system/version- Get version/update info- Returns:
{ currentVersion, latestVersion, releaseUrl, hasUpdate, ... } - Auth: accepts session cookie/Bearer JWT, or API key (
X-API-Key/Authorization: ApiKey <key>)
- Returns:
GET /feed/:token- Public RSS 2.0 feed endpoint; the path token is the bearer credential- No session cookie or API key is required
- Invalid or disabled tokens return RSS XML with a 404 status
GET /cloud/videos/:filename- Redirect to signed cloud video URLGET /cloud/images/:filename- Serve cached cloud image or redirect to signed image URLGET /videos/*- Static local videosGET /images/*- Static local thumbnails/imagesGET /subtitles/*- Static subtitle filesGET /avatars/*- Static avatar files