{ "info": { "title": "Sofa API", "version": "0.1.0" }, "servers": [ { "url": "{protocol}://{host}:{port}/api/v1", "variables": { "protocol": { "default": "http", "enum": [ "http", "https" ] }, "host": { "default": "localhost" }, "port": { "default": "3000" } } } ], "tags": [ { "name": "Titles", "description": "Movie and TV show management" }, { "name": "Episodes", "description": "Episode watch tracking" }, { "name": "Seasons", "description": "Season watch tracking" }, { "name": "People", "description": "Cast and crew information" }, { "name": "Dashboard", "description": "User dashboard data" }, { "name": "Explore", "description": "Discover trending and popular content" }, { "name": "Search", "description": "Search for movies and TV shows" }, { "name": "Discover", "description": "Advanced content discovery with filters" }, { "name": "System", "description": "Server status and configuration" }, { "name": "Integrations", "description": "Media server integrations" }, { "name": "Admin", "description": "Server administration" }, { "name": "Account", "description": "User account management" } ], "components": { "securitySchemes": { "session": { "type": "apiKey", "name": "better-auth.session_token", "in": "cookie", "description": "Better Auth session cookie" } }, "schemas": { "Title": { "type": "object", "properties": { "id": { "type": "string", "description": "Internal UUIDv7 identifier" }, "tmdbId": { "type": "number", "description": "TMDB numeric ID" }, "type": { "enum": [ "movie", "tv" ], "type": "string", "description": "Media type" }, "title": { "type": "string", "description": "Display title (localized)" }, "originalTitle": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "description": "Original language title" }, "overview": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "description": "Plot synopsis" }, "releaseDate": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "description": "Theatrical release date (movies, ISO 8601)" }, "firstAirDate": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "description": "First air date (TV shows, ISO 8601)" }, "posterPath": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "description": "Poster image path" }, "posterThumbHash": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "description": "ThumbHash blur placeholder for the poster" }, "backdropPath": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "description": "Backdrop image path" }, "backdropThumbHash": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "description": "ThumbHash blur placeholder for the backdrop" }, "popularity": { "anyOf": [ { "type": "number" }, { "type": "null" } ], "description": "TMDB popularity score" }, "voteAverage": { "anyOf": [ { "type": "number" }, { "type": "null" } ], "description": "Average user rating (0-10)" }, "voteCount": { "anyOf": [ { "type": "number" }, { "type": "null" } ], "description": "Total number of votes" }, "status": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "description": "Production status (e.g. Released, Returning Series)" }, "contentRating": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "description": "Content rating (e.g. PG-13, TV-MA)" }, "imdbId": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "description": "IMDb title ID (e.g. tt0137523)" }, "tvdbId": { "anyOf": [ { "type": "number" }, { "type": "null" } ], "description": "TVDB ID (TV shows only)" }, "originalLanguage": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "description": "Original language ISO 639-1 code (e.g. en)" }, "runtimeMinutes": { "anyOf": [ { "type": "number" }, { "type": "null" } ], "description": "Runtime in minutes (movies only)" }, "colorPalette": { "anyOf": [ { "type": "object", "properties": { "vibrant": { "anyOf": [ { "type": "string" }, { "type": "null" } ] }, "darkVibrant": { "anyOf": [ { "type": "string" }, { "type": "null" } ] }, "lightVibrant": { "anyOf": [ { "type": "string" }, { "type": "null" } ] }, "muted": { "anyOf": [ { "type": "string" }, { "type": "null" } ] }, "darkMuted": { "anyOf": [ { "type": "string" }, { "type": "null" } ] }, "lightMuted": { "anyOf": [ { "type": "string" }, { "type": "null" } ] } }, "required": [ "vibrant", "darkVibrant", "lightVibrant", "muted", "darkMuted", "lightMuted" ], "description": "Extracted color palette from the title poster image (CSS hex values)" }, { "type": "null" } ] }, "trailerVideoKey": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "description": "YouTube video key for the trailer" }, "genres": { "type": "array", "items": { "type": "string" }, "description": "Genre names" } }, "required": [ "id", "tmdbId", "type", "title", "originalTitle", "overview", "releaseDate", "firstAirDate", "posterPath", "posterThumbHash", "backdropPath", "backdropThumbHash", "popularity", "voteAverage", "voteCount", "status", "contentRating", "imdbId", "tvdbId", "originalLanguage", "runtimeMinutes", "colorPalette", "trailerVideoKey", "genres" ], "description": "A fully resolved movie or TV show from TMDB" }, "Person": { "type": "object", "properties": { "id": { "type": "string", "description": "Internal UUIDv7 identifier" }, "tmdbId": { "type": "number", "description": "TMDB person ID" }, "name": { "type": "string", "description": "Full name" }, "biography": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "description": "Biography text" }, "birthday": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "description": "Date of birth (ISO 8601)" }, "deathday": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "description": "Date of death (ISO 8601)" }, "placeOfBirth": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "description": "Place of birth" }, "profilePath": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "description": "Profile photo image path" }, "profileThumbHash": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "description": "ThumbHash blur placeholder for the profile photo" }, "knownForDepartment": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "description": "Primary department (e.g. Acting, Directing)" }, "imdbId": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "description": "IMDb person ID (e.g. nm0000123)" } }, "required": [ "id", "tmdbId", "name", "biography", "birthday", "deathday", "placeOfBirth", "profilePath", "profileThumbHash", "knownForDepartment", "imdbId" ], "description": "A person (actor, director, crew member) from TMDB" }, "PersonCredit": { "type": "object", "properties": { "titleId": { "type": "string", "description": "Internal title ID" }, "tmdbId": { "type": "number", "description": "TMDB title ID" }, "type": { "enum": [ "movie", "tv" ], "type": "string", "description": "Media type" }, "title": { "type": "string", "description": "Title name" }, "posterPath": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "description": "Poster image path" }, "posterThumbHash": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "description": "ThumbHash blur placeholder for the poster" }, "releaseDate": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "description": "Release date (ISO 8601)" }, "firstAirDate": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "description": "First air date (ISO 8601)" }, "voteAverage": { "anyOf": [ { "type": "number" }, { "type": "null" } ], "description": "Average rating (0-10)" }, "character": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "description": "Character name (for acting credits)" }, "department": { "type": "string", "description": "Department (e.g. Acting, Directing)" }, "job": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "description": "Job title (for crew credits)" } }, "required": [ "titleId", "tmdbId", "type", "title", "posterPath", "posterThumbHash", "releaseDate", "firstAirDate", "voteAverage", "character", "department", "job" ], "description": "A person's credit in a movie or TV show" }, "Episode": { "type": "object", "properties": { "id": { "type": "string", "description": "Episode ID" }, "episodeNumber": { "type": "number", "description": "Episode number within the season" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "description": "Episode title" }, "overview": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "description": "Episode plot summary" }, "stillPath": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "description": "Episode still image path" }, "stillThumbHash": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "description": "ThumbHash blur placeholder for the still image" }, "airDate": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "description": "Original air date (ISO 8601)" }, "runtimeMinutes": { "anyOf": [ { "type": "number" }, { "type": "null" } ], "description": "Episode runtime in minutes" } }, "required": [ "id", "episodeNumber", "name", "overview", "stillPath", "stillThumbHash", "airDate", "runtimeMinutes" ], "description": "A single TV episode" }, "Season": { "type": "object", "properties": { "id": { "type": "string", "description": "Season ID" }, "seasonNumber": { "type": "number", "description": "Season number (0 for specials)" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "description": "Season name" }, "episodes": { "type": "array", "items": { "$ref": "#/components/schemas/Episode" }, "description": "Episodes in this season" } }, "required": [ "id", "seasonNumber", "name", "episodes" ], "description": "A TV season with its episodes" }, "CastMember": { "type": "object", "properties": { "id": { "type": "string", "description": "Credit ID" }, "personId": { "type": "string", "description": "Internal person ID" }, "name": { "type": "string", "description": "Person's name" }, "character": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "description": "Character name (for acting credits)" }, "department": { "type": "string", "description": "Department (e.g. Acting, Directing)" }, "job": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "description": "Job title (for crew credits)" }, "displayOrder": { "type": "number", "description": "Sort order in the credits list" }, "episodeCount": { "anyOf": [ { "type": "number" }, { "type": "null" } ], "description": "Number of episodes (TV only)" }, "profilePath": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "description": "Profile photo image path" }, "profileThumbHash": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "description": "ThumbHash blur placeholder for the profile photo" }, "tmdbId": { "type": "number", "description": "TMDB person ID" } }, "required": [ "id", "personId", "name", "character", "department", "job", "displayOrder", "episodeCount", "profilePath", "profileThumbHash", "tmdbId" ], "description": "A cast or crew member credit" }, "BrowseItem": { "type": "object", "properties": { "id": { "type": "string", "description": "Internal title ID" }, "tmdbId": { "type": "number", "description": "TMDB numeric ID" }, "type": { "enum": [ "movie", "tv" ], "type": "string", "description": "Media type" }, "title": { "type": "string", "description": "Display title" }, "posterPath": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "description": "Poster image path" }, "posterThumbHash": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "description": "ThumbHash blur placeholder for the poster" }, "releaseDate": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "description": "Release date (ISO 8601)" }, "firstAirDate": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "description": "First air date (ISO 8601)" }, "voteAverage": { "anyOf": [ { "type": "number" }, { "type": "null" } ], "description": "Average rating (0-10)" } }, "required": [ "id", "tmdbId", "type", "title", "posterPath", "posterThumbHash", "releaseDate", "firstAirDate", "voteAverage" ], "description": "A TMDB title card used in browse/trending/popular lists" }, "Recommendation": { "type": "object", "properties": { "id": { "type": "string", "description": "Internal title ID" }, "tmdbId": { "type": "number", "description": "TMDB numeric ID" }, "type": { "enum": [ "movie", "tv" ], "type": "string", "description": "Media type" }, "title": { "type": "string", "description": "Display title" }, "posterPath": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "description": "Poster image path" }, "posterThumbHash": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "description": "ThumbHash blur placeholder for the poster" }, "releaseDate": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "description": "Release date (ISO 8601)" }, "firstAirDate": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "description": "First air date (ISO 8601)" }, "voteAverage": { "anyOf": [ { "type": "number" }, { "type": "null" } ], "description": "Average rating (0-10)" } }, "required": [ "id", "tmdbId", "type", "title", "posterPath", "posterThumbHash", "releaseDate", "firstAirDate", "voteAverage" ], "description": "A recommended title" }, "Integration": { "type": "object", "properties": { "id": { "type": "string", "description": "Integration ID" }, "provider": { "type": "string", "description": "Provider name (plex, jellyfin, etc.)" }, "type": { "enum": [ "webhook", "list" ], "type": "string", "description": "Integration type: webhook (Plex/Jellyfin/Emby) or list (Sonarr/Radarr)" }, "token": { "type": "string", "description": "Webhook authentication token" }, "enabled": { "type": "boolean", "description": "Whether the integration is active" }, "lastEventAt": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "description": "Last received event timestamp (ISO 8601)" }, "createdAt": { "type": "string", "description": "When the integration was created (ISO 8601)" } }, "required": [ "id", "provider", "type", "token", "enabled", "lastEventAt", "createdAt" ], "description": "A media server integration configuration" }, "IntegrationEvent": { "type": "object", "properties": { "id": { "type": "string", "description": "Event ID" }, "eventType": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "description": "Webhook event type" }, "mediaType": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "description": "Media type from the event" }, "mediaTitle": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "description": "Title from the event" }, "status": { "enum": [ "success", "ignored", "error" ], "type": "string", "description": "Event processing outcome" }, "receivedAt": { "type": "string", "description": "When the event was received (ISO 8601)" } }, "required": [ "id", "eventType", "mediaType", "mediaTitle", "status", "receivedAt" ], "description": "A webhook or sync event from a media server" }, "Backup": { "type": "object", "properties": { "filename": { "type": "string", "description": "Backup filename on disk" }, "sizeBytes": { "type": "number", "description": "Backup file size in bytes" }, "createdAt": { "type": "string", "description": "When the backup was created (ISO 8601)" }, "source": { "enum": [ "manual", "scheduled", "pre-restore" ], "type": "string", "description": "How the backup was created: manual, scheduled, or automatic pre-restore" } }, "required": [ "filename", "sizeBytes", "createdAt", "source" ], "description": "A database backup file" }, "Job": { "type": "object", "properties": { "jobName": { "type": "string", "description": "Cron job identifier" }, "cronPattern": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "description": "Cron expression (e.g. 0 2 * * *)" }, "nextRunAt": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "description": "Next scheduled run (ISO 8601)" }, "lastRunAt": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "description": "Last run start time (ISO 8601)" }, "lastDurationMs": { "anyOf": [ { "type": "number" }, { "type": "null" } ], "description": "Duration of last run in milliseconds" }, "lastStatus": { "anyOf": [ { "enum": [ "running", "success", "error" ], "type": "string" }, { "type": "null" } ], "description": "Outcome of the last run" }, "lastError": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "description": "Error message from the last failed run" }, "isCurrentlyRunning": { "type": "boolean", "description": "Whether the job is currently executing" }, "disabled": { "type": "boolean", "description": "Whether the job is disabled" } }, "required": [ "jobName", "cronPattern", "nextRunAt", "lastRunAt", "lastDurationMs", "lastStatus", "lastError", "isCurrentlyRunning", "disabled" ], "description": "Status of a background cron job" }, "SystemHealth": { "type": "object", "properties": { "database": { "type": "object", "properties": { "dbSizeBytes": { "type": "number", "description": "SQLite database file size" }, "walSizeBytes": { "type": "number", "description": "WAL file size" }, "titleCount": { "type": "number", "description": "Total titles in database" }, "episodeCount": { "type": "number", "description": "Total episodes in database" }, "userCount": { "type": "number", "description": "Total registered users" } }, "required": [ "dbSizeBytes", "walSizeBytes", "titleCount", "episodeCount", "userCount" ], "description": "Database size and record counts" }, "tmdb": { "type": "object", "properties": { "connected": { "type": "boolean", "description": "Whether TMDB API is reachable" }, "tokenValid": { "type": "boolean", "description": "Whether the API token is valid" }, "tokenConfigured": { "type": "boolean", "description": "Whether a token is set" }, "responseTimeMs": { "anyOf": [ { "type": "number" }, { "type": "null" } ], "description": "TMDB API response time in milliseconds" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "description": "Error message if connectivity check failed" } }, "required": [ "connected", "tokenValid", "tokenConfigured", "responseTimeMs", "error" ], "description": "TMDB API connectivity status" }, "jobs": { "type": "array", "items": { "$ref": "#/components/schemas/Job" }, "description": "Status of all cron jobs" }, "imageCache": { "type": "object", "properties": { "enabled": { "type": "boolean", "description": "Whether image caching is enabled" }, "totalSizeBytes": { "type": "number", "description": "Total cache size on disk" }, "imageCount": { "type": "number", "description": "Total cached images" }, "categories": { "type": "object", "propertyNames": { "type": "string" }, "additionalProperties": { "type": "object", "properties": { "count": { "type": "number", "description": "Images in this category" }, "sizeBytes": { "type": "number", "description": "Category size on disk" } }, "required": [ "count", "sizeBytes" ] }, "description": "Breakdown by image category (posters, backdrops, etc.)" } }, "required": [ "enabled", "totalSizeBytes", "imageCount", "categories" ], "description": "Image cache statistics" }, "backups": { "type": "object", "properties": { "lastBackupAt": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "description": "Last backup timestamp (ISO 8601)" }, "lastBackupAgeHours": { "anyOf": [ { "type": "number" }, { "type": "null" } ], "description": "Hours since the last backup" }, "backupCount": { "type": "number", "description": "Total backup files" }, "totalSizeBytes": { "type": "number", "description": "Total size of all backups" } }, "required": [ "lastBackupAt", "lastBackupAgeHours", "backupCount", "totalSizeBytes" ], "description": "Backup status and size information" }, "environment": { "type": "object", "properties": { "dataDir": { "type": "string", "description": "Configured data directory path" }, "dataDirWritable": { "type": "boolean", "description": "Whether the data directory is writable" }, "envVars": { "type": "array", "items": { "type": "object", "properties": { "name": { "type": "string", "description": "Environment variable name" }, "value": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "description": "Current value (sensitive values are masked)" } }, "required": [ "name", "value" ] }, "description": "Relevant environment variable statuses" } }, "required": [ "dataDir", "dataDirWritable", "envVars" ], "description": "Server environment information" }, "checkedAt": { "type": "string", "description": "When this health check was performed (ISO 8601)" } }, "required": [ "database", "tmdb", "jobs", "imageCache", "backups", "environment", "checkedAt" ], "description": "Comprehensive system health report covering database, TMDB, jobs, cache, backups, and environment" } } }, "openapi": "3.1.1", "paths": { "/titles/{id}": { "get": { "operationId": "titles.detail", "summary": "Get title details", "description": "Fetch full title metadata including seasons, cast, and streaming availability. Imports from TMDB on first access if not yet cached locally.", "tags": [ "Titles" ], "parameters": [ { "name": "id", "in": "path", "required": true, "schema": { "type": "string", "minLength": 1, "description": "Internal UUIDv7 identifier" } } ], "responses": { "200": { "description": "Full title with seasons, cast, and availability offers", "content": { "application/json": { "schema": { "type": "object", "properties": { "title": { "$ref": "#/components/schemas/Title" }, "seasons": { "type": "array", "items": { "$ref": "#/components/schemas/Season" }, "description": "TV seasons (empty for movies)" }, "availability": { "type": "array", "items": { "type": "object", "properties": { "providerId": { "type": "number", "description": "JustWatch provider ID" }, "providerName": { "type": "string", "description": "Display name (e.g. Netflix, Hulu)" }, "logoPath": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "description": "Provider logo image path" }, "offerType": { "type": "string", "description": "Offer type: flatrate, rent, buy, free, ads" }, "watchUrl": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "description": "Direct link to watch on this provider" } }, "required": [ "providerId", "providerName", "logoPath", "offerType", "watchUrl" ], "description": "A streaming availability offer from a provider" }, "description": "Streaming availability offers" }, "cast": { "type": "array", "items": { "$ref": "#/components/schemas/CastMember" }, "description": "Cast and crew credits" } }, "required": [ "title", "seasons", "availability", "cast" ], "description": "Full title details with seasons, cast, and streaming availability" } } } }, "404": { "description": "404", "content": { "application/json": { "schema": { "oneOf": [ { "type": "object", "properties": { "defined": { "const": true }, "code": { "const": "NOT_FOUND" }, "status": { "const": 404 }, "message": { "type": "string", "default": "Title not found" }, "data": { "type": "object", "properties": { "code": { "const": "TITLE_NOT_FOUND" } }, "required": [ "code" ] } }, "required": [ "defined", "code", "status", "message", "data" ] }, { "type": "object", "properties": { "defined": { "const": false }, "code": { "type": "string" }, "status": { "type": "number" }, "message": { "type": "string" }, "data": {} }, "required": [ "defined", "code", "status", "message" ] } ] } } } } }, "security": [ { "session": [] } ] } }, "/titles/{id}/status": { "put": { "operationId": "titles.updateStatus", "summary": "Update tracking status", "description": "Set the user's tracking status for a title. Use null to remove the title from the library entirely.", "tags": [ "Titles" ], "parameters": [ { "name": "id", "in": "path", "required": true, "schema": { "type": "string", "minLength": 1, "description": "Title ID" } } ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "properties": { "status": { "anyOf": [ { "enum": [ "watchlist" ], "type": "string" }, { "type": "null" } ], "description": "Set to watchlist to add to library, or null to remove. Other transitions happen via watch endpoints." } }, "required": [ "status" ], "description": "Add a title to the user's watchlist or remove from library" } } } }, "responses": { "200": { "description": "OK" } }, "security": [ { "session": [] } ] } }, "/titles/{id}/rating": { "put": { "operationId": "titles.updateRating", "summary": "Rate a title", "description": "Set a 0-5 star rating for a title. Use 0 to clear the rating.", "tags": [ "Titles" ], "parameters": [ { "name": "id", "in": "path", "required": true, "schema": { "type": "string", "minLength": 1, "description": "Title ID" } } ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "properties": { "stars": { "type": "integer", "minimum": 0, "maximum": 5, "description": "Star rating from 0 (clear) to 5" } }, "required": [ "stars" ], "description": "Set or clear a star rating for a title" } } } }, "responses": { "200": { "description": "OK" } }, "security": [ { "session": [] } ] } }, "/titles/{id}/watch": { "post": { "operationId": "titles.watchMovie", "summary": "Mark movie as watched", "description": "Log a watch event for a movie. Automatically sets status to completed.", "tags": [ "Titles" ], "parameters": [ { "name": "id", "in": "path", "required": true, "schema": { "type": "string", "minLength": 1, "description": "Internal UUIDv7 identifier" } } ], "requestBody": { "required": false, "content": { "application/json": { "schema": { "type": "object" } } } }, "responses": { "200": { "description": "OK" } }, "security": [ { "session": [] } ] } }, "/titles/{id}/watch-all": { "post": { "operationId": "titles.watchAll", "summary": "Mark all episodes watched", "description": "Mark every episode of a TV show as watched. Requires seasons to be hydrated first.", "tags": [ "Titles" ], "parameters": [ { "name": "id", "in": "path", "required": true, "schema": { "type": "string", "minLength": 1, "description": "Internal UUIDv7 identifier" } } ], "requestBody": { "required": false, "content": { "application/json": { "schema": { "type": "object" } } } }, "responses": { "200": { "description": "OK" } }, "security": [ { "session": [] } ] } }, "/titles/{id}/user-info": { "get": { "operationId": "titles.userInfo", "summary": "Get user's title info", "description": "Fetch the current user's tracking status, rating, and watched episode IDs for a title.", "tags": [ "Titles" ], "parameters": [ { "name": "id", "in": "path", "required": true, "schema": { "type": "string", "minLength": 1, "description": "Internal UUIDv7 identifier" } } ], "responses": { "200": { "description": "User's status, rating, and list of watched episode IDs", "content": { "application/json": { "schema": { "type": "object", "properties": { "status": { "anyOf": [ { "enum": [ "in_watchlist", "watching", "caught_up", "completed" ], "type": "string" }, { "type": "null" } ], "description": "User's display status, or null if not in library" }, "rating": { "anyOf": [ { "type": "number" }, { "type": "null" } ], "description": "User's star rating (0-5), or null if unrated" }, "episodeWatches": { "type": "array", "items": { "type": "string" }, "description": "IDs of episodes the user has watched" } }, "required": [ "status", "rating", "episodeWatches" ], "description": "The current user's tracking info for a title" } } } } }, "security": [ { "session": [] } ] } }, "/titles/{id}/recommendations": { "get": { "operationId": "titles.recommendations", "summary": "Get title recommendations", "description": "Fetch similar titles based on locally cached recommendation data, along with the user's statuses for each.", "tags": [ "Titles" ], "parameters": [ { "name": "id", "in": "path", "required": true, "schema": { "type": "string", "minLength": 1, "description": "Internal UUIDv7 identifier" } } ], "responses": { "200": { "description": "Recommended titles with user statuses", "content": { "application/json": { "schema": { "type": "object", "properties": { "recommendations": { "type": "array", "items": { "$ref": "#/components/schemas/Recommendation" } }, "userStatuses": { "type": "object", "propertyNames": { "type": "string" }, "additionalProperties": { "enum": [ "in_watchlist", "watching", "caught_up", "completed" ], "type": "string" }, "description": "Map of title ID to the user's display status" } }, "required": [ "recommendations", "userStatuses" ], "description": "Recommended titles with the user's statuses" } } } } }, "security": [ { "session": [] } ] } }, "/titles/{id}/quick-add": { "post": { "operationId": "titles.quickAdd", "summary": "Quick add title to library", "description": "Add a title to the user's watchlist and trigger a full TMDB import if needed. If the title already exists in the user's library, returns alreadyAdded: true.", "tags": [ "Titles" ], "parameters": [ { "name": "id", "in": "path", "required": true, "schema": { "type": "string", "minLength": 1, "description": "Internal UUIDv7 identifier" } } ], "requestBody": { "required": false, "content": { "application/json": { "schema": { "type": "object" } } } }, "responses": { "200": { "description": "Title ID and whether it was already in the library", "content": { "application/json": { "schema": { "type": "object", "properties": { "id": { "type": "string", "description": "Internal title ID" }, "alreadyAdded": { "type": "boolean", "description": "True if the title was already in the user's library" } }, "required": [ "id", "alreadyAdded" ], "description": "Result of a quick-add operation" } } } }, "404": { "description": "404", "content": { "application/json": { "schema": { "oneOf": [ { "type": "object", "properties": { "defined": { "const": true }, "code": { "const": "NOT_FOUND" }, "status": { "const": 404 }, "message": { "type": "string", "default": "Title not found" }, "data": { "type": "object", "properties": { "code": { "const": "TITLE_NOT_FOUND" } }, "required": [ "code" ] } }, "required": [ "defined", "code", "status", "message", "data" ] }, { "type": "object", "properties": { "defined": { "const": false }, "code": { "type": "string" }, "status": { "type": "number" }, "message": { "type": "string" }, "data": {} }, "required": [ "defined", "code", "status", "message" ] } ] } } } } }, "security": [ { "session": [] } ] } }, "/episodes/{id}/watch": { "post": { "operationId": "episodes.watch", "summary": "Mark episode watched", "description": "Record a watch event for a single TV episode.", "tags": [ "Episodes" ], "parameters": [ { "name": "id", "in": "path", "required": true, "schema": { "type": "string", "minLength": 1, "description": "Internal UUIDv7 identifier" } } ], "requestBody": { "required": false, "content": { "application/json": { "schema": { "type": "object" } } } }, "responses": { "200": { "description": "OK" } }, "security": [ { "session": [] } ] } }, "/episodes/{id}/unwatch": { "post": { "operationId": "episodes.unwatch", "summary": "Mark episode unwatched", "description": "Remove the watch record for a single TV episode.", "tags": [ "Episodes" ], "parameters": [ { "name": "id", "in": "path", "required": true, "schema": { "type": "string", "minLength": 1, "description": "Internal UUIDv7 identifier" } } ], "requestBody": { "required": false, "content": { "application/json": { "schema": { "type": "object" } } } }, "responses": { "200": { "description": "OK" } }, "security": [ { "session": [] } ] } }, "/episodes/batch-watch": { "post": { "operationId": "episodes.batchWatch", "summary": "Batch mark episodes watched", "description": "Record watch events for multiple episodes at once. Useful for marking a range of episodes as watched.", "tags": [ "Episodes" ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "properties": { "episodeIds": { "type": "array", "minItems": 1, "items": { "type": "string" }, "description": "List of episode IDs to mark as watched" } }, "required": [ "episodeIds" ], "description": "Batch of episode IDs to mark as watched" } } } }, "responses": { "200": { "description": "OK" } }, "security": [ { "session": [] } ] } }, "/seasons/{id}/watch": { "post": { "operationId": "seasons.watch", "summary": "Mark season watched", "description": "Mark all episodes in a season as watched in a single operation.", "tags": [ "Seasons" ], "parameters": [ { "name": "id", "in": "path", "required": true, "schema": { "type": "string", "minLength": 1, "description": "Internal UUIDv7 identifier" } } ], "requestBody": { "required": false, "content": { "application/json": { "schema": { "type": "object" } } } }, "responses": { "200": { "description": "OK" } }, "security": [ { "session": [] } ] } }, "/seasons/{id}/unwatch": { "post": { "operationId": "seasons.unwatch", "summary": "Mark season unwatched", "description": "Remove all watch records for episodes in a season in a single operation.", "tags": [ "Seasons" ], "parameters": [ { "name": "id", "in": "path", "required": true, "schema": { "type": "string", "minLength": 1, "description": "Internal UUIDv7 identifier" } } ], "requestBody": { "required": false, "content": { "application/json": { "schema": { "type": "object" } } } }, "responses": { "200": { "description": "OK" } }, "security": [ { "session": [] } ] } }, "/people/{id}": { "get": { "operationId": "people.detail", "summary": "Get person details", "description": "Fetch a person's profile and paginated filmography. Imports from TMDB on first access if not yet cached locally.", "tags": [ "People" ], "parameters": [ { "name": "id", "in": "path", "required": true, "schema": { "type": "string", "minLength": 1, "description": "Internal UUIDv7 identifier" } }, { "name": "page", "in": "query", "required": false, "schema": { "type": "integer", "minimum": 1, "maximum": 500, "default": 1, "description": "Page number (1-indexed)" }, "allowEmptyValue": true, "allowReserved": true }, { "name": "limit", "in": "query", "required": false, "schema": { "type": "integer", "minimum": 1, "maximum": 100, "default": 20, "description": "Results per page (1-100, default 20)" }, "allowEmptyValue": true, "allowReserved": true } ], "responses": { "200": { "description": "Person profile, paginated filmography credits, and user statuses for their titles", "content": { "application/json": { "schema": { "type": "object", "properties": { "person": { "$ref": "#/components/schemas/Person" }, "filmography": { "type": "array", "items": { "$ref": "#/components/schemas/PersonCredit" }, "description": "Credits for this person (paginated)" }, "userStatuses": { "type": "object", "propertyNames": { "type": "string" }, "additionalProperties": { "enum": [ "in_watchlist", "watching", "caught_up", "completed" ], "type": "string" }, "description": "Map of title ID to the user's display status" }, "page": { "type": "number", "description": "Current page number" }, "totalPages": { "type": "number", "description": "Total number of pages available" }, "totalResults": { "type": "number", "description": "Total number of results across all pages" } }, "required": [ "person", "filmography", "userStatuses", "page", "totalPages", "totalResults" ], "description": "Person profile with paginated filmography and user's statuses for their titles" } } } }, "404": { "description": "404", "content": { "application/json": { "schema": { "oneOf": [ { "type": "object", "properties": { "defined": { "const": true }, "code": { "const": "NOT_FOUND" }, "status": { "const": 404 }, "message": { "type": "string", "default": "Person not found" }, "data": { "type": "object", "properties": { "code": { "const": "PERSON_NOT_FOUND" } }, "required": [ "code" ] } }, "required": [ "defined", "code", "status", "message", "data" ] }, { "type": "object", "properties": { "defined": { "const": false }, "code": { "type": "string" }, "status": { "type": "number" }, "message": { "type": "string" }, "data": {} }, "required": [ "defined", "code", "status", "message" ] } ] } } } } }, "security": [ { "session": [] } ] } }, "/dashboard/stats": { "get": { "operationId": "dashboard.stats", "summary": "Get dashboard statistics", "description": "Fetch summary counts for the current user: movies watched this month, episodes this week, library size, and completed titles.", "tags": [ "Dashboard" ], "responses": { "200": { "description": "Aggregate watch statistics", "content": { "application/json": { "schema": { "type": "object", "properties": { "moviesThisMonth": { "type": "number", "description": "Movies watched in the current calendar month" }, "episodesThisWeek": { "type": "number", "description": "Episodes watched in the current calendar week" }, "librarySize": { "type": "number", "description": "Total titles in the user's library" }, "completed": { "type": "number", "description": "Total titles with completed status" } }, "required": [ "moviesThisMonth", "episodesThisWeek", "librarySize", "completed" ], "description": "Aggregate watch statistics for the dashboard" } } } } }, "security": [ { "session": [] } ] } }, "/dashboard/continue-watching": { "get": { "operationId": "dashboard.continueWatching", "summary": "Get continue watching list", "description": "Fetch TV shows the user is currently watching, with the next unwatched episode and progress for each.", "tags": [ "Dashboard" ], "responses": { "200": { "description": "In-progress shows with next episode and watch progress", "content": { "application/json": { "schema": { "type": "object", "properties": { "items": { "type": "array", "items": { "type": "object", "properties": { "title": { "type": "object", "properties": { "id": { "type": "string", "description": "Title ID" }, "title": { "type": "string", "description": "Display title" }, "backdropPath": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "description": "Backdrop image path" }, "backdropThumbHash": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "description": "ThumbHash blur placeholder for the backdrop" } }, "required": [ "id", "title", "backdropPath", "backdropThumbHash" ] }, "nextEpisode": { "anyOf": [ { "type": "object", "properties": { "seasonNumber": { "type": "number", "description": "Season number" }, "episodeNumber": { "type": "number", "description": "Episode number" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "description": "Episode title" }, "stillPath": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "description": "Episode still image path" }, "stillThumbHash": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "description": "ThumbHash blur placeholder for the still" } }, "required": [ "seasonNumber", "episodeNumber", "name", "stillPath", "stillThumbHash" ] }, { "type": "null" } ], "description": "Next unwatched episode, or null if all caught up" }, "totalEpisodes": { "type": "number", "description": "Total episodes across all seasons" }, "watchedEpisodes": { "type": "number", "description": "Episodes the user has watched" } }, "required": [ "title", "nextEpisode", "totalEpisodes", "watchedEpisodes" ], "description": "An in-progress show with watch progress" } } }, "required": [ "items" ], "description": "TV shows the user is currently watching with next episode info" } } } } }, "security": [ { "session": [] } ] } }, "/dashboard/upcoming": { "get": { "operationId": "dashboard.upcoming", "summary": "Get upcoming episodes and movies", "description": "Fetch upcoming episodes and movie releases for titles in the user's library, sorted by date. Supports cursor-based pagination.", "tags": [ "Dashboard" ], "parameters": [ { "name": "days", "in": "query", "schema": { "type": "integer", "minimum": 1, "maximum": 90, "default": 90, "description": "How many days into the future to look" }, "allowEmptyValue": true, "allowReserved": true }, { "name": "limit", "in": "query", "schema": { "type": "integer", "minimum": 1, "maximum": 50, "default": 20, "description": "Maximum items per page" }, "allowEmptyValue": true, "allowReserved": true }, { "name": "cursor", "in": "query", "schema": { "type": "string", "description": "Pagination cursor" }, "allowEmptyValue": true, "allowReserved": true } ], "responses": { "200": { "description": "Upcoming items sorted by date with streaming info", "content": { "application/json": { "schema": { "type": "object", "properties": { "items": { "type": "array", "items": { "type": "object", "properties": { "episodeId": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "description": "Episode ID (TV only, null for movies and collapsed batches)" }, "titleId": { "type": "string", "description": "Internal title ID" }, "titleName": { "type": "string", "description": "Display title" }, "titleType": { "enum": [ "movie", "tv" ], "type": "string", "description": "Media type" }, "posterPath": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "description": "Poster image path" }, "posterThumbHash": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "description": "ThumbHash blur placeholder" }, "backdropPath": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "description": "Backdrop image path" }, "backdropThumbHash": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "description": "ThumbHash blur placeholder for backdrop" }, "seasonNumber": { "anyOf": [ { "type": "number" }, { "type": "null" } ], "description": "Season number (TV only)" }, "episodeNumber": { "anyOf": [ { "type": "number" }, { "type": "null" } ], "description": "Episode number (TV only)" }, "episodeName": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "description": "Episode title (TV only)" }, "episodeCount": { "type": "number", "description": "Number of episodes (1 for single, >1 for collapsed batch drops)" }, "date": { "type": "string", "description": "Air date or release date (YYYY-MM-DD)" }, "userStatus": { "enum": [ "in_watchlist", "watching", "caught_up", "completed" ], "type": "string", "description": "User's display status" }, "isNewSeason": { "type": "boolean", "description": "Whether this is a new season for a completed show" }, "streamingProvider": { "anyOf": [ { "type": "object", "properties": { "providerId": { "type": "number" }, "providerName": { "type": "string" }, "logoPath": { "anyOf": [ { "type": "string" }, { "type": "null" } ] } }, "required": [ "providerId", "providerName", "logoPath" ] }, { "type": "null" } ], "description": "Primary streaming provider, or null" } }, "required": [ "episodeId", "titleId", "titleName", "titleType", "posterPath", "posterThumbHash", "backdropPath", "backdropThumbHash", "seasonNumber", "episodeNumber", "episodeName", "episodeCount", "date", "userStatus", "isNewSeason", "streamingProvider" ], "description": "An upcoming episode or movie release" } }, "nextCursor": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "description": "Cursor for the next page, or null if no more items" } }, "required": [ "items", "nextCursor" ], "description": "Upcoming episodes and movie releases for tracked titles" } } } } }, "security": [ { "session": [] } ] } }, "/dashboard/library": { "get": { "operationId": "dashboard.library", "summary": "Get user library", "description": "Fetch paginated titles in the user's library with their tracking statuses.", "tags": [ "Dashboard" ], "parameters": [ { "name": "page", "in": "query", "schema": { "type": "integer", "minimum": 1, "maximum": 500, "default": 1, "description": "Page number (1-indexed)" }, "allowEmptyValue": true, "allowReserved": true }, { "name": "limit", "in": "query", "schema": { "type": "integer", "minimum": 1, "maximum": 100, "default": 20, "description": "Results per page (1-100, default 20)" }, "allowEmptyValue": true, "allowReserved": true } ], "responses": { "200": { "description": "Paginated library items with user statuses", "content": { "application/json": { "schema": { "type": "object", "properties": { "items": { "type": "array", "items": { "type": "object", "properties": { "id": { "type": "string", "description": "Title ID" }, "tmdbId": { "type": "number", "description": "TMDB numeric ID" }, "type": { "enum": [ "movie", "tv" ], "type": "string", "description": "Media type" }, "title": { "type": "string", "description": "Display title" }, "posterPath": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "description": "Poster image path" }, "posterThumbHash": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "description": "ThumbHash blur placeholder for the poster" }, "releaseDate": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "description": "Release date (ISO 8601)" }, "firstAirDate": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "description": "First air date (ISO 8601)" }, "voteAverage": { "anyOf": [ { "type": "number" }, { "type": "null" } ], "description": "Average rating (0-10)" }, "userStatus": { "anyOf": [ { "enum": [ "in_watchlist", "watching", "caught_up", "completed" ], "type": "string" }, { "type": "null" } ], "description": "User's display status" } }, "required": [ "id", "tmdbId", "type", "title", "posterPath", "posterThumbHash", "releaseDate", "firstAirDate", "voteAverage", "userStatus" ], "description": "A library item with user status" } }, "page": { "type": "number", "description": "Current page number" }, "totalPages": { "type": "number", "description": "Total number of pages available" }, "totalResults": { "type": "number", "description": "Total number of results across all pages" } }, "required": [ "items", "page", "totalPages", "totalResults" ], "description": "Paginated titles in the user's library" } } } } }, "security": [ { "session": [] } ] } }, "/dashboard/recommendations": { "get": { "operationId": "dashboard.recommendations", "summary": "Get personalized recommendations", "description": "Fetch personalized title recommendations based on the user's library and watch history.", "tags": [ "Dashboard" ], "responses": { "200": { "description": "Recommended titles", "content": { "application/json": { "schema": { "type": "object", "properties": { "items": { "type": "array", "items": { "$ref": "#/components/schemas/Recommendation" } } }, "required": [ "items" ], "description": "Personalized title recommendations for the dashboard" } } } } }, "security": [ { "session": [] } ] } }, "/dashboard/watch-history": { "get": { "operationId": "dashboard.watchHistory", "summary": "Get watch history", "description": "Fetch the user's watch counts grouped by time period. Useful for rendering activity charts.", "tags": [ "Dashboard" ], "parameters": [ { "name": "type", "in": "query", "required": true, "schema": { "enum": [ "movie", "episode" ], "type": "string", "description": "What to count: movie watches or episode watches" }, "allowEmptyValue": true, "allowReserved": true }, { "name": "period", "in": "query", "required": true, "schema": { "enum": [ "today", "this_week", "this_month", "this_year" ], "type": "string", "description": "Time range for the histogram" }, "allowEmptyValue": true, "allowReserved": true } ], "responses": { "200": { "description": "Watch counts bucketed by time period", "content": { "application/json": { "schema": { "type": "object", "properties": { "count": { "type": "number", "description": "Total watches in the selected period" }, "history": { "type": "array", "items": { "type": "object", "properties": { "bucket": { "type": "string", "description": "Time period label (e.g. date or week)" }, "count": { "type": "number", "description": "Number of watches in this bucket" } }, "required": [ "bucket", "count" ], "description": "A single time-bucketed watch count" }, "description": "Watch counts bucketed by time period" } }, "required": [ "count", "history" ], "description": "Watch history with time-bucketed counts" } } } } }, "security": [ { "session": [] } ] } }, "/explore/trending": { "get": { "operationId": "explore.trending", "summary": "Get trending titles", "description": "Fetch today's trending movies and/or TV shows from TMDB, including a featured hero title and the user's statuses.", "tags": [ "Explore" ], "parameters": [ { "name": "type", "in": "query", "required": true, "schema": { "enum": [ "all", "movie", "tv" ], "type": "string", "description": "Trending category: all, movie, or tv" }, "allowEmptyValue": true, "allowReserved": true }, { "name": "page", "in": "query", "required": false, "schema": { "type": "integer", "minimum": 1, "maximum": 500, "default": 1, "description": "Page number (1-indexed)" }, "allowEmptyValue": true, "allowReserved": true } ], "responses": { "200": { "description": "Trending items, hero spotlight, and user statuses", "content": { "application/json": { "schema": { "type": "object", "properties": { "items": { "type": "array", "items": { "$ref": "#/components/schemas/BrowseItem" }, "description": "Trending titles" }, "hero": { "anyOf": [ { "type": "object", "properties": { "id": { "type": "string", "description": "Internal title ID" }, "tmdbId": { "type": "number", "description": "TMDB numeric ID" }, "type": { "enum": [ "movie", "tv" ], "type": "string", "description": "Media type" }, "title": { "type": "string", "description": "Display title" }, "overview": { "type": "string", "description": "Plot synopsis" }, "backdropPath": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "description": "Backdrop image path" }, "voteAverage": { "type": "number", "description": "Average rating (0-10)" } }, "required": [ "id", "tmdbId", "type", "title", "overview", "backdropPath", "voteAverage" ] }, { "type": "null" } ], "description": "Featured hero title for the spotlight banner" }, "userStatuses": { "type": "object", "propertyNames": { "type": "string" }, "additionalProperties": { "enum": [ "in_watchlist", "watching", "caught_up", "completed" ], "type": "string" }, "description": "Map of title ID to the user's display status" }, "episodeProgress": { "type": "object", "propertyNames": { "type": "string" }, "additionalProperties": { "type": "object", "properties": { "watched": { "type": "number" }, "total": { "type": "number" } }, "required": [ "watched", "total" ] }, "description": "Map of title ID to episode watch progress" }, "page": { "type": "number", "description": "Current page number" }, "totalPages": { "type": "number", "description": "Total number of pages available" }, "totalResults": { "type": "number", "description": "Total number of results across all pages" } }, "required": [ "items", "hero", "userStatuses", "episodeProgress", "page", "totalPages", "totalResults" ], "description": "Trending titles with hero spotlight and user statuses" } } } }, "412": { "description": "412", "content": { "application/json": { "schema": { "oneOf": [ { "type": "object", "properties": { "defined": { "const": true }, "code": { "const": "PRECONDITION_FAILED" }, "status": { "const": 412 }, "message": { "type": "string", "default": "TMDB API key is not configured" }, "data": { "type": "object", "properties": { "code": { "const": "TMDB_NOT_CONFIGURED" } }, "required": [ "code" ] } }, "required": [ "defined", "code", "status", "message", "data" ] }, { "type": "object", "properties": { "defined": { "const": false }, "code": { "type": "string" }, "status": { "type": "number" }, "message": { "type": "string" }, "data": {} }, "required": [ "defined", "code", "status", "message" ] } ] } } } } }, "security": [ { "session": [] } ] } }, "/explore/popular": { "get": { "operationId": "explore.popular", "summary": "Get popular titles", "description": "Fetch currently popular movies or TV shows from TMDB with the user's tracking statuses.", "tags": [ "Explore" ], "parameters": [ { "name": "type", "in": "query", "required": true, "schema": { "enum": [ "movie", "tv" ], "type": "string", "description": "Media type filter" }, "allowEmptyValue": true, "allowReserved": true }, { "name": "page", "in": "query", "required": false, "schema": { "type": "integer", "minimum": 1, "maximum": 500, "default": 1, "description": "Page number (1-indexed)" }, "allowEmptyValue": true, "allowReserved": true } ], "responses": { "200": { "description": "Popular items with user statuses", "content": { "application/json": { "schema": { "type": "object", "properties": { "items": { "type": "array", "items": { "$ref": "#/components/schemas/BrowseItem" } }, "userStatuses": { "type": "object", "propertyNames": { "type": "string" }, "additionalProperties": { "enum": [ "in_watchlist", "watching", "caught_up", "completed" ], "type": "string" }, "description": "Map of title ID to the user's display status" }, "episodeProgress": { "type": "object", "propertyNames": { "type": "string" }, "additionalProperties": { "type": "object", "properties": { "watched": { "type": "number" }, "total": { "type": "number" } }, "required": [ "watched", "total" ] }, "description": "Map of title ID to episode watch progress" }, "page": { "type": "number", "description": "Current page number" }, "totalPages": { "type": "number", "description": "Total number of pages available" }, "totalResults": { "type": "number", "description": "Total number of results across all pages" } }, "required": [ "items", "userStatuses", "episodeProgress", "page", "totalPages", "totalResults" ], "description": "Browse results with user tracking statuses and episode progress" } } } }, "412": { "description": "412", "content": { "application/json": { "schema": { "oneOf": [ { "type": "object", "properties": { "defined": { "const": true }, "code": { "const": "PRECONDITION_FAILED" }, "status": { "const": 412 }, "message": { "type": "string", "default": "TMDB API key is not configured" }, "data": { "type": "object", "properties": { "code": { "const": "TMDB_NOT_CONFIGURED" } }, "required": [ "code" ] } }, "required": [ "defined", "code", "status", "message", "data" ] }, { "type": "object", "properties": { "defined": { "const": false }, "code": { "type": "string" }, "status": { "type": "number" }, "message": { "type": "string" }, "data": {} }, "required": [ "defined", "code", "status", "message" ] } ] } } } } }, "security": [ { "session": [] } ] } }, "/explore/genres": { "get": { "operationId": "explore.genres", "summary": "List genres", "description": "Fetch the list of TMDB genres for movies or TV shows. Used to populate genre filters for discovery.", "tags": [ "Explore" ], "parameters": [ { "name": "type", "in": "query", "required": true, "schema": { "enum": [ "movie", "tv" ], "type": "string", "description": "Media type filter" }, "allowEmptyValue": true, "allowReserved": true } ], "responses": { "200": { "description": "Genre ID/name pairs", "content": { "application/json": { "schema": { "type": "object", "properties": { "genres": { "type": "array", "items": { "type": "object", "properties": { "id": { "type": "number", "description": "TMDB genre ID" }, "name": { "type": "string", "description": "Genre display name" } }, "required": [ "id", "name" ] } } }, "required": [ "genres" ], "description": "Available genres for filtering" } } } }, "412": { "description": "412", "content": { "application/json": { "schema": { "oneOf": [ { "type": "object", "properties": { "defined": { "const": true }, "code": { "const": "PRECONDITION_FAILED" }, "status": { "const": 412 }, "message": { "type": "string", "default": "TMDB API key is not configured" }, "data": { "type": "object", "properties": { "code": { "const": "TMDB_NOT_CONFIGURED" } }, "required": [ "code" ] } }, "required": [ "defined", "code", "status", "message", "data" ] }, { "type": "object", "properties": { "defined": { "const": false }, "code": { "type": "string" }, "status": { "type": "number" }, "message": { "type": "string" }, "data": {} }, "required": [ "defined", "code", "status", "message" ] } ] } } } } }, "security": [ { "session": [] } ] } }, "/search": { "get": { "operationId": "search", "summary": "Search movies, TV shows, and people", "description": "Full-text search across movies, TV shows, and people via TMDB. Optionally filter by media type.", "tags": [ "Search" ], "parameters": [ { "name": "query", "in": "query", "required": true, "schema": { "type": "string", "minLength": 1, "maxLength": 200, "description": "Search query text" }, "allowEmptyValue": true, "allowReserved": true }, { "name": "type", "in": "query", "required": false, "schema": { "enum": [ "movie", "tv", "person" ], "type": "string", "description": "Optional type filter to narrow results" }, "allowEmptyValue": true, "allowReserved": true }, { "name": "page", "in": "query", "required": false, "schema": { "type": "integer", "minimum": 1, "maximum": 500, "default": 1, "description": "Page number (1-indexed)" }, "allowEmptyValue": true, "allowReserved": true } ], "responses": { "200": { "description": "Search results with metadata", "content": { "application/json": { "schema": { "type": "object", "properties": { "results": { "type": "array", "items": { "type": "object", "properties": { "id": { "type": "string", "description": "Internal title ID (present for movie/tv results, absent for people)" }, "tmdbId": { "type": "number", "description": "TMDB numeric ID" }, "type": { "enum": [ "movie", "tv", "person" ], "type": "string", "description": "Result type" }, "title": { "type": "string", "description": "Title or person name" }, "overview": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "description": "Plot summary or null for people" }, "posterPath": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "description": "Poster image path (movies/TV)" }, "profilePath": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "description": "Profile photo path (people)" }, "releaseDate": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "description": "Release date (ISO 8601)" }, "popularity": { "anyOf": [ { "type": "number" }, { "type": "null" } ], "description": "TMDB popularity score" }, "voteAverage": { "anyOf": [ { "type": "number" }, { "type": "null" } ], "description": "Average rating (0-10)" }, "knownForDepartment": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "description": "Primary department (people only)" }, "knownFor": { "anyOf": [ { "type": "array", "items": { "type": "string" } }, { "type": "null" } ], "description": "Notable works (people only, up to 3 titles)" } }, "required": [ "tmdbId", "type", "title", "overview", "posterPath", "profilePath", "releaseDate", "popularity", "voteAverage", "knownForDepartment", "knownFor" ], "description": "A search result (movie, TV show, or person)" } }, "page": { "type": "number", "description": "Current page number" }, "totalPages": { "type": "number", "description": "Total number of pages available" }, "totalResults": { "type": "number", "description": "Total number of results across all pages" } }, "required": [ "results", "page", "totalPages", "totalResults" ], "description": "Search results from TMDB" } } } }, "412": { "description": "412", "content": { "application/json": { "schema": { "oneOf": [ { "type": "object", "properties": { "defined": { "const": true }, "code": { "const": "PRECONDITION_FAILED" }, "status": { "const": 412 }, "message": { "type": "string", "default": "TMDB API key is not configured" }, "data": {} }, "required": [ "defined", "code", "status", "message" ] }, { "type": "object", "properties": { "defined": { "const": false }, "code": { "type": "string" }, "status": { "type": "number" }, "message": { "type": "string" }, "data": {} }, "required": [ "defined", "code", "status", "message" ] } ] } } } } }, "security": [ { "session": [] } ] } }, "/discover": { "get": { "operationId": "discover", "summary": "Discover titles by genre", "description": "Browse movies or TV shows filtered by genre, sorted by popularity. Returns user statuses and episode progress.", "tags": [ "Discover" ], "parameters": [ { "name": "type", "in": "query", "required": true, "schema": { "enum": [ "movie", "tv" ], "type": "string", "description": "Media type to discover" }, "allowEmptyValue": true, "allowReserved": true }, { "name": "genreId", "in": "query", "required": true, "schema": { "type": "integer", "minimum": -9007199254740991, "maximum": 9007199254740991, "description": "TMDB genre ID to filter by" }, "allowEmptyValue": true, "allowReserved": true }, { "name": "page", "in": "query", "required": false, "schema": { "type": "integer", "minimum": 1, "maximum": 500, "default": 1, "description": "Page number (1-indexed)" }, "allowEmptyValue": true, "allowReserved": true } ], "responses": { "200": { "description": "Filtered titles with user statuses", "content": { "application/json": { "schema": { "type": "object", "properties": { "items": { "type": "array", "items": { "$ref": "#/components/schemas/BrowseItem" } }, "userStatuses": { "type": "object", "propertyNames": { "type": "string" }, "additionalProperties": { "enum": [ "in_watchlist", "watching", "caught_up", "completed" ], "type": "string" }, "description": "Map of title ID to the user's display status" }, "episodeProgress": { "type": "object", "propertyNames": { "type": "string" }, "additionalProperties": { "type": "object", "properties": { "watched": { "type": "number" }, "total": { "type": "number" } }, "required": [ "watched", "total" ] }, "description": "Map of title ID to episode watch progress" }, "page": { "type": "number", "description": "Current page number" }, "totalPages": { "type": "number", "description": "Total number of pages available" }, "totalResults": { "type": "number", "description": "Total number of results across all pages" } }, "required": [ "items", "userStatuses", "episodeProgress", "page", "totalPages", "totalResults" ], "description": "Browse results with user tracking statuses and episode progress" } } } }, "412": { "description": "412", "content": { "application/json": { "schema": { "oneOf": [ { "type": "object", "properties": { "defined": { "const": true }, "code": { "const": "PRECONDITION_FAILED" }, "status": { "const": 412 }, "message": { "type": "string", "default": "TMDB API key is not configured" }, "data": { "type": "object", "properties": { "code": { "const": "TMDB_NOT_CONFIGURED" } }, "required": [ "code" ] } }, "required": [ "defined", "code", "status", "message", "data" ] }, { "type": "object", "properties": { "defined": { "const": false }, "code": { "type": "string" }, "status": { "type": "number" }, "message": { "type": "string" }, "data": {} }, "required": [ "defined", "code", "status", "message" ] } ] } } } } }, "security": [ { "session": [] } ] } }, "/system/public-info": { "get": { "operationId": "system.publicInfo", "summary": "Get public instance info", "description": "Fetch public information about this Sofa instance. Does not require authentication. Used by the login screen to display instance details.", "tags": [ "System" ], "responses": { "200": { "description": "Instance configuration and status", "content": { "application/json": { "schema": { "type": "object", "properties": { "instanceId": { "type": "string", "description": "Unique instance identifier" }, "tmdbConfigured": { "type": "boolean", "description": "Whether TMDB API is configured" }, "userCount": { "type": "number", "description": "Number of registered users" }, "registrationOpen": { "type": "boolean", "description": "Whether new user registration is open" }, "posterUrls": { "type": "array", "items": { "type": "string" }, "description": "Poster image URLs for the login screen collage" } }, "required": [ "instanceId", "tmdbConfigured", "userCount", "registrationOpen", "posterUrls" ], "description": "Public instance information shown on the login/setup screen" } } } } } } }, "/system/auth-config": { "get": { "operationId": "system.authConfig", "summary": "Get authentication config", "description": "Fetch the authentication configuration including OIDC availability, password login status, and registration state. Does not require authentication.", "tags": [ "System" ], "responses": { "200": { "description": "Authentication provider configuration", "content": { "application/json": { "schema": { "type": "object", "properties": { "oidcEnabled": { "type": "boolean", "description": "Whether OIDC/SSO login is available" }, "oidcProviderName": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "description": "Display name of the OIDC provider (e.g. Authelia, Keycloak)" }, "passwordLoginDisabled": { "type": "boolean", "description": "Whether password-based login is disabled" }, "registrationOpen": { "type": "boolean", "description": "Whether new user registration is open" }, "userCount": { "type": "number", "description": "Number of registered users" } }, "required": [ "oidcEnabled", "oidcProviderName", "passwordLoginDisabled", "registrationOpen", "userCount" ], "description": "Authentication provider configuration" } } } } } } }, "/system/status": { "get": { "operationId": "system.status", "summary": "Get internal system config", "description": "Returns internal configuration such as the public API URL. Requires authentication.", "tags": [ "System" ], "responses": { "200": { "description": "Internal system configuration", "content": { "application/json": { "schema": { "type": "object", "properties": { "publicApiUrl": { "type": "string", "description": "Base URL of the centralized public API" } }, "required": [ "publicApiUrl" ], "description": "Internal system configuration for authenticated clients" } } } } }, "security": [ { "session": [] } ] } }, "/integrations": { "get": { "operationId": "integrations.list", "summary": "List integrations", "description": "Fetch all configured media server integrations for the current user, including recent webhook/sync events for each.", "tags": [ "Integrations" ], "responses": { "200": { "description": "Integrations with their recent events", "content": { "application/json": { "schema": { "type": "object", "properties": { "integrations": { "type": "array", "items": { "type": "object", "properties": { "id": { "type": "string", "description": "Integration ID" }, "provider": { "type": "string", "description": "Provider name (plex, jellyfin, etc.)" }, "type": { "enum": [ "webhook", "list" ], "type": "string", "description": "Integration type: webhook (Plex/Jellyfin/Emby) or list (Sonarr/Radarr)" }, "token": { "type": "string", "description": "Webhook authentication token" }, "enabled": { "type": "boolean", "description": "Whether the integration is active" }, "lastEventAt": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "description": "Last received event timestamp (ISO 8601)" }, "createdAt": { "type": "string", "description": "When the integration was created (ISO 8601)" }, "recentEvents": { "type": "array", "items": { "$ref": "#/components/schemas/IntegrationEvent" }, "description": "Last 10 events for this integration" } }, "required": [ "id", "provider", "type", "token", "enabled", "lastEventAt", "createdAt", "recentEvents" ] } } }, "required": [ "integrations" ], "description": "All integrations with their recent events" } } } } }, "security": [ { "session": [] } ] }, "post": { "operationId": "integrations.create", "summary": "Create or update integration", "description": "Create a new media server integration or update an existing one. Generates a unique webhook token for the provider.", "tags": [ "Integrations" ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "properties": { "provider": { "enum": [ "plex", "jellyfin", "emby", "sonarr", "radarr" ], "type": "string", "description": "Media server provider to integrate" }, "enabled": { "type": "boolean", "description": "Whether the integration starts enabled (default: true)" } }, "required": [ "provider" ], "description": "Create a new media server integration" } } } }, "responses": { "200": { "description": "Created or updated integration with token", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Integration" } } } } }, "security": [ { "session": [] } ] } }, "/integrations/{provider}": { "delete": { "operationId": "integrations.delete", "summary": "Delete integration", "description": "Remove a media server integration and all its event history.", "tags": [ "Integrations" ], "parameters": [ { "name": "provider", "in": "path", "required": true, "schema": { "enum": [ "plex", "jellyfin", "emby", "sonarr", "radarr" ], "type": "string", "description": "Media server provider type" } } ], "requestBody": { "required": false, "content": { "application/json": { "schema": { "type": "object" } } } }, "responses": { "200": { "description": "OK" } }, "security": [ { "session": [] } ] } }, "/integrations/{provider}/regenerate-token": { "post": { "operationId": "integrations.regenerateToken", "summary": "Regenerate webhook token", "description": "Generate a new webhook token for an integration. The old token is immediately invalidated.", "tags": [ "Integrations" ], "parameters": [ { "name": "provider", "in": "path", "required": true, "schema": { "enum": [ "plex", "jellyfin", "emby", "sonarr", "radarr" ], "type": "string", "description": "Media server provider type" } } ], "requestBody": { "required": false, "content": { "application/json": { "schema": { "type": "object" } } } }, "responses": { "200": { "description": "Integration with new token", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Integration" } } } }, "404": { "description": "404", "content": { "application/json": { "schema": { "oneOf": [ { "type": "object", "properties": { "defined": { "const": true }, "code": { "const": "NOT_FOUND" }, "status": { "const": 404 }, "message": { "type": "string", "default": "Integration not found" }, "data": { "type": "object", "properties": { "code": { "const": "INTEGRATION_NOT_FOUND" } }, "required": [ "code" ] } }, "required": [ "defined", "code", "status", "message", "data" ] }, { "type": "object", "properties": { "defined": { "const": false }, "code": { "type": "string" }, "status": { "type": "number" }, "message": { "type": "string" }, "data": {} }, "required": [ "defined", "code", "status", "message" ] } ] } } } } }, "security": [ { "session": [] } ] } }, "/admin/backups": { "get": { "operationId": "admin.backups.list", "summary": "List backups", "description": "Fetch all database backups with their sizes and creation times.", "tags": [ "Admin" ], "responses": { "200": { "description": "Available backups", "content": { "application/json": { "schema": { "type": "object", "properties": { "backups": { "type": "array", "items": { "$ref": "#/components/schemas/Backup" } } }, "required": [ "backups" ], "description": "All available database backups" } } } } }, "security": [ { "session": [] } ] }, "post": { "operationId": "admin.backups.create", "summary": "Create backup", "description": "Create a new manual database backup. The backup is tagged as a manual backup.", "tags": [ "Admin" ], "responses": { "200": { "description": "Created backup metadata", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Backup" } } } } }, "security": [ { "session": [] } ] } }, "/admin/backups/{filename}": { "delete": { "operationId": "admin.backups.delete", "summary": "Delete backup", "description": "Permanently delete a database backup file by filename.", "tags": [ "Admin" ], "parameters": [ { "name": "filename", "in": "path", "required": true, "schema": { "type": "string", "minLength": 1, "description": "Backup filename" } } ], "requestBody": { "required": false, "content": { "application/json": { "schema": { "type": "object" } } } }, "responses": { "200": { "description": "OK" }, "400": { "description": "400", "content": { "application/json": { "schema": { "oneOf": [ { "type": "object", "properties": { "defined": { "const": true }, "code": { "const": "BAD_REQUEST" }, "status": { "const": 400 }, "message": { "type": "string", "default": "Failed to delete backup" }, "data": { "type": "object", "properties": { "code": { "const": "BACKUP_DELETE_FAILED" } }, "required": [ "code" ] } }, "required": [ "defined", "code", "status", "message", "data" ] }, { "type": "object", "properties": { "defined": { "const": false }, "code": { "type": "string" }, "status": { "type": "number" }, "message": { "type": "string" }, "data": {} }, "required": [ "defined", "code", "status", "message" ] } ] } } } }, "404": { "description": "404", "content": { "application/json": { "schema": { "oneOf": [ { "type": "object", "properties": { "defined": { "const": true }, "code": { "const": "NOT_FOUND" }, "status": { "const": 404 }, "message": { "type": "string", "default": "Backup not found" }, "data": { "type": "object", "properties": { "code": { "const": "BACKUP_NOT_FOUND" } }, "required": [ "code" ] } }, "required": [ "defined", "code", "status", "message", "data" ] }, { "type": "object", "properties": { "defined": { "const": false }, "code": { "type": "string" }, "status": { "type": "number" }, "message": { "type": "string" }, "data": {} }, "required": [ "defined", "code", "status", "message" ] } ] } } } } }, "security": [ { "session": [] } ] } }, "/admin/backups/restore": { "post": { "operationId": "admin.backups.restore", "summary": "Restore from backup", "description": "Upload a database backup file and restore it. A pre-restore backup is automatically created before overwriting the current database.", "tags": [ "Admin" ], "requestBody": { "required": true, "content": { "multipart/form-data": { "schema": { "type": "object", "properties": { "file": { "type": "string", "format": "binary" } }, "required": [ "file" ] } } } }, "responses": { "200": { "description": "OK" }, "400": { "description": "400", "content": { "application/json": { "schema": { "oneOf": [ { "type": "object", "properties": { "defined": { "const": true }, "code": { "const": "BAD_REQUEST" }, "status": { "const": 400 }, "message": { "type": "string", "default": "Backup restoration failed" }, "data": { "type": "object", "properties": { "code": { "const": "BACKUP_RESTORE_FAILED" } }, "required": [ "code" ] } }, "required": [ "defined", "code", "status", "message", "data" ] }, { "type": "object", "properties": { "defined": { "const": false }, "code": { "type": "string" }, "status": { "type": "number" }, "message": { "type": "string" }, "data": {} }, "required": [ "defined", "code", "status", "message" ] } ] } } } } }, "security": [ { "session": [] } ] } }, "/admin/backups/schedule": { "get": { "operationId": "admin.backups.schedule", "summary": "Get backup schedule", "description": "Fetch the current automated backup schedule configuration.", "tags": [ "Admin" ], "responses": { "200": { "description": "Backup schedule settings", "content": { "application/json": { "schema": { "type": "object", "properties": { "enabled": { "type": "boolean", "description": "Whether scheduled backups are enabled" }, "maxRetention": { "type": "number", "description": "Maximum backups to keep (0 = unlimited)" }, "frequency": { "enum": [ "6h", "12h", "1d", "7d" ], "type": "string", "description": "Backup interval" }, "time": { "type": "string", "description": "Scheduled time (HH:MM, 24-hour format)" }, "dayOfWeek": { "type": "number", "description": "Day of week for weekly backups (0 = Sunday)" } }, "required": [ "enabled", "maxRetention", "frequency", "time", "dayOfWeek" ], "description": "Automated backup schedule configuration" } } } } }, "security": [ { "session": [] } ] }, "put": { "operationId": "admin.backups.updateSchedule", "summary": "Update backup schedule", "description": "Update the automated backup schedule. Only provided fields are changed; omitted fields keep their current values.", "tags": [ "Admin" ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "properties": { "enabled": { "type": "boolean", "description": "Enable or disable scheduled backups" }, "frequency": { "enum": [ "6h", "12h", "1d", "7d" ], "type": "string", "description": "Backup interval" }, "time": { "type": "string", "pattern": "^\\d{2}:\\d{2}$", "description": "Time of day to run backups (HH:MM, 24-hour format)" }, "dayOfWeek": { "type": "integer", "minimum": 0, "maximum": 6, "description": "Day of week for weekly backups (0 = Sunday, 6 = Saturday)" }, "maxRetention": { "type": "integer", "minimum": -9007199254740991, "maximum": 9007199254740991, "description": "Maximum number of backups to keep (0 = unlimited, 1-30 otherwise)" } }, "description": "Partial update to the automated backup schedule" } } } }, "responses": { "200": { "description": "OK" } }, "security": [ { "session": [] } ] } }, "/admin/registration": { "get": { "operationId": "admin.registration", "summary": "Get registration status", "description": "Check whether new user registration is currently open or closed.", "tags": [ "Admin" ], "responses": { "200": { "description": "Registration open/closed state", "content": { "application/json": { "schema": { "type": "object", "properties": { "open": { "type": "boolean", "description": "Whether new user registration is open" } }, "required": [ "open" ], "description": "User registration status" } } } } }, "security": [ { "session": [] } ] }, "put": { "operationId": "admin.toggleRegistration", "summary": "Toggle registration", "description": "Open or close new user registration.", "tags": [ "Admin" ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "properties": { "open": { "type": "boolean", "description": "Whether new user registration is allowed" } }, "required": [ "open" ] } } } }, "responses": { "200": { "description": "OK" } }, "security": [ { "session": [] } ] } }, "/admin/update-check": { "get": { "operationId": "admin.updateCheck", "summary": "Get update check status", "description": "Fetch whether automatic update checks are enabled, and the latest cached check result if available.", "tags": [ "Admin" ], "responses": { "200": { "description": "Update check configuration and latest result", "content": { "application/json": { "schema": { "type": "object", "properties": { "enabled": { "type": "boolean", "description": "Whether automatic update checks are enabled" }, "updateCheck": { "anyOf": [ { "type": "object", "properties": { "updateAvailable": { "type": "boolean", "description": "Whether a newer version is available" }, "currentVersion": { "type": "string", "description": "Currently running version" }, "latestVersion": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "description": "Latest available version, or null if check failed" }, "releaseUrl": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "description": "URL to the latest release page" }, "lastCheckedAt": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "description": "When the last check was performed (ISO 8601)" } }, "required": [ "updateAvailable", "currentVersion", "latestVersion", "releaseUrl", "lastCheckedAt" ], "description": "Result of an update availability check" }, { "type": "null" } ], "description": "Latest check result, or null if checks are disabled or never ran" } }, "required": [ "enabled", "updateCheck" ], "description": "Update check configuration and latest result" } } } } }, "security": [ { "session": [] } ] }, "put": { "operationId": "admin.toggleUpdateCheck", "summary": "Toggle update checks", "description": "Enable or disable automatic update checks against the public API.", "tags": [ "Admin" ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "properties": { "enabled": { "type": "boolean", "description": "Whether automatic update checks are enabled" } }, "required": [ "enabled" ] } } } }, "responses": { "200": { "description": "OK" } }, "security": [ { "session": [] } ] } }, "/admin/telemetry": { "get": { "operationId": "admin.telemetry", "summary": "Get telemetry status", "description": "Fetch whether anonymous telemetry is enabled and when the last report was sent.", "tags": [ "Admin" ], "responses": { "200": { "description": "Telemetry configuration and last report time", "content": { "application/json": { "schema": { "type": "object", "properties": { "enabled": { "type": "boolean", "description": "Whether telemetry is currently enabled" }, "lastReportedAt": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "description": "ISO 8601 timestamp of the last telemetry report, or null if never sent" } }, "required": [ "enabled", "lastReportedAt" ], "description": "Telemetry configuration and last report time" } } } } }, "security": [ { "session": [] } ] }, "put": { "operationId": "admin.toggleTelemetry", "summary": "Toggle telemetry", "description": "Enable or disable anonymous telemetry reporting.", "tags": [ "Admin" ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "properties": { "enabled": { "type": "boolean", "description": "Whether anonymous telemetry reporting is enabled" } }, "required": [ "enabled" ] } } } }, "responses": { "200": { "description": "OK" } }, "security": [ { "session": [] } ] } }, "/admin/jobs/trigger": { "post": { "operationId": "admin.triggerJob", "summary": "Trigger cron job", "description": "Manually trigger a background cron job by name. The job runs asynchronously.", "tags": [ "Admin" ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "properties": { "name": { "enum": [ "scheduledBackup", "nightlyRefreshLibrary", "refreshAvailability", "refreshRecommendations", "refreshTvChildren", "cacheImages", "refreshCredits", "updateCheck", "telemetryReport" ], "type": "string", "description": "Cron job to trigger" } }, "required": [ "name" ], "description": "Specify which background job to trigger manually" } } } }, "responses": { "200": { "description": "OK", "content": { "application/json": { "schema": { "type": "object", "properties": { "ok": { "const": true, "description": "Always true on success" } }, "required": [ "ok" ] } } } }, "404": { "description": "404", "content": { "application/json": { "schema": { "oneOf": [ { "type": "object", "properties": { "defined": { "const": true }, "code": { "const": "NOT_FOUND" }, "status": { "const": 404 }, "message": { "type": "string", "default": "Job not found" }, "data": { "type": "object", "properties": { "code": { "const": "JOB_NOT_FOUND" } }, "required": [ "code" ] } }, "required": [ "defined", "code", "status", "message", "data" ] }, { "type": "object", "properties": { "defined": { "const": false }, "code": { "type": "string" }, "status": { "type": "number" }, "message": { "type": "string" }, "data": {} }, "required": [ "defined", "code", "status", "message" ] } ] } } } } }, "security": [ { "session": [] } ] } }, "/admin/cache/purge-metadata": { "post": { "operationId": "admin.purgeMetadataCache", "summary": "Purge metadata cache", "description": "Delete un-enriched stub titles not in any user's library and clean up orphaned person records.", "tags": [ "Admin" ], "responses": { "200": { "description": "Counts of deleted titles and persons", "content": { "application/json": { "schema": { "type": "object", "properties": { "deletedTitles": { "type": "number", "description": "Number of un-enriched stub titles deleted" }, "deletedPersons": { "type": "number", "description": "Number of orphaned person records deleted" } }, "required": [ "deletedTitles", "deletedPersons" ], "description": "Result of purging un-enriched metadata from the database" } } } } }, "security": [ { "session": [] } ] } }, "/admin/cache/purge-images": { "post": { "operationId": "admin.purgeImageCache", "summary": "Purge image cache", "description": "Delete all cached TMDB images from disk. Images will be re-downloaded on demand.", "tags": [ "Admin" ], "responses": { "200": { "description": "Count of deleted files and bytes freed", "content": { "application/json": { "schema": { "type": "object", "properties": { "deletedFiles": { "type": "number", "description": "Number of image files deleted from disk" }, "freedBytes": { "type": "number", "description": "Total bytes freed from disk" } }, "required": [ "deletedFiles", "freedBytes" ], "description": "Result of purging the image cache from disk" } } } } }, "security": [ { "session": [] } ] } }, "/admin/system-health": { "get": { "operationId": "admin.systemHealth", "summary": "Get system health report", "description": "Comprehensive health check covering database, TMDB connectivity, cron jobs, image cache, backups, and environment.", "tags": [ "Admin" ], "responses": { "200": { "description": "Full system health report", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SystemHealth" } } } } }, "security": [ { "session": [] } ] } }, "/account/name": { "put": { "operationId": "account.updateName", "summary": "Update display name", "description": "Change the current user's display name.", "tags": [ "Account" ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "properties": { "name": { "type": "string", "minLength": 1, "maxLength": 100, "description": "New display name" } }, "required": [ "name" ] } } } }, "responses": { "200": { "description": "OK" } }, "security": [ { "session": [] } ] } }, "/account/avatar": { "post": { "operationId": "account.uploadAvatar", "summary": "Upload avatar", "description": "Upload a new profile avatar image. Accepts JPEG, PNG, WebP, or GIF up to 2 MB.", "tags": [ "Account" ], "requestBody": { "required": true, "content": { "multipart/form-data": { "schema": { "type": "object", "properties": { "file": { "type": "string", "format": "binary" } }, "required": [ "file" ] } } } }, "responses": { "200": { "description": "URL of the uploaded avatar image", "content": { "application/json": { "schema": { "type": "object", "properties": { "imageUrl": { "type": "string", "description": "URL of the uploaded avatar image" } }, "required": [ "imageUrl" ] } } } } }, "security": [ { "session": [] } ] }, "delete": { "operationId": "account.removeAvatar", "summary": "Remove avatar", "description": "Delete the current user's profile avatar, reverting to the default.", "tags": [ "Account" ], "responses": { "200": { "description": "OK" } }, "security": [ { "session": [] } ] } }, "/imports/parse-file": { "post": { "operationId": "imports.parseFile", "summary": "Parse import file", "description": "Upload and parse an export file from Trakt, Simkl, Letterboxd, or Sofa. Returns a preview of items found without importing anything.", "tags": [ "Imports" ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "properties": { "source": { "enum": [ "trakt", "simkl", "letterboxd", "sofa" ], "type": "string", "description": "Service to import from" }, "file": { "type": "string", "contentMediaType": "*/*" } }, "required": [ "source", "file" ] } }, "multipart/form-data": { "schema": { "type": "object", "properties": { "source": { "enum": [ "trakt", "simkl", "letterboxd", "sofa" ], "type": "string", "description": "Service to import from" }, "file": { "type": "string", "contentMediaType": "*/*" } }, "required": [ "source", "file" ] } } } }, "responses": { "200": { "description": "Preview of importable items with counts", "content": { "application/json": { "schema": { "type": "object", "properties": { "data": { "type": "object", "properties": { "source": { "enum": [ "trakt", "simkl", "letterboxd", "sofa" ], "type": "string", "description": "Service to import from" }, "movies": { "type": "array", "maxItems": 50000, "items": { "type": "object", "properties": { "tmdbId": { "type": "number" }, "imdbId": { "type": "string" }, "title": { "type": "string" }, "year": { "type": "number" }, "watchedAt": { "type": "string", "format": "date-time", "description": "ISO 8601 timestamp" }, "watchedOn": { "type": "string", "format": "date", "description": "YYYY-MM-DD date-only" } }, "required": [ "title" ] } }, "episodes": { "type": "array", "maxItems": 50000, "items": { "type": "object", "properties": { "showTmdbId": { "type": "number" }, "imdbId": { "type": "string" }, "tvdbId": { "type": "number" }, "showTitle": { "type": "string" }, "year": { "type": "number" }, "seasonNumber": { "type": "integer", "minimum": 0, "maximum": 9007199254740991 }, "episodeNumber": { "type": "integer", "minimum": 1, "maximum": 9007199254740991 }, "watchedAt": { "type": "string", "format": "date-time" }, "watchedOn": { "type": "string", "format": "date" } }, "required": [ "seasonNumber", "episodeNumber" ] } }, "watchlist": { "type": "array", "maxItems": 50000, "items": { "type": "object", "properties": { "tmdbId": { "type": "number" }, "imdbId": { "type": "string" }, "tvdbId": { "type": "number" }, "title": { "type": "string" }, "year": { "type": "number" }, "type": { "enum": [ "movie", "tv" ], "type": "string" }, "status": { "enum": [ "watchlist", "in_progress", "completed" ], "type": "string", "description": "Library status (default: watchlist)" }, "addedAt": { "type": "string", "format": "date-time", "description": "When the item was added to library" } }, "required": [ "title", "type" ] } }, "ratings": { "type": "array", "maxItems": 50000, "items": { "type": "object", "properties": { "tmdbId": { "type": "number" }, "imdbId": { "type": "string" }, "tvdbId": { "type": "number" }, "title": { "type": "string" }, "year": { "type": "number" }, "type": { "enum": [ "movie", "tv" ], "type": "string" }, "rating": { "type": "integer", "minimum": 1, "maximum": 5, "description": "Sofa 1-5 star rating" }, "ratedAt": { "type": "string", "format": "date-time" }, "ratedOn": { "type": "string", "format": "date" } }, "required": [ "title", "type", "rating" ] } } }, "required": [ "source", "movies", "episodes", "watchlist", "ratings" ] }, "warnings": { "type": "array", "items": { "type": "string" } }, "stats": { "type": "object", "properties": { "movies": { "type": "number" }, "episodes": { "type": "number" }, "watchlist": { "type": "number" }, "ratings": { "type": "number" } }, "required": [ "movies", "episodes", "watchlist", "ratings" ] }, "diagnostics": { "type": "object", "properties": { "unresolved": { "type": "number" }, "unsupported": { "type": "number" } }, "required": [ "unresolved", "unsupported" ] }, "blockingErrors": { "type": "array", "items": { "type": "string" } } }, "required": [ "data", "warnings", "stats" ] } } } }, "400": { "description": "400", "content": { "application/json": { "schema": { "oneOf": [ { "type": "object", "properties": { "defined": { "const": true }, "code": { "const": "BAD_REQUEST" }, "status": { "const": 400 }, "message": { "type": "string", "default": "Invalid import file" }, "data": { "type": "object", "properties": { "code": { "const": "IMPORT_INVALID_FILE" } }, "required": [ "code" ] } }, "required": [ "defined", "code", "status", "message", "data" ] }, { "type": "object", "properties": { "defined": { "const": false }, "code": { "type": "string" }, "status": { "type": "number" }, "message": { "type": "string" }, "data": {} }, "required": [ "defined", "code", "status", "message" ] } ] } } } } }, "security": [ { "session": [] } ] } }, "/imports/parse-payload": { "post": { "operationId": "imports.parsePayload", "summary": "Parse raw OAuth import data", "description": "Accept raw API responses from the OAuth proxy, parse them into normalized import format, and return a preview with item counts.", "tags": [ "Imports" ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "properties": { "source": { "enum": [ "trakt", "simkl" ], "type": "string", "description": "OAuth import provider" }, "rawPayload": { "description": "Raw aggregated API response from the OAuth proxy" } }, "required": [ "source" ] } } } }, "responses": { "200": { "description": "Preview of importable items with counts", "content": { "application/json": { "schema": { "type": "object", "properties": { "data": { "type": "object", "properties": { "source": { "enum": [ "trakt", "simkl", "letterboxd", "sofa" ], "type": "string", "description": "Service to import from" }, "movies": { "type": "array", "maxItems": 50000, "items": { "type": "object", "properties": { "tmdbId": { "type": "number" }, "imdbId": { "type": "string" }, "title": { "type": "string" }, "year": { "type": "number" }, "watchedAt": { "type": "string", "format": "date-time", "description": "ISO 8601 timestamp" }, "watchedOn": { "type": "string", "format": "date", "description": "YYYY-MM-DD date-only" } }, "required": [ "title" ] } }, "episodes": { "type": "array", "maxItems": 50000, "items": { "type": "object", "properties": { "showTmdbId": { "type": "number" }, "imdbId": { "type": "string" }, "tvdbId": { "type": "number" }, "showTitle": { "type": "string" }, "year": { "type": "number" }, "seasonNumber": { "type": "integer", "minimum": 0, "maximum": 9007199254740991 }, "episodeNumber": { "type": "integer", "minimum": 1, "maximum": 9007199254740991 }, "watchedAt": { "type": "string", "format": "date-time" }, "watchedOn": { "type": "string", "format": "date" } }, "required": [ "seasonNumber", "episodeNumber" ] } }, "watchlist": { "type": "array", "maxItems": 50000, "items": { "type": "object", "properties": { "tmdbId": { "type": "number" }, "imdbId": { "type": "string" }, "tvdbId": { "type": "number" }, "title": { "type": "string" }, "year": { "type": "number" }, "type": { "enum": [ "movie", "tv" ], "type": "string" }, "status": { "enum": [ "watchlist", "in_progress", "completed" ], "type": "string", "description": "Library status (default: watchlist)" }, "addedAt": { "type": "string", "format": "date-time", "description": "When the item was added to library" } }, "required": [ "title", "type" ] } }, "ratings": { "type": "array", "maxItems": 50000, "items": { "type": "object", "properties": { "tmdbId": { "type": "number" }, "imdbId": { "type": "string" }, "tvdbId": { "type": "number" }, "title": { "type": "string" }, "year": { "type": "number" }, "type": { "enum": [ "movie", "tv" ], "type": "string" }, "rating": { "type": "integer", "minimum": 1, "maximum": 5, "description": "Sofa 1-5 star rating" }, "ratedAt": { "type": "string", "format": "date-time" }, "ratedOn": { "type": "string", "format": "date" } }, "required": [ "title", "type", "rating" ] } } }, "required": [ "source", "movies", "episodes", "watchlist", "ratings" ] }, "warnings": { "type": "array", "items": { "type": "string" } }, "stats": { "type": "object", "properties": { "movies": { "type": "number" }, "episodes": { "type": "number" }, "watchlist": { "type": "number" }, "ratings": { "type": "number" } }, "required": [ "movies", "episodes", "watchlist", "ratings" ] }, "diagnostics": { "type": "object", "properties": { "unresolved": { "type": "number" }, "unsupported": { "type": "number" } }, "required": [ "unresolved", "unsupported" ] }, "blockingErrors": { "type": "array", "items": { "type": "string" } } }, "required": [ "data", "warnings", "stats" ] } } } } }, "security": [ { "session": [] } ] } }, "/imports/jobs": { "post": { "operationId": "imports.createJob", "summary": "Create import job", "description": "Create and start a background import job from previously parsed data.", "tags": [ "Imports" ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "properties": { "data": { "type": "object", "properties": { "source": { "enum": [ "trakt", "simkl", "letterboxd", "sofa" ], "type": "string", "description": "Service to import from" }, "movies": { "type": "array", "maxItems": 50000, "items": { "type": "object", "properties": { "tmdbId": { "type": "number" }, "imdbId": { "type": "string" }, "title": { "type": "string" }, "year": { "type": "number" }, "watchedAt": { "type": "string", "format": "date-time", "description": "ISO 8601 timestamp" }, "watchedOn": { "type": "string", "format": "date", "description": "YYYY-MM-DD date-only" } }, "required": [ "title" ] } }, "episodes": { "type": "array", "maxItems": 50000, "items": { "type": "object", "properties": { "showTmdbId": { "type": "number" }, "imdbId": { "type": "string" }, "tvdbId": { "type": "number" }, "showTitle": { "type": "string" }, "year": { "type": "number" }, "seasonNumber": { "type": "integer", "minimum": 0, "maximum": 9007199254740991 }, "episodeNumber": { "type": "integer", "minimum": 1, "maximum": 9007199254740991 }, "watchedAt": { "type": "string", "format": "date-time" }, "watchedOn": { "type": "string", "format": "date" } }, "required": [ "seasonNumber", "episodeNumber" ] } }, "watchlist": { "type": "array", "maxItems": 50000, "items": { "type": "object", "properties": { "tmdbId": { "type": "number" }, "imdbId": { "type": "string" }, "tvdbId": { "type": "number" }, "title": { "type": "string" }, "year": { "type": "number" }, "type": { "enum": [ "movie", "tv" ], "type": "string" }, "status": { "enum": [ "watchlist", "in_progress", "completed" ], "type": "string", "description": "Library status (default: watchlist)" }, "addedAt": { "type": "string", "format": "date-time", "description": "When the item was added to library" } }, "required": [ "title", "type" ] } }, "ratings": { "type": "array", "maxItems": 50000, "items": { "type": "object", "properties": { "tmdbId": { "type": "number" }, "imdbId": { "type": "string" }, "tvdbId": { "type": "number" }, "title": { "type": "string" }, "year": { "type": "number" }, "type": { "enum": [ "movie", "tv" ], "type": "string" }, "rating": { "type": "integer", "minimum": 1, "maximum": 5, "description": "Sofa 1-5 star rating" }, "ratedAt": { "type": "string", "format": "date-time" }, "ratedOn": { "type": "string", "format": "date" } }, "required": [ "title", "type", "rating" ] } } }, "required": [ "source", "movies", "episodes", "watchlist", "ratings" ] }, "options": { "type": "object", "properties": { "importWatches": { "type": "boolean", "description": "Import movie and episode watch history" }, "importWatchlist": { "type": "boolean", "description": "Import watchlist items" }, "importRatings": { "type": "boolean", "description": "Import ratings" } }, "required": [ "importWatches", "importWatchlist", "importRatings" ] } }, "required": [ "data", "options" ] } } } }, "responses": { "200": { "description": "Created import job", "content": { "application/json": { "schema": { "type": "object", "properties": { "id": { "type": "string" }, "source": { "enum": [ "trakt", "simkl", "letterboxd", "sofa" ], "type": "string", "description": "Service to import from" }, "status": { "enum": [ "pending", "running", "success", "error", "cancelled" ], "type": "string" }, "totalItems": { "type": "number" }, "processedItems": { "type": "number" }, "importedCount": { "type": "number" }, "skippedCount": { "type": "number" }, "failedCount": { "type": "number" }, "currentMessage": { "anyOf": [ { "type": "string" }, { "type": "null" } ] }, "errors": { "type": "array", "items": { "type": "string" } }, "warnings": { "type": "array", "items": { "type": "string" } }, "createdAt": { "type": "string" }, "startedAt": { "anyOf": [ { "type": "string" }, { "type": "null" } ] }, "finishedAt": { "anyOf": [ { "type": "string" }, { "type": "null" } ] } }, "required": [ "id", "source", "status", "totalItems", "processedItems", "importedCount", "skippedCount", "failedCount", "currentMessage", "errors", "warnings", "createdAt", "startedAt", "finishedAt" ] } } } }, "400": { "description": "400", "content": { "application/json": { "schema": { "oneOf": [ { "type": "object", "properties": { "defined": { "const": true }, "code": { "const": "BAD_REQUEST" }, "status": { "const": 400 }, "message": { "type": "string", "default": "Import payload too large" }, "data": { "type": "object", "properties": { "code": { "const": "IMPORT_PAYLOAD_TOO_LARGE" } }, "required": [ "code" ] } }, "required": [ "defined", "code", "status", "message", "data" ] }, { "type": "object", "properties": { "defined": { "const": false }, "code": { "type": "string" }, "status": { "type": "number" }, "message": { "type": "string" }, "data": {} }, "required": [ "defined", "code", "status", "message" ] } ] } } } }, "409": { "description": "409", "content": { "application/json": { "schema": { "oneOf": [ { "type": "object", "properties": { "defined": { "const": true }, "code": { "const": "CONFLICT" }, "status": { "const": 409 }, "message": { "type": "string", "default": "An import is already in progress" }, "data": { "type": "object", "properties": { "code": { "const": "IMPORT_ALREADY_RUNNING" } }, "required": [ "code" ] } }, "required": [ "defined", "code", "status", "message", "data" ] }, { "type": "object", "properties": { "defined": { "const": false }, "code": { "type": "string" }, "status": { "type": "number" }, "message": { "type": "string" }, "data": {} }, "required": [ "defined", "code", "status", "message" ] } ] } } } } }, "security": [ { "session": [] } ] } }, "/imports/jobs/{id}": { "get": { "operationId": "imports.getJob", "summary": "Get import job status", "description": "Get the current status and progress of an import job.", "tags": [ "Imports" ], "parameters": [ { "name": "id", "in": "path", "required": true, "schema": { "type": "string", "minLength": 1, "description": "Internal UUIDv7 identifier" } } ], "responses": { "200": { "description": "Current job state", "content": { "application/json": { "schema": { "type": "object", "properties": { "id": { "type": "string" }, "source": { "enum": [ "trakt", "simkl", "letterboxd", "sofa" ], "type": "string", "description": "Service to import from" }, "status": { "enum": [ "pending", "running", "success", "error", "cancelled" ], "type": "string" }, "totalItems": { "type": "number" }, "processedItems": { "type": "number" }, "importedCount": { "type": "number" }, "skippedCount": { "type": "number" }, "failedCount": { "type": "number" }, "currentMessage": { "anyOf": [ { "type": "string" }, { "type": "null" } ] }, "errors": { "type": "array", "items": { "type": "string" } }, "warnings": { "type": "array", "items": { "type": "string" } }, "createdAt": { "type": "string" }, "startedAt": { "anyOf": [ { "type": "string" }, { "type": "null" } ] }, "finishedAt": { "anyOf": [ { "type": "string" }, { "type": "null" } ] } }, "required": [ "id", "source", "status", "totalItems", "processedItems", "importedCount", "skippedCount", "failedCount", "currentMessage", "errors", "warnings", "createdAt", "startedAt", "finishedAt" ] } } } } }, "security": [ { "session": [] } ] } }, "/imports/jobs/{id}/cancel": { "post": { "operationId": "imports.cancelJob", "summary": "Cancel import job", "description": "Cancel a pending or running import job. Already-imported items are not rolled back.", "tags": [ "Imports" ], "parameters": [ { "name": "id", "in": "path", "required": true, "schema": { "type": "string", "minLength": 1, "description": "Internal UUIDv7 identifier" } } ], "requestBody": { "required": false, "content": { "application/json": { "schema": { "type": "object" } } } }, "responses": { "200": { "description": "Updated job state", "content": { "application/json": { "schema": { "type": "object", "properties": { "id": { "type": "string" }, "source": { "enum": [ "trakt", "simkl", "letterboxd", "sofa" ], "type": "string", "description": "Service to import from" }, "status": { "enum": [ "pending", "running", "success", "error", "cancelled" ], "type": "string" }, "totalItems": { "type": "number" }, "processedItems": { "type": "number" }, "importedCount": { "type": "number" }, "skippedCount": { "type": "number" }, "failedCount": { "type": "number" }, "currentMessage": { "anyOf": [ { "type": "string" }, { "type": "null" } ] }, "errors": { "type": "array", "items": { "type": "string" } }, "warnings": { "type": "array", "items": { "type": "string" } }, "createdAt": { "type": "string" }, "startedAt": { "anyOf": [ { "type": "string" }, { "type": "null" } ] }, "finishedAt": { "anyOf": [ { "type": "string" }, { "type": "null" } ] } }, "required": [ "id", "source", "status", "totalItems", "processedItems", "importedCount", "skippedCount", "failedCount", "currentMessage", "errors", "warnings", "createdAt", "startedAt", "finishedAt" ] } } } }, "400": { "description": "400", "content": { "application/json": { "schema": { "oneOf": [ { "type": "object", "properties": { "defined": { "const": true }, "code": { "const": "BAD_REQUEST" }, "status": { "const": 400 }, "message": { "type": "string", "default": "Import cannot be cancelled" }, "data": { "type": "object", "properties": { "code": { "const": "IMPORT_CANNOT_CANCEL" } }, "required": [ "code" ] } }, "required": [ "defined", "code", "status", "message", "data" ] }, { "type": "object", "properties": { "defined": { "const": false }, "code": { "type": "string" }, "status": { "type": "number" }, "message": { "type": "string" }, "data": {} }, "required": [ "defined", "code", "status", "message" ] } ] } } } } }, "security": [ { "session": [] } ] } }, "/imports/jobs/{id}/events": { "get": { "operationId": "imports.jobEvents", "summary": "Stream import job events", "description": "SSE stream of import job progress events. Yields progress updates and a final complete event.", "tags": [ "Imports" ], "parameters": [ { "name": "id", "in": "path", "required": true, "schema": { "type": "string", "minLength": 1, "description": "Internal UUIDv7 identifier" } } ], "responses": { "200": { "description": "Stream of job progress events", "content": { "text/event-stream": { "schema": { "oneOf": [ { "type": "object", "properties": { "event": { "const": "message" }, "data": { "type": "object", "properties": { "type": { "enum": [ "progress", "complete", "timeout" ], "type": "string" }, "job": { "type": "object", "properties": { "id": { "type": "string" }, "source": { "enum": [ "trakt", "simkl", "letterboxd", "sofa" ], "type": "string", "description": "Service to import from" }, "status": { "enum": [ "pending", "running", "success", "error", "cancelled" ], "type": "string" }, "totalItems": { "type": "number" }, "processedItems": { "type": "number" }, "importedCount": { "type": "number" }, "skippedCount": { "type": "number" }, "failedCount": { "type": "number" }, "currentMessage": { "anyOf": [ { "type": "string" }, { "type": "null" } ] }, "errors": { "type": "array", "items": { "type": "string" } }, "warnings": { "type": "array", "items": { "type": "string" } }, "createdAt": { "type": "string" }, "startedAt": { "anyOf": [ { "type": "string" }, { "type": "null" } ] }, "finishedAt": { "anyOf": [ { "type": "string" }, { "type": "null" } ] } }, "required": [ "id", "source", "status", "totalItems", "processedItems", "importedCount", "skippedCount", "failedCount", "currentMessage", "errors", "warnings", "createdAt", "startedAt", "finishedAt" ] } }, "required": [ "type", "job" ] }, "id": { "type": "string" }, "retry": { "type": "number" } }, "required": [ "event", "data" ] }, { "type": "object", "properties": { "event": { "const": "done" }, "data": {}, "id": { "type": "string" }, "retry": { "type": "number" } }, "required": [ "event" ] }, { "type": "object", "properties": { "event": { "const": "error" }, "data": {}, "id": { "type": "string" }, "retry": { "type": "number" } }, "required": [ "event" ] } ] } } } } }, "security": [ { "session": [] } ] } } } }