{ "info": { "title": "Sofa API", "version": "0.2.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 metadata" }, { "name": "Tracking", "description": "Watch tracking, ratings, and status management" }, { "name": "Library", "description": "User library browsing and feeds" }, { "name": "Discover", "description": "Search, trending, and content discovery" }, { "name": "People", "description": "Cast and crew information" }, { "name": "Account", "description": "User account and integrations" }, { "name": "System", "description": "Server status and configuration" }, { "name": "Admin", "description": "Server administration" }, { "name": "Imports", "description": "Data import from external services" } ], "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.get", "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": { "platformId": { "type": "string", "description": "Platform 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 category: stream or purchase" }, "watchUrl": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "description": "Direct link to watch on this provider" }, "isUserSubscribed": { "type": "boolean", "description": "Whether the user subscribes to this platform" } }, "required": [ "platformId", "providerName", "logoPath", "offerType", "watchUrl", "isUserSubscribed" ], "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}/similar": { "get": { "operationId": "titles.similar", "summary": "Get similar titles", "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": "Similar 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": [] } ] } }, "/tracking/watch": { "post": { "operationId": "tracking.watch", "summary": "Mark items as watched", "description": "Mark one or more items as watched. Use scope to indicate what the IDs refer to: movie (title IDs), episode (episode IDs), season (season IDs), or series (title IDs to mark all episodes).", "tags": [ "Tracking" ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "properties": { "scope": { "enum": [ "movie", "episode", "season", "series" ], "type": "string", "description": "What the IDs refer to: movie title, episode, season, or entire TV series" }, "ids": { "type": "array", "minItems": 1, "items": { "type": "string", "minLength": 1 }, "description": "IDs to mark as watched" } }, "required": [ "scope", "ids" ], "description": "Mark one or more items as watched" } } } }, "responses": { "200": { "description": "OK" } }, "security": [ { "session": [] } ] } }, "/tracking/unwatch": { "post": { "operationId": "tracking.unwatch", "summary": "Remove watch records", "description": "Remove watch records for one or more items. Use scope to indicate what the IDs refer to: movie, episode, season, or series.", "tags": [ "Tracking" ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "properties": { "scope": { "enum": [ "movie", "episode", "season", "series" ], "type": "string", "description": "What the IDs refer to: movie title, episode, season, or entire TV series" }, "ids": { "type": "array", "minItems": 1, "items": { "type": "string", "minLength": 1 }, "description": "IDs to unwatch" } }, "required": [ "scope", "ids" ], "description": "Remove watch records for one or more items" } } } }, "responses": { "200": { "description": "OK" } }, "security": [ { "session": [] } ] } }, "/tracking/titles/{id}/status": { "put": { "operationId": "tracking.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": [ "Tracking" ], "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": [] } ] } }, "/tracking/titles/{id}/rating": { "put": { "operationId": "tracking.rate", "summary": "Rate a title", "description": "Set a 0-5 star rating for a title. Use 0 to clear the rating.", "tags": [ "Tracking" ], "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": [] } ] } }, "/tracking/titles/{id}": { "get": { "operationId": "tracking.userInfo", "summary": "Get user's tracking info for a title", "description": "Fetch the current user's tracking status, rating, and watched episode IDs for a title.", "tags": [ "Tracking" ], "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": [] } ] } }, "/tracking/stats": { "get": { "operationId": "tracking.stats", "summary": "Get watch statistics", "description": "Fetch the user's watch counts grouped by time period. Useful for rendering activity charts and dashboard counters.", "tags": [ "Tracking" ], "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": [] } ] } }, "/library": { "get": { "operationId": "library.list", "summary": "List library with filters", "description": "Fetch paginated, filtered, and sorted titles from the user's library. Supports filtering by status, type, genre, rating, year, content rating, and streaming availability.", "tags": [ "Library" ], "parameters": [ { "name": "search", "in": "query", "schema": { "type": "string", "maxLength": 200, "description": "Search within library by title name" }, "allowEmptyValue": true, "allowReserved": true }, { "name": "statuses", "in": "query", "schema": { "type": "array", "items": { "enum": [ "in_watchlist", "watching", "caught_up", "completed" ], "type": "string" }, "description": "Filter by display statuses (multi-select)" }, "style": "deepObject", "explode": true, "allowEmptyValue": true, "allowReserved": true }, { "name": "type", "in": "query", "schema": { "enum": [ "movie", "tv" ], "type": "string", "description": "Filter by media type" }, "allowEmptyValue": true, "allowReserved": true }, { "name": "genreId", "in": "query", "schema": { "type": "integer", "minimum": -9007199254740991, "maximum": 9007199254740991, "description": "Filter by TMDB genre ID" }, "allowEmptyValue": true, "allowReserved": true }, { "name": "ratingMin", "in": "query", "schema": { "type": "integer", "minimum": 1, "maximum": 5, "description": "Minimum user star rating" }, "allowEmptyValue": true, "allowReserved": true }, { "name": "ratingMax", "in": "query", "schema": { "type": "integer", "minimum": 1, "maximum": 5, "description": "Maximum user star rating" }, "allowEmptyValue": true, "allowReserved": true }, { "name": "yearMin", "in": "query", "schema": { "type": "integer", "minimum": 1900, "maximum": 2100, "description": "Minimum release year" }, "allowEmptyValue": true, "allowReserved": true }, { "name": "yearMax", "in": "query", "schema": { "type": "integer", "minimum": 1900, "maximum": 2100, "description": "Maximum release year" }, "allowEmptyValue": true, "allowReserved": true }, { "name": "contentRating", "in": "query", "schema": { "type": "string", "description": "Content rating filter (e.g. PG-13, TV-MA)" }, "allowEmptyValue": true, "allowReserved": true }, { "name": "onMyServices", "in": "query", "schema": { "type": "boolean", "description": "Only show titles available on the user's streaming services" }, "allowEmptyValue": true, "allowReserved": true }, { "name": "sortBy", "in": "query", "schema": { "enum": [ "title", "added_at", "release_date", "popularity", "user_rating", "vote_average" ], "type": "string", "default": "added_at", "description": "Sort field" }, "allowEmptyValue": true, "allowReserved": true }, { "name": "sortDirection", "in": "query", "schema": { "enum": [ "asc", "desc" ], "type": "string", "default": "desc", "description": "Sort direction" }, "allowEmptyValue": true, "allowReserved": true }, { "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": "Filtered library items with user statuses and ratings", "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" }, "userRating": { "anyOf": [ { "type": "number" }, { "type": "null" } ], "description": "User's star rating (1-5), or null" } }, "required": [ "id", "tmdbId", "type", "title", "posterPath", "posterThumbHash", "releaseDate", "firstAirDate", "voteAverage", "userStatus", "userRating" ], "description": "A library item with user status and rating" } }, "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": "Filtered and sorted library titles" } } } } }, "security": [ { "session": [] } ] } }, "/library/genres": { "get": { "operationId": "library.genres", "summary": "List genres in user's library", "description": "Get the distinct genres present in the user's library, ordered alphabetically. Used to populate the genre filter dropdown.", "tags": [ "Library" ], "responses": { "200": { "description": "Genres present in the library", "content": { "application/json": { "schema": { "type": "object", "properties": { "genres": { "type": "array", "items": { "type": "object", "properties": { "id": { "type": "number" }, "name": { "type": "string" } }, "required": [ "id", "name" ] }, "description": "Genres present in the user's library" } }, "required": [ "genres" ], "description": "Genres that exist in the user's library" } } } } }, "security": [ { "session": [] } ] } }, "/library/stats": { "get": { "operationId": "library.stats", "summary": "Get library statistics", "description": "Fetch aggregate library counts: total titles and completed titles.", "tags": [ "Library" ], "responses": { "200": { "description": "Library size and completed count", "content": { "application/json": { "schema": { "type": "object", "properties": { "size": { "type": "number", "description": "Total titles in the user's library" }, "completed": { "type": "number", "description": "Total titles with completed status" } }, "required": [ "size", "completed" ], "description": "Aggregate library statistics" } } } } }, "security": [ { "session": [] } ] } }, "/library/continue-watching": { "get": { "operationId": "library.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": [ "Library" ], "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": [] } ] } }, "/library/upcoming": { "get": { "operationId": "library.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": [ "Library" ], "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 }, { "name": "mediaType", "in": "query", "schema": { "enum": [ "movie", "tv" ], "type": "string", "description": "Filter to only movies or only TV episodes" }, "allowEmptyValue": true, "allowReserved": true }, { "name": "statusFilter", "in": "query", "schema": { "type": "array", "items": { "enum": [ "watching", "watchlist" ], "type": "string" }, "description": "Filter by user tracking status" }, "style": "deepObject", "explode": true, "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": { "platformId": { "type": "string" }, "providerName": { "type": "string" }, "logoPath": { "anyOf": [ { "type": "string" }, { "type": "null" } ] } }, "required": [ "platformId", "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": [] } ] } }, "/discover/trending": { "get": { "operationId": "discover.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": [ "Discover" ], "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": [] } ] } }, "/discover/popular": { "get": { "operationId": "discover.popular", "summary": "Get popular titles", "description": "Fetch currently popular movies or TV shows from TMDB with the user's tracking statuses.", "tags": [ "Discover" ], "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": [] } ] } }, "/discover/search": { "get": { "operationId": "discover.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": [ "Discover" ], "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": { "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": [] } ] } }, "/discover/browse": { "get": { "operationId": "discover.browse", "summary": "Browse titles with filters", "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": false, "schema": { "type": "integer", "minimum": -9007199254740991, "maximum": 9007199254740991, "description": "TMDB genre ID to filter by" }, "allowEmptyValue": true, "allowReserved": true }, { "name": "yearMin", "in": "query", "required": false, "schema": { "type": "integer", "minimum": 1900, "maximum": 2100, "description": "Minimum release year" }, "allowEmptyValue": true, "allowReserved": true }, { "name": "yearMax", "in": "query", "required": false, "schema": { "type": "integer", "minimum": 1900, "maximum": 2100, "description": "Maximum release year" }, "allowEmptyValue": true, "allowReserved": true }, { "name": "ratingMin", "in": "query", "required": false, "schema": { "type": "number", "minimum": 0, "maximum": 10, "description": "Minimum TMDB vote average" }, "allowEmptyValue": true, "allowReserved": true }, { "name": "sortBy", "in": "query", "required": false, "schema": { "enum": [ "popularity.desc", "vote_average.desc", "primary_release_date.desc", "primary_release_date.asc" ], "type": "string", "description": "Sort order for results" }, "allowEmptyValue": true, "allowReserved": true }, { "name": "language", "in": "query", "required": false, "schema": { "type": "string", "minLength": 2, "maxLength": 2, "pattern": "^[a-z]{2}$", "description": "ISO 639-1 original language code" }, "allowEmptyValue": true, "allowReserved": true }, { "name": "platformId", "in": "query", "required": false, "schema": { "type": "string", "description": "Platform 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": [] } ] } }, "/discover/genres": { "get": { "operationId": "discover.genres", "summary": "List genres", "description": "Fetch the list of TMDB genres for movies or TV shows. Used to populate genre filters for discovery.", "tags": [ "Discover" ], "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": [] } ] } }, "/discover/platforms": { "get": { "operationId": "discover.platforms", "summary": "List available streaming platforms", "description": "Fetch all available streaming platforms, ordered by popularity. Includes subscription status for filtering.", "tags": [ "Discover" ], "responses": { "200": { "description": "Platform list with logos and metadata", "content": { "application/json": { "schema": { "type": "object", "properties": { "platforms": { "type": "array", "items": { "type": "object", "properties": { "id": { "type": "string", "description": "Platform ID" }, "name": { "type": "string", "description": "Display name" }, "tmdbProviderIds": { "type": "array", "items": { "type": "number" }, "description": "TMDB provider IDs mapped to this platform" }, "logoPath": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "description": "Logo image path" }, "isSubscription": { "type": "boolean", "description": "True for subscription services, false for purchase/rental stores" } }, "required": [ "id", "name", "tmdbProviderIds", "logoPath", "isSubscription" ], "description": "A streaming platform" } } }, "required": [ "platforms" ] } } } } }, "security": [ { "session": [] } ] } }, "/discover/recommendations": { "get": { "operationId": "discover.recommendations", "summary": "Get personalized recommendations", "description": "Fetch personalized title recommendations based on the user's library and watch history.", "tags": [ "Discover" ], "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 based on the user's library" } } } } }, "security": [ { "session": [] } ] } }, "/people/{id}": { "get": { "operationId": "people.get", "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": [] } ] } }, "/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": [] } ] } }, "/account/platforms": { "get": { "operationId": "account.platforms", "summary": "Get user's streaming platforms", "description": "Fetch the current user's subscribed streaming platform IDs.", "tags": [ "Account" ], "responses": { "200": { "description": "List of platform IDs", "content": { "application/json": { "schema": { "type": "object", "properties": { "platformIds": { "type": "array", "items": { "type": "string" } } }, "required": [ "platformIds" ] } } } } }, "security": [ { "session": [] } ] }, "put": { "operationId": "account.updatePlatforms", "summary": "Update streaming platforms", "description": "Set the current user's subscribed streaming platforms.", "tags": [ "Account" ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "properties": { "platformIds": { "type": "array", "items": { "type": "string" }, "description": "List of platform IDs the user subscribes to" } }, "required": [ "platformIds" ] } } } }, "responses": { "200": { "description": "OK" } }, "security": [ { "session": [] } ] } }, "/account/integrations": { "get": { "operationId": "account.integrations.list", "summary": "List integrations", "description": "Fetch all configured media server integrations for the current user, including recent webhook/sync events for each.", "tags": [ "Account" ], "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": "account.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": [ "Account" ], "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": [] } ] } }, "/account/integrations/{provider}": { "delete": { "operationId": "account.integrations.delete", "summary": "Delete integration", "description": "Remove a media server integration and all its event history.", "tags": [ "Account" ], "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": [] } ] } }, "/account/integrations/{provider}/regenerate-token": { "post": { "operationId": "account.integrations.regenerateToken", "summary": "Regenerate webhook token", "description": "Generate a new webhook token for an integration. The old token is immediately invalidated.", "tags": [ "Account" ], "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": [] } ] } }, "/system/public-info": { "get": { "operationId": "system.publicInfo", "summary": "Get public instance info", "description": "Fetch public information about this Sofa instance including authentication configuration. Does not require authentication.", "tags": [ "System" ], "responses": { "200": { "description": "Instance configuration, auth settings, 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" }, "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" } }, "required": [ "instanceId", "tmdbConfigured", "userCount", "registrationOpen", "posterUrls", "oidcEnabled", "oidcProviderName", "passwordLoginDisabled" ], "description": "Public instance information and authentication 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": [] } ] } }, "/admin/settings": { "get": { "operationId": "admin.settings.get", "summary": "Get admin settings", "description": "Fetch all admin-configurable settings: registration, update checks, and telemetry.", "tags": [ "Admin" ], "responses": { "200": { "description": "Current admin settings", "content": { "application/json": { "schema": { "type": "object", "properties": { "registration": { "type": "object", "properties": { "open": { "type": "boolean", "description": "Whether new user registration is open" } }, "required": [ "open" ] }, "updateCheck": { "type": "object", "properties": { "enabled": { "type": "boolean", "description": "Whether automatic update checks are enabled" }, "updateAvailable": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "description": "Whether a newer version is available" }, "currentVersion": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "description": "Currently running version" }, "latestVersion": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "description": "Latest available version" }, "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": [ "enabled", "updateAvailable", "currentVersion", "latestVersion", "releaseUrl", "lastCheckedAt" ] }, "telemetry": { "type": "object", "properties": { "enabled": { "type": "boolean", "description": "Whether anonymous telemetry is enabled" }, "lastReportedAt": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "description": "ISO 8601 timestamp of the last telemetry report" } }, "required": [ "enabled", "lastReportedAt" ] } }, "required": [ "registration", "updateCheck", "telemetry" ], "description": "Combined admin settings for registration, update checks, and telemetry" } } } } }, "security": [ { "session": [] } ] }, "patch": { "operationId": "admin.settings.update", "summary": "Update admin settings", "description": "Partially update admin settings. Only provided sections are changed; omitted sections keep their current values.", "tags": [ "Admin" ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "properties": { "registration": { "type": "object", "properties": { "open": { "type": "boolean", "description": "Whether new user registration is allowed" } }, "required": [ "open" ] }, "updateCheck": { "type": "object", "properties": { "enabled": { "type": "boolean", "description": "Whether automatic update checks are enabled" } }, "required": [ "enabled" ] }, "telemetry": { "type": "object", "properties": { "enabled": { "type": "boolean", "description": "Whether anonymous telemetry is enabled" } }, "required": [ "enabled" ] } }, "description": "Partial update to admin settings" } } } }, "responses": { "200": { "description": "OK" } }, "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/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": [] } ] } }, "/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": [] } ] } } } }