API Reference¶
Generated from the server's source by
cd server && npm run generate-api-docs; do not edit it by hand. Last updated: 2026-10-13
Authentication names who may call a route: None (anyone), Session (a signed-in Peek user), Admin (a signed-in admin), Session or signed link (a session, or the personal link the external player gets), or None until setup starts, then Admin (open to the setup wizard while Peek has no user and no Stash server).
Contents¶
- Server
- Auth
- Setup
- User
- User Groups
- Library
- Clips
- Timeline
- Playback
- Media Proxy
- Playlists
- Downloads
- Ratings
- Watch History
- Image View History
- User Stats
- Carousels
- Custom Themes
- Sync
- Exclusions
- Admin
Server¶
Health check, version, and server statistics.
GET /api/health¶
Health check (no auth). Proves Node answers through nginx and nothing more: no database query, per the homelab health-check convention.
Authentication: None
Handler: inline in server/initializers/api.ts
GET /api/version¶
Version endpoint (no auth required)
Authentication: None
Handler: inline in server/initializers/api.ts
GET /api/stats¶
Server stats endpoint (admin only - authenticated)
Authentication: Admin
Response:
interface GetStatsResponse {
system: StatsSystemInfo;
process: StatsProcessInfo;
cache: StatsCacheInfo;
database: StatsDatabaseInfo;
}
Handler: getStats in server/controllers/stats.ts
POST /api/stats/refresh-cache¶
Refresh cache endpoint (admin only)
Authentication: Admin
Response:
Handler: refreshCache in server/controllers/stats.ts
Auth¶
Sign in and out, the signed-in user, and password recovery with a recovery key.
POST /api/auth/login¶
Login endpoint
Authentication: None
Handler: inline in server/routes/auth.ts
POST /api/auth/logout¶
Logout endpoint
Authentication: None
Handler: inline in server/routes/auth.ts
GET /api/auth/me¶
Get current user
Authentication: Session
Handler: inline in server/routes/auth.ts
GET /api/auth/check¶
Check if authenticated
Authentication: Session
Handler: inline in server/routes/auth.ts
POST /api/auth/forgot-password/init¶
Forgot password - check username and get recovery method
Authentication: None
Handler: inline in server/routes/auth.ts
POST /api/auth/forgot-password/reset¶
Forgot password - verify recovery key and set new password
Authentication: None
Handler: inline in server/routes/auth.ts
Setup¶
The setup wizard, and the admin's management of Stash servers.
GET /api/setup/status¶
Authentication: None
Response:
interface GetSetupStatusResponse {
setupComplete: boolean;
hasUsers: boolean;
hasStashInstance: boolean;
stashInstanceCount: number;
}
Handler: getSetupStatus in server/controllers/setup.ts
POST /api/setup/create-admin¶
Authentication: None
Request Body:
Response:
interface CreateFirstAdminResponse {
success: true;
user: {
id: number;
username: string;
role: string;
createdAt: string;
};
}
Handler: createFirstAdmin in server/controllers/setup.ts
POST /api/setup/test-stash-connection¶
Authentication: None until setup starts, then Admin
Request Body:
Response:
interface TestStashConnectionResponse {
success: boolean;
message?: string;
error?: string;
version?: string;
/** Stash's own error text; only the admin-only test of a saved instance sends it */
details?: string;
}
Handler: testStashConnection in server/controllers/setup.ts
POST /api/setup/create-stash-instance¶
Authentication: None until setup starts, then Admin
Request Body:
interface CreateFirstStashInstanceRequest {
name?: string;
url: string;
uiUrl?: string;
apiKey: string;
}
Response:
interface CreateFirstStashInstanceResponse {
success: true;
instance: {
id: string;
name: string;
url: string;
uiUrl: string | null;
enabled: boolean;
createdAt: string;
};
}
Handler: createFirstStashInstance in server/controllers/setup.ts
GET /api/setup/stash-instance¶
The instance record carries Stash's address: admin only, like the Server settings tab that shows it
Authentication: Admin
Response:
interface GetStashInstanceResponse {
instance: {
id: string;
name: string;
url: string;
uiUrl: string | null;
enabled: boolean;
priority: number;
createdAt: string;
updatedAt: string;
/**
* The VR tag the admin chose for this server, a bare tag id on it; null
* uses Stash's own
*/
vrTagId: string | null;
/**
* The chosen tag's name; null when none is chosen or the tag was deleted in
* Stash (Peek then uses Stash's tag)
*/
vrTagName: string | null;
/**
* Stash's own VR tag (its `ui.vrTag` setting) by name, as last read by a
* sync; null when Stash has none
*/
stashVrTag: string | null;
} | null;
instanceCount: number;
}
Handler: getStashInstance in server/controllers/setup.ts
GET /api/setup/stash-instances¶
Authentication: Admin
Response:
Handler: getAllStashInstances in server/controllers/setup.ts
POST /api/setup/stash-instance¶
Authentication: Admin
Request Body:
interface CreateStashInstanceRequest {
name: string;
description?: string;
url: string;
uiUrl?: string;
apiKey: string;
enabled?: boolean;
priority?: number;
}
Response:
interface CreateStashInstanceResponse {
success: true;
instance: StashInstanceData;
/**
* The instance's first sync: "started", "queued" to start once the running
* sync ends, or "none" for a disabled instance
*/
sync: "started" | "queued" | "none";
}
Handler: createStashInstance in server/controllers/setup.ts
PUT /api/setup/stash-instance/:id¶
Authentication: Admin
Request Body:
interface UpdateStashInstanceRequest {
name?: string;
description?: string;
url?: string;
uiUrl?: string;
apiKey?: string;
enabled?: boolean;
priority?: number;
/**
* The server's VR tag: a bare id of a live tag on it (400 otherwise), or
* null to use Stash's own. A new `url` clears it.
*/
vrTagId?: string | null;
}
URL Parameters:
Response:
interface UpdateStashInstanceResponse {
success: true;
instance: StashInstanceData;
/**
* The re-sync a new URL or API key needs, or the first sync of an instance
* enabled before one ever finished: "started", "queued" to start once the
* running sync ends, or "none" (nothing to fetch, or disabled)
*/
sync: "started" | "queued" | "none";
}
Handler: updateStashInstance in server/controllers/setup.ts
POST /api/setup/stash-instance/:id/test-connection¶
Authentication: Admin
Request Body:
URL Parameters:
Response:
interface TestStashConnectionResponse {
success: boolean;
message?: string;
error?: string;
version?: string;
/** Stash's own error text; only the admin-only test of a saved instance sends it */
details?: string;
}
Handler: testSavedStashInstance in server/controllers/setup.ts
DELETE /api/setup/stash-instance/:id¶
Authentication: Admin
Request Body: never
URL Parameters:
Response:
Handler: deleteStashInstance in server/controllers/setup.ts
User¶
The signed-in user's settings, filter presets, hidden items, Stash server selection and permissions; user management for admins.
GET /api/user/settings¶
Authentication: Session
Response:
interface GetUserSettingsResponse {
settings: {
preferredPreviewQuality: string | null;
/** A built-in key, the user's own `custom-<id>`, or null when none is chosen */
theme: string | null;
carouselPreferences: CarouselPreference[];
navPreferences: NavPreference[] | null;
minimumPlayPercent: number;
syncToStash: boolean;
hideConfirmationDisabled: boolean;
unitPreference: string;
wallPlayback: string;
tableColumnDefaults: Record<string, TableColumnsConfig> | null;
cardDisplaySettings: CardDisplaySettings;
landingPagePreference: LandingPagePreference;
lightboxDoubleTapAction: string;
recommendationPreferences: RecommendationPreferences;
};
}
Handler: getUserSettings in server/controllers/user.ts
PUT /api/user/settings¶
Authentication: Session
Request Body:
interface UpdateUserSettingsBody {
preferredPreviewQuality?: string;
/** A built-in key or the target user's own `custom-<id>`; null clears it */
theme?: string | null;
carouselPreferences?: CarouselPreference[];
navPreferences?: NavPreference[];
minimumPlayPercent?: number;
syncToStash?: boolean;
unitPreference?: string;
wallPlayback?: string;
tableColumnDefaults?: Record<string, TableColumnsConfig> | null;
cardDisplaySettings?: CardDisplaySettings;
landingPagePreference?: LandingPagePreference | null;
lightboxDoubleTapAction?: string;
recommendationPreferences?: RecommendationPreferences;
}
URL Parameters:
Response:
interface UpdateUserSettingsResponse {
success: true;
settings: {
theme: string | null;
carouselPreferences: CarouselPreference[];
navPreferences: NavPreference[] | null;
minimumPlayPercent: number;
syncToStash: boolean;
wallPlayback: string;
tableColumnDefaults: Record<string, TableColumnsConfig> | null;
cardDisplaySettings: CardDisplaySettings;
landingPagePreference: LandingPagePreference;
lightboxDoubleTapAction: string;
recommendationPreferences: RecommendationPreferences;
};
}
Handler: updateUserSettings in server/controllers/user.ts
POST /api/user/change-password¶
Authentication: Session
Request Body:
Response:
Handler: changePassword in server/controllers/user.ts
GET /api/user/recovery-key¶
Authentication: Session
Response:
Handler: getRecoveryKey in server/controllers/user.ts
POST /api/user/recovery-key/regenerate¶
Authentication: Session
Request Body:
Response:
Handler: regenerateRecoveryKey in server/controllers/user.ts
GET /api/user/setup-status¶
Authentication: Session
Response:
Handler: getSetupStatus in server/controllers/user.ts
POST /api/user/complete-setup¶
Authentication: Session
Request Body:
Response:
Handler: completeSetup in server/controllers/user.ts
GET /api/user/filter-presets¶
Authentication: Session
Response:
Handler: getFilterPresets in server/controllers/user.ts
POST /api/user/filter-presets¶
Authentication: Session
Request Body:
interface SaveFilterPresetBody {
artifactType: string;
context?: string;
name: string;
filters: unknown;
sort: string;
direction: string;
viewMode?: string;
zoomLevel?: string;
gridDensity?: string;
tableColumns?: TableColumnsConfig | null;
perPage?: number | null;
setAsDefault?: boolean;
}
Response:
Handler: saveFilterPreset in server/controllers/user.ts
PUT /api/user/filter-presets/:artifactType/:presetId¶
Authentication: Session
Request Body:
type OverwriteViewBody = Omit<
SaveFilterPresetBody,
"artifactType" | "context" | "name" | "setAsDefault"
>;
URL Parameters:
interface DeleteFilterPresetParams extends Record<string, string> {
artifactType: string;
presetId: string;
}
Response:
Handler: overwriteFilterPreset in server/controllers/user.ts
PATCH /api/user/filter-presets/:artifactType/:presetId¶
Authentication: Session
Request Body:
URL Parameters:
interface DeleteFilterPresetParams extends Record<string, string> {
artifactType: string;
presetId: string;
}
Response:
Handler: renameFilterPreset in server/controllers/user.ts
DELETE /api/user/filter-presets/:artifactType/:presetId¶
Authentication: Session
Request Body: never
URL Parameters:
interface DeleteFilterPresetParams extends Record<string, string> {
artifactType: string;
presetId: string;
}
Response:
Handler: deleteFilterPreset in server/controllers/user.ts
GET /api/user/default-presets¶
Authentication: Session
Response:
Handler: getDefaultFilterPresets in server/controllers/user.ts
PUT /api/user/default-preset¶
Authentication: Session
Request Body:
Response:
Handler: setDefaultFilterPreset in server/controllers/user.ts
GET /api/user/filter-pins¶
Authentication: Session
Response:
Handler: getFilterPins in server/controllers/user.ts
PUT /api/user/filter-pins/:list¶
Authentication: Session
Request Body:
URL Parameters:
Response:
Handler: putFilterPins in server/controllers/user.ts
DELETE /api/user/filter-pins/:list¶
Authentication: Session
Request Body: never
URL Parameters:
Response:
Handler: resetFilterPins in server/controllers/user.ts
GET /api/user/permissions¶
User's own permissions
Authentication: Session
Response:
interface GetMyPermissionsResponse {
permissions: {
canShare: boolean;
canDownloadFiles: boolean;
canDownloadPlaylists: boolean;
sources: {
canShare: string;
canDownloadFiles: string;
canDownloadPlaylists: string;
};
};
}
Handler: getUserPermissions in server/controllers/user.ts
GET /api/user/all¶
Authentication: Admin
Response:
interface GetAllUsersResponse {
users: Array<{
id: number;
username: string;
role: string;
createdAt: string;
updatedAt: string;
syncToStash: boolean;
groups: Array<{ id: number; name: string }>;
}>;
}
Handler: getAllUsers in server/controllers/user.ts
POST /api/user/create¶
Authentication: Admin
Request Body:
Response:
interface CreateUserResponse {
success: true;
user: {
id: number;
username: string;
role: string;
createdAt: string;
};
}
Handler: createUser in server/controllers/user.ts
DELETE /api/user/:userId¶
Authentication: Admin
Request Body: never
URL Parameters:
Response:
Handler: deleteUser in server/controllers/user.ts
PUT /api/user/:userId/role¶
Authentication: Admin
Request Body:
URL Parameters:
Response:
interface UpdateUserRoleResponse {
success: true;
user: {
id: number;
username: string;
role: string;
updatedAt: string;
};
}
Handler: updateUserRole in server/controllers/user.ts
PUT /api/user/:userId/settings¶
Admin can update any user's settings
Authentication: Admin
Request Body:
interface UpdateUserSettingsBody {
preferredPreviewQuality?: string;
/** A built-in key or the target user's own `custom-<id>`; null clears it */
theme?: string | null;
carouselPreferences?: CarouselPreference[];
navPreferences?: NavPreference[];
minimumPlayPercent?: number;
syncToStash?: boolean;
unitPreference?: string;
wallPlayback?: string;
tableColumnDefaults?: Record<string, TableColumnsConfig> | null;
cardDisplaySettings?: CardDisplaySettings;
landingPagePreference?: LandingPagePreference | null;
lightboxDoubleTapAction?: string;
recommendationPreferences?: RecommendationPreferences;
}
URL Parameters:
Response:
interface UpdateUserSettingsResponse {
success: true;
settings: {
theme: string | null;
carouselPreferences: CarouselPreference[];
navPreferences: NavPreference[] | null;
minimumPlayPercent: number;
syncToStash: boolean;
wallPlayback: string;
tableColumnDefaults: Record<string, TableColumnsConfig> | null;
cardDisplaySettings: CardDisplaySettings;
landingPagePreference: LandingPagePreference;
lightboxDoubleTapAction: string;
recommendationPreferences: RecommendationPreferences;
};
}
Handler: updateUserSettings in server/controllers/user.ts
POST /api/user/:userId/sync-from-stash¶
Admin can sync Stash data for any user
Authentication: Admin
Request Body:
interface SyncFromStashBody {
options?: {
[K in keyof SyncFromStashOptions]?: Partial<SyncFromStashOptions[K]>;
};
}
URL Parameters:
Response:
interface SyncFromStashResponse {
/** False when an instance's import failed; the others still ran */
success: boolean;
message: string;
stats: SyncStats;
/** The instances whose import failed (the name falls back to the id) */
failedInstances: Array<{ id: string; name: string }>;
}
Handler: syncFromStash in server/controllers/user.ts
GET /api/user/:userId/permissions¶
Authentication: Admin
Request Body: never
URL Parameters:
Response:
interface GetMyPermissionsResponse {
permissions: {
canShare: boolean;
canDownloadFiles: boolean;
canDownloadPlaylists: boolean;
sources: {
canShare: string;
canDownloadFiles: string;
canDownloadPlaylists: string;
};
};
}
Handler: getAnyUserPermissions in server/controllers/user.ts
PUT /api/user/:userId/permissions¶
Authentication: Admin
Request Body:
interface UpdatePermissionOverridesBody {
canShareOverride?: boolean | null;
canDownloadFilesOverride?: boolean | null;
canDownloadPlaylistsOverride?: boolean | null;
}
URL Parameters:
Response:
Handler: updateUserPermissionOverrides in server/controllers/user.ts
GET /api/user/:userId/groups¶
Admin: get user's group memberships
Authentication: Admin
Request Body: never
URL Parameters:
Response:
Handler: getUserGroupMemberships in server/controllers/user.ts
POST /api/user/:userId/reset-password¶
Admin: reset user password
Authentication: Admin
Request Body:
URL Parameters:
Response:
Handler: adminResetPassword in server/controllers/user.ts
POST /api/user/:userId/regenerate-recovery-key¶
Admin: regenerate user recovery key
Authentication: Admin
Request Body: never
URL Parameters:
Response:
Handler: adminRegenerateRecoveryKey in server/controllers/user.ts
GET /api/user/:userId/restrictions¶
Get user's content restrictions
Authentication: Admin
Request Body: never
URL Parameters:
Response:
Handler: getUserRestrictions in server/controllers/user.ts
PUT /api/user/:userId/restrictions¶
Update user's content restrictions
Authentication: Admin
Request Body:
URL Parameters:
Response:
interface UpdateUserRestrictionsResponse {
success: true;
message: string;
restrictions: unknown[];
}
Handler: updateUserRestrictions in server/controllers/user.ts
DELETE /api/user/:userId/restrictions¶
Delete all user's content restrictions
Authentication: Admin
Request Body: never
URL Parameters:
Response:
Handler: deleteUserRestrictions in server/controllers/user.ts
POST /api/user/hidden-entities¶
Hide an entity
Authentication: Session
Request Body:
interface HideEntityBody {
entityType: string;
entityId: string;
/** The entity's Stash instance; a hide without one answers 400 */
instanceId: string;
}
Response:
Handler: hideEntity in server/controllers/user.ts
POST /api/user/hidden-entities/bulk¶
Hide multiple entities
Authentication: Session
Request Body:
interface HideEntitiesBody {
entities: Array<{
entityType: string;
entityId: string;
/** The entity's Stash instance; a target without one answers 400 */
instanceId: string;
}>;
}
Response:
interface HideEntitiesResponse {
success: true;
message: string;
successCount: number;
failCount: number;
}
Handler: hideEntities in server/controllers/user.ts
DELETE /api/user/hidden-entities/all¶
Unhide all entities (optionally filtered by type) - must be before :entityType/:entityId route
Authentication: Session
Request Body: never
URL Parameters: Record<string, string>
Query Parameters:
interface UnhideAllEntitiesQuery extends Record<
string,
string | string[] | undefined
> {
entityType?: string;
}
Response:
Handler: unhideAllEntities in server/controllers/user.ts
DELETE /api/user/hidden-entities/:entityType/:entityId¶
Unhide an entity
Authentication: Session
Request Body: never
URL Parameters:
interface UnhideEntityParams extends Record<string, string> {
entityType: string;
entityId: string;
}
Query Parameters:
interface UnhideEntityQuery extends Record<
string,
string | string[] | undefined
> {
instanceId?: string;
}
Response:
Handler: unhideEntity in server/controllers/user.ts
GET /api/user/hidden-entities¶
One page of hidden entities, with counts per type
Authentication: Session
Request Body: never
URL Parameters: Record<string, string>
Query Parameters:
interface GetHiddenEntitiesQuery extends Record<
string,
string | string[] | undefined
> {
entityType?: string;
/** 1-based, default 1 */
page?: string;
/** 1 to 100, default 50 */
per_page?: string;
}
Response:
interface GetHiddenEntitiesResponse {
/** The page's rows, newest first */
items: HiddenEntityItem[];
/** Rows of the requested type (every hideable type when none) */
total: number;
/** The user's rows per hideable type, whatever type was requested */
counts: Record<HiddenEntityType, number>;
}
Handler: getHiddenEntities in server/controllers/user.ts
PUT /api/user/hide-confirmation¶
Update hide confirmation preference
Authentication: Session
Request Body:
Response:
Handler: updateHideConfirmation in server/controllers/user.ts
GET /api/user/stash-instances¶
Get user's selected instances
Authentication: Session
Response:
Handler: getUserStashInstances in server/controllers/user.ts
PUT /api/user/stash-instances¶
Update user's instance selection
Authentication: Session
Request Body:
Response:
Handler: updateUserStashInstances in server/controllers/user.ts
User Groups¶
User groups and their members (admin only, except your own).
GET /api/groups/user/mine¶
User-facing route (must be before /:id to avoid conflicts)
Authentication: Session
Response:
Handler: getUserGroups in server/controllers/groups.ts
GET /api/groups¶
Authentication: Admin
Response:
Handler: getAllGroups in server/controllers/groups.ts
GET /api/groups/:id¶
Authentication: Admin
Request Body: never
URL Parameters:
Response:
Handler: getGroup in server/controllers/groups.ts
POST /api/groups¶
Authentication: Admin
Request Body:
interface CreateUserGroupBody {
name: string;
/** null or empty clears it */
description?: string | null;
canShare?: boolean;
canDownloadFiles?: boolean;
canDownloadPlaylists?: boolean;
}
Response:
interface CreateUserGroupResponse {
group: {
id: number;
name: string;
description: string | null;
canShare: boolean;
canDownloadFiles: boolean;
canDownloadPlaylists: boolean;
createdAt: string;
updatedAt: string;
};
}
Handler: createGroup in server/controllers/groups.ts
PUT /api/groups/:id¶
Authentication: Admin
Request Body:
interface UpdateUserGroupBody {
name?: string;
/** null or empty clears it */
description?: string | null;
canShare?: boolean;
canDownloadFiles?: boolean;
canDownloadPlaylists?: boolean;
}
URL Parameters:
Response:
interface UpdateUserGroupResponse {
group: {
id: number;
name: string;
description: string | null;
canShare: boolean;
canDownloadFiles: boolean;
canDownloadPlaylists: boolean;
createdAt: string;
updatedAt: string;
};
}
Handler: updateGroup in server/controllers/groups.ts
DELETE /api/groups/:id¶
Authentication: Admin
Request Body: never
URL Parameters:
Response:
Handler: deleteGroup in server/controllers/groups.ts
POST /api/groups/:id/members¶
Authentication: Admin
Request Body:
URL Parameters:
Response:
interface AddMemberResponse {
membership: {
id: number;
userId: number;
groupId: number;
createdAt: string;
};
}
Handler: addMember in server/controllers/groups.ts
DELETE /api/groups/:id/members/:userId¶
Authentication: Admin
Request Body: never
URL Parameters:
Response:
Handler: removeMember in server/controllers/groups.ts
Library¶
Browsing scenes, performers, studios, tags, collections, galleries and images, with the user's restrictions and hidden items applied.
GET /api/library/ready¶
Authentication: Session
Response:
Handler: getLibraryReady in server/routes/library/ready.ts
POST /api/library/scenes¶
Find scenes with filters
Authentication: Session
Request Body:
Response: FindScenesResponse<ListCount> | AmbiguousLookupResponse
Handler: findScenes in server/controllers/library/scenes.ts
POST /api/library/scenes/minimal¶
Minimal data for the scene picker (a clip filter's scenes)
Authentication: Session
Request Body:
Response:
Handler: findScenesMinimal in server/controllers/library/scenes.ts
GET /api/library/scenes/:id/similar¶
Find similar scenes
Authentication: Session
URL Parameters:
Query Parameters:
interface FindSimilarScenesQuery extends Record<
string,
string | undefined
> {
page?: string;
/** The seed's instance (required; a request without it answers 400) */
instanceId?: string;
}
Response:
interface FindSimilarScenesResponse {
scenes: NormalizedScene[];
count: number;
page: number;
perPage: number;
}
Handler: findSimilarScenes in server/controllers/library/scenes.ts
POST /api/library/scenes/recommended¶
Recommended: the scene list request within the user's ranked scenes
Authentication: Session
Request Body:
type FindRecommendedScenesRequest = Omit<
ListRequestInput<"scene">,
"filter" | "ids"
> & {
filter?: Omit<ListPageInput<"scene">, "sort"> & {
sort?: RecommendedSort | RandomSortKey;
};
};
Response:
interface GetRecommendedScenesResponse {
scenes: WithStashUrl<NormalizedScene>[];
/** The scenes matching the request within the ranked list; null when it asked for none (`filter.count: false`) */
count: number | null;
page: number;
perPage: number;
message?: string;
criteria?: {
favoritedPerformers: number;
ratedPerformers: number;
favoritedStudios: number;
ratedStudios: number;
favoritedTags: number;
ratedTags: number;
favoritedScenes: number;
ratedScenes: number;
/** Performers, studios and tags the user's viewing ranks in its top half */
rankedEntities: number;
};
}
Handler: findRecommendedScenes in server/controllers/library/scenes.ts
POST /api/library/scenes/recommended/count¶
How many scenes that request matches (the filter sheet's "Show N results")
Authentication: Session
Request Body:
type FindRecommendedScenesRequest = Omit<
ListRequestInput<"scene">,
"filter" | "ids"
> & {
filter?: Omit<ListPageInput<"scene">, "sort"> & {
sort?: RecommendedSort | RandomSortKey;
};
};
Response:
Handler: countRecommendedScenes in server/controllers/library/scenes.ts
GET /api/library/scenes/recommended¶
Get recommended scenes: for a tab still on the beta.8 bundle; remove in the release after 3.4.0-beta.9
Authentication: Session
URL Parameters: Record<string, string>
Query Parameters:
interface GetRecommendedScenesQuery extends Record<
string,
string | undefined
> {
page?: string;
per_page?: string;
}
Response:
interface GetRecommendedScenesResponse {
scenes: WithStashUrl<NormalizedScene>[];
/** The scenes matching the request within the ranked list; null when it asked for none (`filter.count: false`) */
count: number | null;
page: number;
perPage: number;
message?: string;
criteria?: {
favoritedPerformers: number;
ratedPerformers: number;
favoritedStudios: number;
ratedStudios: number;
favoritedTags: number;
ratedTags: number;
favoritedScenes: number;
ratedScenes: number;
/** Performers, studios and tags the user's viewing ranks in its top half */
rankedEntities: number;
};
}
Handler: getRecommendedScenes in server/controllers/library/scenes.ts
POST /api/library/performers¶
Find performers with filters
Authentication: Session
Request Body:
Response: FindPerformersResponse<ListCount> | AmbiguousLookupResponse
Handler: findPerformers in server/controllers/library/performers.ts
POST /api/library/performers/minimal¶
Minimal data for filter dropdowns
Authentication: Session
Request Body:
Response:
Handler: findPerformersMinimal in server/controllers/library/performers.ts
GET /api/library/performers/:id/counts¶
The detail page's tab counts, as the viewer sees them
Authentication: Session
Handler: getPerformerCounts in server/controllers/library/performers.ts
POST /api/library/studios¶
Find studios with filters
Authentication: Session
Request Body:
Response: FindStudiosResponse<ListCount> | AmbiguousLookupResponse
Handler: findStudios in server/controllers/library/studios.ts
POST /api/library/studios/minimal¶
Minimal data for filter dropdowns
Authentication: Session
Request Body:
Response:
Handler: findStudiosMinimal in server/controllers/library/studios.ts
GET /api/library/studios/:id/counts¶
The detail page's tab counts, as the viewer sees them
Authentication: Session
Handler: getStudioCounts in server/controllers/library/studios.ts
POST /api/library/tags¶
Find tags with filters
Authentication: Session
Request Body:
Response: FindTagsResponse<ListCount> | AmbiguousLookupResponse
Handler: findTags in server/controllers/library/tags.ts
POST /api/library/tags/minimal¶
Minimal data for filter dropdowns
Authentication: Session
Request Body:
Response:
Handler: findTagsMinimal in server/controllers/library/tags.ts
POST /api/library/tags/tree¶
The compact tag tree (hierarchy and folder views), optionally scoped
Authentication: Session
Request Body: FindTagTreeRequest | undefined
Response:
interface FindTagTreeResponse {
tags: TagTreeRow[];
/**
* With `untagged` in the request: the items of that type the user can see
* with no tag of their own, the total of the Untagged folder's list
* (`tag_count` EQUALS 0). With a scope, the scope's scenes (galleries and
* images 0, as the scoped rows count).
*/
untagged?: number;
}
Handler: findTagTree in server/controllers/library/tags.ts
GET /api/library/tags/:id/counts¶
The detail page's tab counts, as the viewer sees them
Authentication: Session
Handler: getTagCounts in server/controllers/library/tags.ts
POST /api/library/groups¶
Find groups with filters
Authentication: Session
Request Body:
Response: FindGroupsResponse<ListCount> | AmbiguousLookupResponse
Handler: findGroups in server/controllers/library/groups.ts
POST /api/library/groups/minimal¶
Minimal data for filter dropdowns
Authentication: Session
Request Body:
Response:
Handler: findGroupsMinimal in server/controllers/library/groups.ts
GET /api/library/groups/:id/counts¶
The detail page's tab counts, as the viewer sees them
Authentication: Session
Handler: getGroupCounts in server/controllers/library/groups.ts
POST /api/library/galleries¶
Gallery list: filter, sort and page
Authentication: Session
Request Body:
Response: FindGalleriesResponse<ListCount> | AmbiguousLookupResponse
Handler: findGalleries in server/controllers/library/galleries.ts
POST /api/library/galleries/minimal¶
Authentication: Session
Request Body:
Response:
Handler: findGalleriesMinimal in server/controllers/library/galleries.ts
GET /api/library/galleries/:id/counts¶
The detail page's tab counts, as the viewer sees them
Authentication: Session
Handler: getGalleryCounts in server/controllers/library/galleries.ts
POST /api/library/images¶
Find images (with filters, pagination, sorting)
Authentication: Session
Request Body:
Response: FindImagesResponse<ListCount> | AmbiguousLookupResponse
Handler: findImages in server/controllers/library/images.ts
POST /api/library/clips¶
Find clips with the clip filter body (GET /api/clips keeps the old parameters)
Authentication: Session
Request Body:
Response: FindClipsResponse<ListCount>
Handler: findClips in server/controllers/clips.ts
POST /api/library/scenes/count¶
Authentication: Session
Handler: inline in server/routes/library/counts.ts
POST /api/library/performers/count¶
Authentication: Session
Handler: inline in server/routes/library/counts.ts
POST /api/library/studios/count¶
Authentication: Session
Handler: inline in server/routes/library/counts.ts
POST /api/library/tags/count¶
Authentication: Session
Handler: inline in server/routes/library/counts.ts
POST /api/library/groups/count¶
Authentication: Session
Handler: inline in server/routes/library/counts.ts
POST /api/library/galleries/count¶
Authentication: Session
Handler: inline in server/routes/library/counts.ts
POST /api/library/images/count¶
Authentication: Session
Handler: inline in server/routes/library/counts.ts
POST /api/library/clips/count¶
Authentication: Session
Handler: inline in server/routes/library/counts.ts
Clips¶
Clips (scene markers from Stash).
GET /api/scenes/:id/clips¶
Scene clips endpoint (get clips for a specific scene)
Authentication: Session
Request Body: never
URL Parameters:
Query Parameters:
interface GetClipsForSceneQuery extends Record<
string,
string | string[] | undefined
> {
includeUngenerated?: string;
instanceId?: string;
}
Response:
Handler: getClipsForScene in server/controllers/clips.ts
GET /api/clips¶
Authentication: Session
Request Body: never
URL Parameters: Record<string, string>
Query Parameters:
interface GetClipsQuery extends Record<
string,
string | string[] | undefined
> {
page?: string;
perPage?: string;
sortBy?: string;
sortDir?: string;
isGenerated?: string;
sceneId?: string;
tagIds?: string;
sceneTagIds?: string;
performerIds?: string;
studioId?: string;
q?: string;
instanceId?: string;
/** "false": the page alone, `total` and `totalPages` null */
count?: string;
}
Response: GetClipsResponse<ListCount>
Handler: getClips in server/controllers/clips.ts
GET /api/clips/:id¶
Authentication: Session
Request Body: never
URL Parameters:
Response: GetClipByIdResponse | AmbiguousLookupResponse
Handler: getClipById in server/controllers/clips.ts
Timeline¶
Date distribution for the timeline view.
POST /api/timeline/:entityType/distribution¶
The bars of a list: the list's own request (filter, search, ids) plus the period
Authentication: Session
Request Body:
type PostDateDistributionRequest<
E extends TimelineEntityType = TimelineEntityType,
> = ListRequestInput<E> & {
/** Default "months" */
granularity?: TimelineGranularity;
};
URL Parameters:
Response:
Handler: postDateDistribution in server/controllers/timelineController.ts
GET /api/timeline/:entityType/distribution¶
The documented form: one entity parameter (performerId, tagId, ...)
Authentication: Session
Request Body: never
URL Parameters:
Query Parameters:
interface GetDateDistributionQuery extends Record<
string,
string | string[] | undefined
> {
granularity?: string;
performerId?: string;
tagId?: string;
studioId?: string;
groupId?: string;
galleryId?: string;
}
Response:
Handler: getDateDistribution in server/controllers/timelineController.ts
Playback¶
The stream and caption proxy, and the external player's personal signed link.
GET /api/scene/:sceneId/proxy-stream/:streamPath/:subPath¶
Authentication: Session or signed link
Request Body: never
URL Parameters:
Query Parameters:
Handler: proxyStashStream in server/controllers/video.ts
GET /api/scene/:sceneId/proxy-stream/:streamPath¶
Authentication: Session or signed link
Request Body: never
URL Parameters:
Query Parameters:
Handler: proxyStashStream in server/controllers/video.ts
GET /api/scene/:sceneId/caption¶
Caption/subtitle proxy
Authentication: Session or signed link
Request Body: never
URL Parameters:
Query Parameters:
Handler: getCaption in server/controllers/video.ts
GET /api/scene/:sceneId/poster¶
Scene poster (Stash's screenshot), for a Cast receiver's media link too
Authentication: Session or signed link
Request Body: never
URL Parameters:
Query Parameters:
Handler: proxyScenePoster in server/controllers/proxy.ts
POST /api/scene/:sceneId/external-player-link¶
Personal signed link for the external player button
Authentication: Session
Request Body:
URL Parameters:
Response:
interface ExternalPlayerLinkResponse {
/** `/api/scene/:id/proxy-stream/stream?instanceId=...&uid=...&exp=...&sig=...` */
url: string;
/** ISO timestamp, 12 hours after minting. */
expiresAt: string;
/**
* The scene file's MIME type from its extension (`video/x-matroska` for an
* mkv), `video/*` when unknown. The path itself is never returned.
*/
mimeType: string;
}
Handler: createExternalPlayerLink in server/controllers/video.ts
POST /api/scene/:sceneId/media-link¶
One signed media link per scene: Direct and HLS, captions and poster, for a Cast receiver or Safari's native player
Authentication: Session
Request Body:
URL Parameters:
Response:
interface SceneMediaLinkResponse {
/** ISO timestamp, 12 hours after minting. */
expiresAt: string;
/** Direct and the HLS tiers only, in `buildSceneStreams`' order. */
streams: { url: string; mime_type: string; label: string }[];
/** The one source a Cast receiver is given; null when none plays there. */
cast: { url: string; contentType: string; kind: "direct" | "hls" } | null;
captions: { url: string; lang: string; type: string }[];
poster: string | null;
}
Handler: createSceneMediaLink in server/controllers/video.ts
Media Proxy¶
Images and previews from Stash, served through Peek so no user gets Stash's address or API key.
GET /api/proxy/stash¶
Media proxy (requires a Peek session; per-entity access in the handler)
Authentication: Session
Request Body: never
URL Parameters: Record<string, string>
Query Parameters:
Handler: proxyStashMedia in server/controllers/proxy.ts
GET /api/proxy/scene/:id/preview¶
Authentication: Session
Request Body: never
URL Parameters:
Query Parameters:
Handler: proxyScenePreview in server/controllers/proxy.ts
GET /api/proxy/scene/:id/webp¶
Authentication: Session
Request Body: never
URL Parameters:
Query Parameters:
Handler: proxySceneWebp in server/controllers/proxy.ts
GET /api/proxy/image/:imageId/:type¶
Image proxy route (requires a Peek session; per-entity access in the handler)
Authentication: Session
Request Body: never
URL Parameters:
Query Parameters:
Handler: proxyImage in server/controllers/proxy.ts
GET /api/proxy/clip/:id/preview¶
Clip preview proxy route (requires a Peek session; per-entity access in the handler)
Authentication: Session
Request Body: never
URL Parameters:
Query Parameters:
Handler: proxyClipPreview in server/controllers/proxy.ts
Playlists¶
Playlists, their items, play queue and sharing.
GET /api/playlists/shared¶
Get playlists shared with current user
Authentication: Session
URL Parameters: Record<string, string>
Query Parameters:
interface GetUserPlaylistsQuery extends Record<
string,
string | undefined
> {
containsScene?: string;
}
Response:
Handler: getSharedPlaylists in server/controllers/playlist.ts
GET /api/playlists¶
Get all user playlists
Authentication: Session
URL Parameters: Record<string, string>
Query Parameters:
interface GetUserPlaylistsQuery extends Record<
string,
string | undefined
> {
containsScene?: string;
}
Response:
Handler: getUserPlaylists in server/controllers/playlist.ts
GET /api/playlists/:id¶
Get single playlist with a page of its items
Authentication: Session
URL Parameters:
Query Parameters:
interface GetPlaylistQuery extends Record<string, string | undefined> {
page?: string;
per_page?: string;
sort?: string;
direction?: string;
}
Response:
interface GetPlaylistResponse {
playlist: PlaylistData & { items: PlaylistItemWithScene[] };
/** How many of the playlist's items the viewer can see */
totalItems: number;
/**
* The owner's count of items they cannot play (hidden, restricted,
* deleted from Stash, or on an instance they do not use); 0 for anyone
* else, who is told nothing about them
*/
unavailableItems: number;
/** The page read */
page: number;
perPage: number;
/**
* The sort the items came back in: one of `PLAYLIST_ITEM_SORTS`, a random
* one as `random_<seed>` (a bare `random` names the seed it used)
*/
sort: string;
direction: "ASC" | "DESC";
isOwner: boolean;
accessLevel: "owner" | "shared";
sharedViaGroups?: string[];
/** Who owns the playlist, for "Shared by" */
owner: { id: number; username: string };
}
Handler: getPlaylist in server/controllers/playlist.ts
GET /api/playlists/:id/queue¶
Get the play queue: every visible item in the shown order
Authentication: Session
URL Parameters:
Query Parameters:
interface GetPlaylistQueueQuery extends Record<
string,
string | undefined
> {
sort?: string;
direction?: string;
}
Response:
Handler: getPlaylistQueue in server/controllers/playlist.ts
POST /api/playlists¶
Create new playlist
Authentication: Session
Request Body:
Response:
Handler: createPlaylist in server/controllers/playlist.ts
PUT /api/playlists/:id¶
Update playlist
Authentication: Session
Request Body:
interface UpdatePlaylistRequest {
/** Not empty once trimmed */
name?: string;
/** A client may send null to clear the description. */
description?: string | null;
shuffle?: boolean;
repeat?: PlaylistRepeatMode;
}
URL Parameters:
Response:
Handler: updatePlaylist in server/controllers/playlist.ts
DELETE /api/playlists/:id¶
Delete playlist
Authentication: Session
URL Parameters:
Response:
Handler: deletePlaylist in server/controllers/playlist.ts
POST /api/playlists/:id/items¶
Add scene to playlist
Authentication: Session
Request Body:
URL Parameters:
Response:
interface AddSceneToPlaylistResponse {
item: {
id: number;
playlistId: number;
instanceId: string;
sceneId: string;
position: number;
addedAt: Date;
};
}
Handler: addSceneToPlaylist in server/controllers/playlist.ts
POST /api/playlists/:id/items/bulk¶
Add several scenes to playlist
Authentication: Session
Request Body:
URL Parameters:
Response:
interface AddScenesToPlaylistResponse {
added: number;
alreadyInPlaylist: number;
/** Scenes the requester cannot see or that no longer exist */
unavailable: number;
}
Handler: addScenesToPlaylist in server/controllers/playlist.ts
POST /api/playlists/:id/items/remove-unavailable¶
Remove the items deleted from Stash (owner only)
Authentication: Session
URL Parameters:
Response:
Handler: removeUnavailablePlaylistItems in server/controllers/playlist.ts
POST /api/playlists/:id/items/remove¶
Remove several items by item id (owner only)
Authentication: Session
Request Body:
URL Parameters:
Response:
Handler: removePlaylistItems in server/controllers/playlist.ts
PUT /api/playlists/:id/items/:itemId/position¶
Move one item to an index among the items the owner sees (owner only)
Authentication: Session
Request Body:
URL Parameters:
Response:
Handler: movePlaylistItem in server/controllers/playlist.ts
DELETE /api/playlists/:id/items/:sceneId¶
Remove scene from playlist
Authentication: Session
URL Parameters:
interface RemoveSceneFromPlaylistParams extends Record<string, string> {
id: string;
sceneId: string;
}
Query Parameters:
interface RemoveSceneFromPlaylistQuery extends Record<
string,
string | string[] | undefined
> {
instanceId?: string | string[];
}
Response:
Handler: removeSceneFromPlaylist in server/controllers/playlist.ts
POST /api/playlists/:id/sort¶
Save a view sort as the playlist's order (owner only)
Authentication: Session
Request Body:
URL Parameters:
Response:
Handler: sortPlaylist in server/controllers/playlist.ts
GET /api/playlists/:id/shares¶
Get sharing info for a playlist
Authentication: Session
URL Parameters:
Response:
Handler: getPlaylistShares in server/controllers/playlist.ts
PUT /api/playlists/:id/shares¶
Update playlist sharing
Authentication: Session
Request Body:
URL Parameters:
Response:
Handler: updatePlaylistShares in server/controllers/playlist.ts
POST /api/playlists/:id/duplicate¶
Duplicate a playlist
Authentication: Session
URL Parameters:
Response:
Handler: duplicatePlaylist in server/controllers/playlist.ts
Downloads¶
Scene, image and playlist downloads.
GET /api/downloads¶
Get all user downloads
Authentication: Session
Response:
Handler: getUserDownloads in server/controllers/download.ts
GET /api/downloads/:id¶
Get specific download status
Authentication: Session
Request Body: never
URL Parameters:
Response:
Handler: getDownloadStatus in server/controllers/download.ts
GET /api/downloads/:id/file¶
Get download file
Authentication: Session
Request Body: never
URL Parameters:
Handler: getDownloadFile in server/controllers/download.ts
POST /api/downloads/scene/:sceneId¶
Start scene download
Authentication: Session
Request Body: Partial<StartEntityDownloadRequest> | undefined
URL Parameters:
Response:
Handler: startSceneDownload in server/controllers/download.ts
POST /api/downloads/image/:imageId¶
Start image download
Authentication: Session
Request Body: Partial<StartEntityDownloadRequest> | undefined
URL Parameters:
Response:
Handler: startImageDownload in server/controllers/download.ts
POST /api/downloads/playlist/:playlistId¶
Start playlist download
Authentication: Session
Request Body: never
URL Parameters:
Response: StartPlaylistDownloadResponse | PlaylistTooLargeResponse
Handler: startPlaylistDownload in server/controllers/download.ts
DELETE /api/downloads/:id¶
Delete download
Authentication: Session
Request Body: never
URL Parameters:
Response:
Handler: deleteDownload in server/controllers/download.ts
POST /api/downloads/:id/retry¶
Retry failed download
Authentication: Session
Request Body: never
URL Parameters:
Response:
Handler: retryDownload in server/controllers/download.ts
Ratings¶
The user's ratings and favorites.
PUT /api/ratings/scene/:sceneId¶
Authentication: Session
Handler: updateSceneRating in server/controllers/ratings.ts
PUT /api/ratings/performer/:performerId¶
Authentication: Session
Handler: updatePerformerRating in server/controllers/ratings.ts
PUT /api/ratings/studio/:studioId¶
Authentication: Session
Handler: updateStudioRating in server/controllers/ratings.ts
PUT /api/ratings/tag/:tagId¶
Authentication: Session
Handler: updateTagRating in server/controllers/ratings.ts
PUT /api/ratings/gallery/:galleryId¶
Authentication: Session
Handler: updateGalleryRating in server/controllers/ratings.ts
PUT /api/ratings/group/:groupId¶
Authentication: Session
Handler: updateGroupRating in server/controllers/ratings.ts
PUT /api/ratings/image/:imageId¶
Authentication: Session
Handler: updateImageRating in server/controllers/ratings.ts
Watch History¶
Plays, resume points and O counts of scenes.
POST /api/watch-history/save-activity¶
Save activity (called by track-activity plugin every 10 seconds)
Authentication: Session
Request Body:
interface SaveActivityRequest {
/** The scene's instance: required, the server never guesses one */
instanceId: string;
sceneId: string;
resumeTime?: number;
playDuration?: number;
}
Response:
Handler: saveActivity in server/controllers/watchHistory.ts
POST /api/watch-history/increment-play-count¶
Increment play count (called when minimum play percentage reached)
Authentication: Session
Request Body:
interface IncrementPlayCountRequest {
/** The scene's instance: required, the server never guesses one */
instanceId: string;
sceneId: string;
/**
* One viewing's token (1 to 64 characters), the same on every retry of its
* request: the server counts a token once for 10 minutes. A request
* without one always counts.
*/
playToken?: string;
}
Response:
Handler: incrementPlayCount in server/controllers/watchHistory.ts
POST /api/watch-history/increment-o¶
Increment O counter
Authentication: Session
Request Body:
Response:
Handler: incrementOCounter in server/controllers/watchHistory.ts
POST /api/watch-history/decrement-o¶
Remove the newest O ("Remove last O")
Authentication: Session
Request Body:
Response:
Handler: decrementOCounter in server/controllers/watchHistory.ts
DELETE /api/watch-history¶
Clear all watch history for current user
Authentication: Session
Response:
interface ClearAllWatchHistoryResponse {
success: true;
deletedCounts: {
watchHistory: number;
performerStats: number;
studioStats: number;
tagStats: number;
rankings: number;
};
message: string;
}
Handler: clearAllWatchHistory in server/controllers/watchHistory.ts
GET /api/watch-history/scenes¶
The viewer's watched scenes, paged, sorted and filtered in SQL (before /:sceneId, which would take "scenes" as an id)
Authentication: Session
URL Parameters: Record<string, string>
Query Parameters:
interface GetWatchedScenesQuery extends Record<
string,
string | undefined
> {
/** A `WatchedScenesView`; default `all` */
view?: string;
/** A `WatchedScenesSort`; default `recent` */
sort?: string;
/** Default 1 */
page?: string;
/** 1 to 250; default 24 */
per_page?: string;
/** `false` skips the totals (both answer null); default `true` */
count?: string;
}
Response:
interface GetWatchedScenesResponse {
scenes: NormalizedScene[];
/** Every scene in the view; null when the request sent `count=false` */
total: number | null;
/** Seconds watched over every scene in the view; null with `count=false` */
totalPlayDuration: number | null;
}
Handler: getWatchedScenes in server/controllers/watchHistory.ts
GET /api/watch-history/:sceneId¶
Get watch history for specific scene
Authentication: Session
URL Parameters:
Response:
interface GetWatchHistoryResponse {
exists: boolean;
resumeTime: number | null;
playCount: number;
playDuration?: number;
lastPlayedAt?: Date | null;
oCount: number;
oHistory?: string[];
playHistory?: string[];
}
Handler: getWatchHistory in server/controllers/watchHistory.ts
Image View History¶
Image views and O counts.
POST /api/image-view-history/increment-o¶
Increment O counter for image
Authentication: Session
Request Body:
Response:
Handler: incrementImageOCounter in server/controllers/imageViewHistory.ts
POST /api/image-view-history/decrement-o¶
Remove the newest O ("Remove last O")
Authentication: Session
Request Body:
Response:
Handler: decrementImageOCounter in server/controllers/imageViewHistory.ts
POST /api/image-view-history/view¶
Record image view (when opened in Lightbox)
Authentication: Session
Request Body:
Response:
Handler: recordImageView in server/controllers/imageViewHistory.ts
GET /api/image-view-history/:imageId¶
Get view history for specific image
Authentication: Session
URL Parameters:
Response:
interface GetImageViewHistoryResponse {
exists: boolean;
viewCount: number;
viewHistory?: string[];
oCount: number;
oHistory?: string[];
lastViewedAt?: Date | null;
}
Handler: getImageViewHistory in server/controllers/imageViewHistory.ts
User Stats¶
The user's own statistics.
GET /api/user-stats¶
Get user stats
Authentication: Session
Response:
interface UserStatsResponse {
library: LibraryStats;
engagement: EngagementStats;
topScenes: TopScene[];
topPerformers: TopPerformer[];
/** Performers of the viewer's partner genders; [] unless they set some */
topPartners: TopPerformer[];
topStudios: TopStudio[];
topTags: TopTag[];
mostWatchedScene: HighlightScene | null;
mostViewedImage: HighlightImage | null;
mostOdScene: HighlightScene | null;
mostOdPerformer: HighlightPerformer | null;
}
Handler: getUserStats in server/controllers/userStats.ts
Carousels¶
Custom home page carousels.
GET /api/carousels¶
Get all user's custom carousels
Authentication: Session
Response:
Handler: getUserCarousels in server/controllers/carousel.ts
POST /api/carousels/preview¶
Preview carousel results without saving. Scenes are listed only from the user's instances: none ready yet answers 503 ready:false, as the lists do
Authentication: Session
Request Body:
Response:
Handler: previewCarousel in server/controllers/carousel.ts
GET /api/carousels/:id¶
Get single carousel by ID
Authentication: Session
URL Parameters:
Response:
Handler: getCarousel in server/controllers/carousel.ts
GET /api/carousels/:id/execute¶
Execute carousel query and get scenes (503 ready:false as above)
Authentication: Session
URL Parameters:
Response:
interface ExecuteCarouselByIdResponse {
carousel: {
id: string;
title: string;
icon: string;
};
scenes: WithStashUrl<NormalizedScene>[];
}
Handler: executeCarouselById in server/controllers/carousel.ts
POST /api/carousels¶
Create new carousel
Authentication: Session
Request Body:
interface CreateCarouselRequest {
title: string;
icon?: string;
rules: CarouselRulesInput;
sort?: string;
direction?: string;
}
Response:
Handler: createCarousel in server/controllers/carousel.ts
PUT /api/carousels/:id¶
Update carousel
Authentication: Session
Request Body:
interface UpdateCarouselRequest {
title?: string;
icon?: string;
rules?: CarouselRulesInput;
sort?: string;
direction?: string;
}
URL Parameters:
Response:
Handler: updateCarousel in server/controllers/carousel.ts
DELETE /api/carousels/:id¶
Delete carousel
Authentication: Session
URL Parameters:
Response:
Handler: deleteCarousel in server/controllers/carousel.ts
Custom Themes¶
Custom themes.
GET /api/themes/custom¶
Get all user custom themes
Authentication: Session
Response:
Handler: getUserCustomThemes in server/controllers/customTheme.ts
GET /api/themes/custom/:id¶
Get single custom theme
Authentication: Session
URL Parameters:
Response:
Handler: getCustomTheme in server/controllers/customTheme.ts
POST /api/themes/custom¶
Create new custom theme
Authentication: Session
Request Body:
Response:
Handler: createCustomTheme in server/controllers/customTheme.ts
PUT /api/themes/custom/:id¶
Update custom theme
Authentication: Session
Request Body:
URL Parameters:
Response:
Handler: updateCustomTheme in server/controllers/customTheme.ts
DELETE /api/themes/custom/:id¶
Delete custom theme
Authentication: Session
URL Parameters:
Response:
Handler: deleteCustomTheme in server/controllers/customTheme.ts
POST /api/themes/custom/:id/duplicate¶
Duplicate custom theme
Authentication: Session
URL Parameters:
Response:
Handler: duplicateCustomTheme in server/controllers/customTheme.ts
Sync¶
Syncing the library cache from Stash (admin only).
GET /api/sync/status¶
Whether a sync runs, the sync settings, and every configured instance's entity sync states (admin only: only the Server settings tab shows them). Instances appear by id and name, never by address.
Authentication: Admin
Response:
interface SyncStatusResponse {
/** A sync is running. */
inProgress: boolean;
/** What holds the lock, if anything: a sync or an instance's removal. */
activeJob: SyncJob | null;
settings: {
syncIntervalMinutes: number;
enableScanSubscription: boolean;
};
/** Every configured instance, enabled or not, in priority order. */
instances: SyncInstanceStatus[];
}
Handler: inline in server/routes/sync.ts
POST /api/sync/trigger¶
Manually trigger a sync (admin only)
Body: { type?: 'full' | 'incremental' } Default: incremental
Authentication: Admin
Handler: inline in server/routes/sync.ts
POST /api/sync/abort¶
Abort the current sync (admin only)
Authentication: Admin
Handler: inline in server/routes/sync.ts
POST /api/sync/cleanup¶
The sync status's "Apply deletions" (admin only): one type's cleanup on one enabled instance, without the ratio guard, after a cleanup refused to soft-delete more than half of the type. It runs in the background under the sync lock, so it answers 409 while a sync or an instance deletion runs; its outcome goes to the type's lastError.
Body: { instanceId: string, entityType: "tag" | "studio" | ... }
Authentication: Admin
Request Body: Partial<ApplyDeletionsRequest> | undefined
Response:
Handler: inline in server/routes/sync.ts
POST /api/sync/reprobe-clips¶
Re-probe clips that were synced before previews were generated (admin only)
Body: { instanceId?: string } If instanceId is not provided, uses the first enabled instance
Authentication: Admin
Handler: inline in server/routes/sync.ts
PUT /api/sync/settings¶
Update sync settings (admin only). A new interval re-arms the scheduler's timer; no sync starts, so the answer comes at once.
Body: { syncIntervalMinutes?: number, enableScanSubscription?: boolean }
Authentication: Admin
Handler: inline in server/routes/sync.ts
Exclusions¶
Recomputing the content-restriction exclusions (admin only).
POST /api/exclusions/recompute/:userId¶
Recompute exclusions for a single user (admin only)
Authentication: Admin
Handler: inline in server/routes/exclusions.ts
POST /api/exclusions/recompute-all¶
Recompute exclusions for all users (admin only)
Authentication: Admin
Handler: inline in server/routes/exclusions.ts
GET /api/exclusions/stats¶
Get exclusion statistics per user and entity type (admin only)
Authentication: Admin
Handler: inline in server/routes/exclusions.ts
Admin¶
Merge reconciliation and database backups (admin only).
GET /api/admin/orphaned-scenes¶
List all orphaned scenes with user activity
Authentication: Admin
Handler: inline in server/routes/mergeReconciliation.ts
GET /api/admin/orphaned-scenes/:ref/matches¶
Live scenes of the orphan's instance with a matching phash
Authentication: Admin
Handler: inline in server/routes/mergeReconciliation.ts
POST /api/admin/orphaned-scenes/:ref/reconcile¶
Transfer user data from the orphan to targetSceneId, a scene id on the orphan's instance. A target that is not a live scene there answers 400.
Authentication: Admin
Handler: inline in server/routes/mergeReconciliation.ts
POST /api/admin/orphaned-scenes/:ref/discard¶
Delete the orphan's history, ratings and playlist entries (on its instance only)
Authentication: Admin
Handler: inline in server/routes/mergeReconciliation.ts
POST /api/admin/reconcile-all¶
Reconcile every orphan with exactly one phash match on its instance, as sync does; orphans with several matches are left for the admin to pick.
Authentication: Admin
Handler: inline in server/routes/mergeReconciliation.ts
GET /api/admin/database/backups¶
List all database backups
Authentication: Admin
Response:
interface ListDatabaseBackupsResponse {
backups: DatabaseBackup[];
/** The directory backups are written to and listed from. */
directory: string;
}
Handler: inline in server/routes/databaseBackup.ts
POST /api/admin/database/backup¶
Create a new database backup
Authentication: Admin
Response:
Handler: inline in server/routes/databaseBackup.ts
DELETE /api/admin/database/backups/:filename¶
Delete a specific backup
Authentication: Admin
Response:
Handler: inline in server/routes/databaseBackup.ts