# API Documentation ## Document Purpose This document provides comprehensive REST API and WebSocket reference for the Aniworld application. Source: [src/server/fastapi_app.py](../src/server/fastapi_app.py#L1-L252) --- ## 1. API Overview ### Base URL and Versioning | Environment | Base URL | | ------------------- | --------------------------------- | | Local Development | `http://127.0.0.1:8000` | | API Documentation | `http://127.0.0.1:8000/api/docs` | | ReDoc Documentation | `http://127.0.0.1:8000/api/redoc` | The API does not use versioning prefixes. All endpoints are available under `/api/*`. Source: [src/server/fastapi_app.py](../src/server/fastapi_app.py#L177-L184) ### Authentication The API uses JWT Bearer Token authentication. **Header Format:** ``` Authorization: Bearer ``` **Public Endpoints (no authentication required):** - `/api/auth/*` - Authentication endpoints - `/api/health` - Health check endpoints - `/api/docs`, `/api/redoc` - API documentation - `/static/*` - Static files - `/`, `/login`, `/setup`, `/queue` - UI pages Source: [src/server/middleware/auth.py](../src/server/middleware/auth.py#L39-L52) ### Content Types | Direction | Content-Type | | --------- | ----------------------------- | | Request | `application/json` | | Response | `application/json` | | WebSocket | `application/json` (messages) | ### Common Headers | Header | Required | Description | | --------------- | -------- | ------------------------------------ | | `Authorization` | Yes\* | Bearer token for protected endpoints | | `Content-Type` | Yes | `application/json` for POST/PUT | | `Origin` | No | Required for CORS preflight | \*Not required for public endpoints listed above. --- ## 2. Authentication Endpoints Prefix: `/api/auth` Source: [src/server/api/auth.py](../src/server/api/auth.py#L1-L180) ### POST /api/auth/setup Initial setup endpoint to configure the master password. Can only be called once. **Request Body:** ```json { "master_password": "string (min 8 chars, mixed case, number, special char)", "anime_directory": "string (optional, path to anime folder)" } ``` **Response (201 Created):** ```json { "status": "ok" } ``` **Errors:** - `400 Bad Request` - Master password already configured or invalid password Source: [src/server/api/auth.py](../src/server/api/auth.py#L28-L90) ### POST /api/auth/login Validate master password and return JWT token. **Request Body:** ```json { "password": "string", "remember": false } ``` **Response (200 OK):** ```json { "access_token": "eyJ...", "token_type": "bearer", "expires_at": "2025-12-14T10:30:00Z" } ``` **Errors:** - `401 Unauthorized` - Invalid credentials - `429 Too Many Requests` - Account locked due to failed attempts Source: [src/server/api/auth.py](../src/server/api/auth.py#L93-L124) ### POST /api/auth/logout Logout by revoking token. **Response (200 OK):** ```json { "status": "ok", "message": "Logged out successfully" } ``` Source: [src/server/api/auth.py](../src/server/api/auth.py#L127-L140) ### GET /api/auth/status Return whether master password is configured and if caller is authenticated. **Response (200 OK):** ```json { "configured": true, "authenticated": true } ``` Source: [src/server/api/auth.py](../src/server/api/auth.py#L157-L162) --- ## 3. Anime Endpoints Prefix: `/api/anime` Source: [src/server/api/anime.py](../src/server/api/anime.py#L1-L812) ### Series Identifier Convention The API uses two identifier fields: | Field | Purpose | Example | | -------- | ---------------------------------------------------- | -------------------------- | | `key` | **Primary identifier** - provider-assigned, URL-safe | `"attack-on-titan"` | | `folder` | Metadata only - filesystem folder name | `"Attack on Titan (2013)"` | Use `key` for all API operations. The `folder` field is for display purposes only. ### GET /api/anime/status Get anime library status information. **Authentication:** Required **Response (200 OK):** ```json { "directory": "/path/to/anime", "series_count": 42 } ``` Source: [src/server/api/anime.py](../src/server/api/anime.py#L28-L58) ### GET /api/anime List library series that have missing episodes. **Authentication:** Required **Query Parameters:** | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `page` | int | 1 | Page number (must be positive) | | `per_page` | int | 20 | Items per page (max 1000) | | `sort_by` | string | null | Sort field: `title`, `id`, `name`, `missing_episodes` | | `filter` | string | null | Filter: `missing_episodes` (shows series with any missing episodes), `no_episodes` (shows series with zero downloaded episodes) | **Filter Details:** - `missing_episodes`: Returns series that have at least one missing episode recorded in the database (`is_downloaded=False`) - `no_episodes`: Returns series that have missing episodes and no downloaded episodes (i.e., only missing episodes exist in the database) - Episodes in the database represent MISSING episodes (from episodeDict during scanning) - `is_downloaded=False` means the episode file was not found in the folder **Response (200 OK):** ```json [ { "key": "beheneko-the-elf-girls-cat", "name": "Beheneko", "site": "aniworld.to", "folder": "beheneko the elf girls cat (2025)", "missing_episodes": { "1": [1, 2, 3, 4] }, "link": "" } ] ``` **Example with filter:** ```bash GET /api/anime?filter=no_episodes ``` Source: [src/server/api/anime.py](../src/server/api/anime.py#L155-L303) ### GET /api/anime/search Search the provider for anime series matching a query. **Authentication:** Not required **Query Parameters:** | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `query` | string | Yes | Search term (max 200 chars) | **Response (200 OK):** ```json [ { "key": "attack-on-titan", "name": "Attack on Titan", "site": "aniworld.to", "folder": "Attack on Titan (2013)", "missing_episodes": {}, "link": "https://aniworld.to/anime/stream/attack-on-titan" } ] ``` Source: [src/server/api/anime.py](../src/server/api/anime.py#L431-L474) ### POST /api/anime/search Search via POST body. **Request Body:** ```json { "query": "attack on titan" } ``` **Response:** Same as GET /api/anime/search Source: [src/server/api/anime.py](../src/server/api/anime.py#L477-L495) ### POST /api/anime/add Add a new series to the library with automatic database persistence, folder creation, and episode scanning. **Authentication:** Required **Request Body:** ```json { "link": "https://aniworld.to/anime/stream/attack-on-titan", "name": "Attack on Titan" } ``` **Response (200 OK):** ```json { "status": "success", "message": "Successfully added series: Attack on Titan", "key": "attack-on-titan", "folder": "Attack on Titan", "db_id": 1, "missing_episodes": ["1-1", "1-2", "1-3"], "total_missing": 3 } ``` **Enhanced Flow:** 1. Validates the request (link format, name) 2. Creates Serie object with sanitized folder name 3. Saves to database via AnimeDBService 4. Creates folder using sanitized display name (not internal key) 5. Performs targeted episode scan for this anime only 6. Returns response with missing episodes count **Folder Name Sanitization:** - Removes invalid filesystem characters: `< > : " / \ | ? *` - Trims leading/trailing whitespace and dots - Preserves Unicode characters (for Japanese titles) - Example: `"Attack on Titan: Final Season"` → `"Attack on Titan Final Season"` Source: [src/server/api/anime.py](../src/server/api/anime.py#L604-L710) ### POST /api/anime/rescan Trigger a rescan of the local library. **Authentication:** Required **Response (200 OK):** ```json { "success": true, "message": "Rescan started successfully" } ``` Source: [src/server/api/anime.py](../src/server/api/anime.py#L306-L337) ### GET /api/anime/{anime_id} Return detailed information about a specific series. **Authentication:** Not required **Path Parameters:** | Parameter | Description | |-----------|-------------| | `anime_id` | Series `key` (primary) or `folder` (deprecated fallback) | **Response (200 OK):** ```json { "key": "attack-on-titan", "title": "Attack on Titan", "folder": "Attack on Titan (2013)", "episodes": ["1-1", "1-2", "1-3"], "description": null } ``` Source: [src/server/api/anime.py](../src/server/api/anime.py#L713-L793) ### DELETE /api/anime/{anime_key} Delete an anime series from the database, filesystem, or both. Requires authentication and explicit typed confirmation. **Authentication:** Required **Path Parameters:** | Parameter | Description | |-----------|-------------| | `anime_key` | Series key (primary identifier) | **Request Body:** ```json { "delete_database": true, "delete_folder": false, "confirm_text": "delete" } ``` | Field | Type | Default | Description | |-------|------|---------|-------------| | `delete_database` | bool | `true` | Remove series and episodes from SQLite | | `delete_folder` | bool | `false` | Delete the series folder and all files | | `confirm_text` | string | — | Must be exactly `"delete"` (case-sensitive) | **Response (200 OK):** ```json { "success": true, "key": "attack-on-titan", "name": "Attack on Titan", "deleted_from_database": true, "deleted_folder": false, "folder_path": null, "database_error": null, "folder_error": null, "message": "Removed from database." } ``` **Error Responses:** | Status | Condition | |--------|-----------| | 400 | `confirm_text != "delete"` or neither flag is `true` | | 401 | Not authenticated | | 404 | Series `key` not found in database | | 500 | Unexpected server error | **Deletion Modes:** | Flags | Effect | |-------|--------| | `delete_database=true, delete_folder=false` | Removes series from SQLite. Folder on disk is preserved. | | `delete_database=false, delete_folder=true` | Deletes folder and all files. Database record preserved. | | `delete_database=true, delete_folder=true` | Full removal: database record deleted AND folder/files deleted. | **Path Safety:** Folder deletion is blocked if the path is outside the configured anime base directory (path traversal protection via `is_safe_path`). **WebSocket Broadcast:** On success, a `series_deleted` event is broadcast to all connected clients, causing the anime card to be removed from all browser sessions in real-time. Source: [src/server/api/anime.py](../src/server/api/anime.py#L1759-L1840) --- ## 4. Download Queue Endpoints Prefix: `/api/queue` Source: [src/server/api/download.py](../src/server/api/download.py#L1-L529) ### GET /api/queue/status Get current download queue status and statistics. **Authentication:** Required **Response (200 OK):** ```json { "status": { "is_running": false, "is_paused": false, "active_downloads": [], "pending_queue": [], "completed_downloads": [], "failed_downloads": [] }, "statistics": { "total_items": 5, "pending_count": 3, "active_count": 1, "completed_count": 1, "failed_count": 0, "total_downloaded_mb": 1024.5, "average_speed_mbps": 2.5, "estimated_time_remaining": 3600 } } ``` Source: [src/server/api/download.py](../src/server/api/download.py#L21-L56) ### POST /api/queue/add Add episodes to the download queue. **Authentication:** Required **Request Body:** ```json { "serie_id": "attack-on-titan", "serie_folder": "Attack on Titan (2013)", "serie_name": "Attack on Titan", "episodes": [ { "season": 1, "episode": 1, "title": "Episode 1" }, { "season": 1, "episode": 2, "title": "Episode 2" } ], "priority": "NORMAL" } ``` **Priority Values:** `LOW`, `NORMAL`, `HIGH` **Response (201 Created):** ```json { "status": "success", "message": "Added 2 episode(s) to download queue", "added_items": ["uuid1", "uuid2"], "item_ids": ["uuid1", "uuid2"], "failed_items": [] } ``` Source: [src/server/api/download.py](../src/server/api/download.py#L59-L120) ### POST /api/queue/start Start automatic queue processing. **Authentication:** Required **Response (200 OK):** ```json { "status": "success", "message": "Queue processing started" } ``` Source: [src/server/api/download.py](../src/server/api/download.py#L293-L331) ### POST /api/queue/stop Stop processing new downloads from queue. **Authentication:** Required **Response (200 OK):** ```json { "status": "success", "message": "Queue processing stopped (current download will continue)" } ``` Source: [src/server/api/download.py](../src/server/api/download.py#L334-L387) ### POST /api/queue/pause Pause queue processing (alias for stop). **Authentication:** Required **Response (200 OK):** ```json { "status": "success", "message": "Queue processing paused" } ``` Source: [src/server/api/download.py](../src/server/api/download.py#L416-L445) ### DELETE /api/queue/{item_id} Remove a specific item from the download queue. **Authentication:** Required **Path Parameters:** | Parameter | Description | |-----------|-------------| | `item_id` | Download item UUID | **Response (204 No Content)** Source: [src/server/api/download.py](../src/server/api/download.py#L225-L256) ### DELETE /api/queue Remove multiple items from the download queue. **Authentication:** Required **Request Body:** ```json { "item_ids": ["uuid1", "uuid2"] } ``` **Response (204 No Content)** Source: [src/server/api/download.py](../src/server/api/download.py#L259-L290) ### DELETE /api/queue/completed Clear completed downloads from history. **Authentication:** Required **Response (200 OK):** ```json { "status": "success", "message": "Cleared 5 completed item(s)", "count": 5 } ``` Source: [src/server/api/download.py](../src/server/api/download.py#L123-L149) ### DELETE /api/queue/failed Clear failed downloads from history. **Authentication:** Required **Response (200 OK):** ```json { "status": "success", "message": "Cleared 2 failed item(s)", "count": 2 } ``` Source: [src/server/api/download.py](../src/server/api/download.py#L152-L178) ### DELETE /api/queue/pending Clear all pending downloads from the queue. **Authentication:** Required **Response (200 OK):** ```json { "status": "success", "message": "Removed 10 pending item(s)", "count": 10 } ``` Source: [src/server/api/download.py](../src/server/api/download.py#L181-L207) ### POST /api/queue/reorder Reorder items in the pending queue. **Authentication:** Required **Request Body:** ```json { "item_ids": ["uuid3", "uuid1", "uuid2"] } ``` **Response (200 OK):** ```json { "status": "success", "message": "Queue reordered with 3 items" } ``` Source: [src/server/api/download.py](../src/server/api/download.py#L448-L477) ### POST /api/queue/retry Retry failed downloads. **Authentication:** Required **Request Body:** ```json { "item_ids": ["uuid1", "uuid2"] } ``` Pass empty `item_ids` array to retry all failed items. **Response (200 OK):** ```json { "status": "success", "message": "Retrying 2 failed item(s)", "retried_count": 2, "retried_ids": ["uuid1", "uuid2"] } ``` Source: [src/server/api/download.py](../src/server/api/download.py#L480-L514) --- ## 5. Configuration Endpoints Prefix: `/api/config` Source: [src/server/api/config.py](../src/server/api/config.py#L1-L374) ### GET /api/config Return current application configuration. **Authentication:** Required **Response (200 OK):** ```json { "name": "Aniworld", "data_dir": "data", "scheduler": { "enabled": true, "interval_minutes": 60, "schedule_time": "03:00", "schedule_days": ["mon", "tue", "wed", "thu", "fri", "sat", "sun"], "auto_download_after_rescan": false }, "logging": { "level": "INFO", "file": null, "max_bytes": null, "backup_count": 3 }, "backup": { "enabled": false, "path": "data/backups", "keep_days": 30 }, "other": {} } ``` Source: [src/server/api/config.py](../src/server/api/config.py#L16-L27) ### PUT /api/config Apply an update to the configuration. **Authentication:** Required **Request Body:** ```json { "scheduler": { "enabled": true, "interval_minutes": 60, "schedule_time": "06:30", "schedule_days": ["mon", "wed", "fri"] }, "logging": { "level": "DEBUG" } } ``` **Response (200 OK):** Updated configuration object Source: [src/server/api/config.py](../src/server/api/config.py#L30-L47) ### POST /api/config/validate Validate a configuration without applying it. **Authentication:** Required **Request Body:** Full `AppConfig` object **Response (200 OK):** ```json { "valid": true, "errors": [] } ``` Source: [src/server/api/config.py](../src/server/api/config.py#L50-L64) ### GET /api/config/backups List all available configuration backups. **Authentication:** Required **Response (200 OK):** ```json [ { "name": "config_backup_20251213_090130.json", "size": 1024, "created": "2025-12-13T09:01:30Z" } ] ``` Source: [src/server/api/config.py](../src/server/api/config.py#L67-L81) ### POST /api/config/backups Create a backup of the current configuration. **Authentication:** Required **Query Parameters:** | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `name` | string | No | Custom backup name | **Response (200 OK):** ```json { "name": "config_backup_20251213_090130.json", "message": "Backup created successfully" } ``` Source: [src/server/api/config.py](../src/server/api/config.py#L84-L102) ### POST /api/config/backups/{backup_name}/restore Restore configuration from a backup. **Authentication:** Required **Response (200 OK):** Restored configuration object Source: [src/server/api/config.py](../src/server/api/config.py#L105-L123) ### DELETE /api/config/backups/{backup_name} Delete a configuration backup. **Authentication:** Required **Response (200 OK):** ```json { "message": "Backup 'config_backup_20251213.json' deleted successfully" } ``` Source: [src/server/api/config.py](../src/server/api/config.py#L126-L142) ### POST /api/config/directory Update anime directory configuration. **Authentication:** Required **Request Body:** ```json { "directory": "/path/to/anime" } ``` **Response (200 OK):** ```json { "message": "Anime directory updated successfully", "synced_series": 15 } ``` Source: [src/server/api/config.py](../src/server/api/config.py#L189-L247) --- ## 6. NFO Management Endpoints Prefix: `/api/nfo` Source: [src/server/api/nfo.py](../src/server/api/nfo.py) These endpoints manage tvshow.nfo metadata files for anime series. The per-anime settings page (replacing the old "NFO Diagnostics" UI) lives at `/api/anime/{key}/settings` — see section **7. Anime Settings Endpoints** below. **Prerequisites:** - TMDB API key must be configured in settings - NFO endpoints return 503 if the API key is missing ### Anime Settings — New Endpoints For the UI-driven settings page (renamed from NFO Diagnostics), the following endpoints replace the older `/api/nfo/{serie_id}/*` flow: | Method | Path | Purpose | | --- | --- | --- | | GET | `/api/anime/{key}/settings` | Return all editable fields for a series | | PUT | `/api/anime/{key}/settings` | Update name/folder/tmdb_id/tvdb_id/site, optionally regenerate tvshow.nfo | | POST | `/api/anime/{key}/regenerate-nfo` | Regenerate tvshow.nfo using TMDB | See [AnimeSettingsResponse](../src/server/models/anime.py) for the response shape, and [section 7](#7-anime-settings-endpoints-new) for full documentation. ### GET /api/nfo/{serie_id}/check Check if NFO file and media files exist for a series. **Authentication:** Required **Path Parameters:** - `serie_id` (string): Series identifier **Response (200 OK):** ```json { "serie_id": "one-piece", "serie_folder": "One Piece (1999)", "has_nfo": true, "nfo_path": "/path/to/anime/One Piece (1999)/tvshow.nfo", "media_files": { "has_poster": true, "has_logo": false, "has_fanart": true, "poster_path": "/path/to/anime/One Piece (1999)/poster.jpg", "logo_path": null, "fanart_path": "/path/to/anime/One Piece (1999)/fanart.jpg" } } ``` **Errors:** - `401 Unauthorized` - Not authenticated - `404 Not Found` - Series not found - `503 Service Unavailable` - TMDB API key not configured Source: [src/server/api/nfo.py](../src/server/api/nfo.py#L90-L147) ### POST /api/nfo/{serie_id}/create Create NFO file and download media for a series. **Authentication:** Required **Path Parameters:** - `serie_id` (string): Series identifier **Request Body:** ```json { "serie_name": "One Piece", "year": 1999, "download_poster": true, "download_logo": true, "download_fanart": true, "overwrite_existing": false } ``` **Fields:** - `serie_name` (string, optional): Series name for TMDB search (defaults to folder name) - `year` (integer, optional): Series year to help narrow TMDB search - `download_poster` (boolean, default: true): Download poster.jpg - `download_logo` (boolean, default: true): Download logo.png - `download_fanart` (boolean, default: true): Download fanart.jpg - `overwrite_existing` (boolean, default: false): Overwrite existing NFO **Response (200 OK):** ```json { "serie_id": "one-piece", "serie_folder": "One Piece (1999)", "nfo_path": "/path/to/anime/One Piece (1999)/tvshow.nfo", "media_files": { "has_poster": true, "has_logo": true, "has_fanart": true, "poster_path": "/path/to/anime/One Piece (1999)/poster.jpg", "logo_path": "/path/to/anime/One Piece (1999)/logo.png", "fanart_path": "/path/to/anime/One Piece (1999)/fanart.jpg" }, "message": "NFO and media files created successfully" } ``` **Errors:** - `401 Unauthorized` - Not authenticated - `404 Not Found` - Series not found - `409 Conflict` - NFO already exists (use `overwrite_existing: true`) - `503 Service Unavailable` - TMDB API error or key not configured Source: [src/server/api/nfo.py](../src/server/api/nfo.py#L150-L240) ### PUT /api/nfo/{serie_id}/update Update existing NFO file with fresh TMDB data. **Authentication:** Required **Path Parameters:** - `serie_id` (string): Series identifier **Query Parameters:** - `download_media` (boolean, default: true): Re-download media files **Response (200 OK):** ```json { "serie_id": "one-piece", "serie_folder": "One Piece (1999)", "nfo_path": "/path/to/anime/One Piece (1999)/tvshow.nfo", "media_files": { "has_poster": true, "has_logo": true, "has_fanart": true, "poster_path": "/path/to/anime/One Piece (1999)/poster.jpg", "logo_path": "/path/to/anime/One Piece (1999)/logo.png", "fanart_path": "/path/to/anime/One Piece (1999)/fanart.jpg" }, "message": "NFO updated successfully" } ``` **Errors:** - `401 Unauthorized` - Not authenticated - `404 Not Found` - Series or NFO not found (use create endpoint) - `503 Service Unavailable` - TMDB API error Source: [src/server/api/nfo.py](../src/server/api/nfo.py#L243-L325) ### GET /api/nfo/{serie_id}/content Get NFO file XML content for a series. **Authentication:** Required **Path Parameters:** - `serie_id` (string): Series identifier **Response (200 OK):** ```json { "serie_id": "one-piece", "serie_folder": "One Piece (1999)", "content": "\n...", "file_size": 2048, "last_modified": "2026-01-15T10:30:00" } ``` **Errors:** - `401 Unauthorized` - Not authenticated - `404 Not Found` - Series or NFO not found Source: [src/server/api/nfo.py](../src/server/api/nfo.py#L328-L397) ### GET /api/nfo/{serie_id}/media/status Get media files status for a series. **Authentication:** Required **Path Parameters:** - `serie_id` (string): Series identifier **Response (200 OK):** ```json { "has_poster": true, "has_logo": false, "has_fanart": true, "poster_path": "/path/to/anime/One Piece (1999)/poster.jpg", "logo_path": null, "fanart_path": "/path/to/anime/One Piece (1999)/fanart.jpg" } ``` **Errors:** - `401 Unauthorized` - Not authenticated - `404 Not Found` - Series not found Source: [src/server/api/nfo.py](../src/server/api/nfo.py#L400-L447) ### POST /api/nfo/{serie_id}/media/download Download missing media files for a series. **Authentication:** Required **Path Parameters:** - `serie_id` (string): Series identifier **Request Body:** ```json { "download_poster": true, "download_logo": true, "download_fanart": true } ``` **Response (200 OK):** ```json { "has_poster": true, "has_logo": true, "has_fanart": true, "poster_path": "/path/to/anime/One Piece (1999)/poster.jpg", "logo_path": "/path/to/anime/One Piece (1999)/logo.png", "fanart_path": "/path/to/anime/One Piece (1999)/fanart.jpg" } ``` **Errors:** - `401 Unauthorized` - Not authenticated - `404 Not Found` - Series or NFO not found (NFO required for TMDB ID) - `503 Service Unavailable` - TMDB API error Source: [src/server/api/nfo.py](../src/server/api/nfo.py#L450-L519) ### POST /api/nfo/batch/create Batch create NFO files for multiple series. **Authentication:** Required **Request Body:** ```json { "serie_ids": ["one-piece", "naruto", "bleach"], "download_media": true, "skip_existing": true, "max_concurrent": 3 } ``` **Fields:** - `serie_ids` (array of strings): Series identifiers to process - `download_media` (boolean, default: true): Download media files - `skip_existing` (boolean, default: true): Skip series with existing NFOs - `max_concurrent` (integer, 1-10, default: 3): Number of concurrent operations **Response (200 OK):** ```json { "total": 3, "successful": 2, "failed": 0, "skipped": 1, "results": [ { "serie_id": "one-piece", "serie_folder": "One Piece (1999)", "success": true, "message": "NFO created successfully", "nfo_path": "/path/to/anime/One Piece (1999)/tvshow.nfo" }, { "serie_id": "naruto", "serie_folder": "Naruto (2002)", "success": false, "message": "Skipped - NFO already exists", "nfo_path": null }, { "serie_id": "bleach", "serie_folder": "Bleach (2004)", "success": true, "message": "NFO created successfully", "nfo_path": "/path/to/anime/Bleach (2004)/tvshow.nfo" } ] } ``` **Errors:** - `401 Unauthorized` - Not authenticated - `503 Service Unavailable` - TMDB API key not configured Source: [src/server/api/nfo.py](../src/server/api/nfo.py#L522-L634) ### GET /api/nfo/missing Get list of series without NFO files. **Authentication:** Required **Response (200 OK):** ```json { "total_series": 150, "missing_nfo_count": 23, "series": [ { "serie_id": "dragon-ball", "serie_folder": "Dragon Ball (1986)", "serie_name": "Dragon Ball", "has_media": false, "media_files": { "has_poster": false, "has_logo": false, "has_fanart": false, "poster_path": null, "logo_path": null, "fanart_path": null } } ] } ``` **Errors:** - `401 Unauthorized` - Not authenticated - `503 Service Unavailable` - TMDB API key not configured Source: [src/server/api/nfo.py](../src/server/api/nfo.py#L637-L684) --- ## 7. Scheduler Endpoints Prefix: `/api/scheduler` All GET/POST config responses share the same envelope: ```json { "success": true, "config": { ... }, "status": { ... } } ``` Source: [src/server/api/scheduler.py](../src/server/api/scheduler.py) ### GET /api/scheduler/config Get current scheduler configuration and runtime status. **Authentication:** Required **Response (200 OK):** ```json { "success": true, "config": { "enabled": true, "interval_minutes": 60, "schedule_time": "03:00", "schedule_days": ["mon", "tue", "wed", "thu", "fri", "sat", "sun"], "auto_download_after_rescan": false }, "status": { "is_running": true, "next_run": "2025-07-15T03:00:00+00:00", "last_run": null, "scan_in_progress": false } } ``` ### POST /api/scheduler/config Update scheduler configuration and apply changes immediately. **Authentication:** Required **Request Body (all fields optional, uses model defaults):** ```json { "enabled": true, "schedule_time": "06:30", "schedule_days": ["mon", "wed", "fri"], "auto_download_after_rescan": true } ``` **Response (200 OK):** Same envelope as GET, reflecting saved values. **Validation errors (422):** - `schedule_time` must match `HH:MM` (00:00–23:59) - `schedule_days` entries must be one of `mon tue wed thu fri sat sun` - `interval_minutes` must be ≥ 1 ### POST /api/scheduler/trigger-rescan Manually trigger a library rescan (and auto-download if configured). **Authentication:** Required **Response (200 OK):** ```json { "message": "Rescan started successfully" } ``` **Error responses:** - `503` — SeriesApp not yet initialised - `500` — Rescan failed unexpectedly --- ## 8. Health Check Endpoints Prefix: `/health` Source: [src/server/api/health.py](../src/server/api/health.py#L1-L267) ### GET /health Basic health check endpoint. **Authentication:** Not required **Response (200 OK):** ```json { "status": "healthy", "timestamp": "2025-12-13T10:30:00.000Z", "version": "1.0.1" } ``` Source: [src/server/api/health.py](../src/server/api/health.py#L151-L161) ### GET /health/detailed Comprehensive health check with database, filesystem, and system metrics. **Authentication:** Not required **Response (200 OK):** ```json { "status": "healthy", "timestamp": "2025-12-13T10:30:00.000Z", "version": "1.0.1", "dependencies": { "database": { "status": "healthy", "connection_time_ms": 1.5, "message": "Database connection successful" }, "filesystem": { "status": "healthy", "data_dir_writable": true, "logs_dir_writable": true }, "system": { "cpu_percent": 25.0, "memory_percent": 45.0, "memory_available_mb": 8192.0, "disk_percent": 60.0, "disk_free_mb": 102400.0, "uptime_seconds": 86400.0 } }, "startup_time": "2025-12-13T08:00:00.000Z" } ``` Source: [src/server/api/health.py](../src/server/api/health.py#L164-L200) --- ## 9. WebSocket Protocol Endpoint: `/ws/connect` Source: [src/server/api/websocket.py](../src/server/api/websocket.py#L1-L260) ### Connection **URL:** `ws://127.0.0.1:8000/ws/connect` **Query Parameters:** | Parameter | Required | Description | |-----------|----------|-------------| | `token` | No | JWT token for authenticated access | ### Message Types | Type | Direction | Description | | ------------------- | ---------------- | -------------------------- | | `connected` | Server -> Client | Connection confirmation | | `ping` | Client -> Server | Keepalive request | | `pong` | Server -> Client | Keepalive response | | `download_progress` | Server -> Client | Download progress update | | `download_complete` | Server -> Client | Download completed | | `download_failed` | Server -> Client | Download failed | | `download_added` | Server -> Client | Item added to queue | | `download_removed` | Server -> Client | Item removed from queue | | `queue_status` | Server -> Client | Queue status update | | `queue_started` | Server -> Client | Queue processing started | | `queue_stopped` | Server -> Client | Queue processing stopped | | `scan_progress` | Server -> Client | Library scan progress | | `scan_complete` | Server -> Client | Library scan completed | | `system_info` | Server -> Client | System information message | | `error` | Server -> Client | Error message | Source: [src/server/models/websocket.py](../src/server/models/websocket.py#L25-L57) ### Room Subscriptions Clients can join/leave rooms to receive specific updates. **Join Room:** ```json { "action": "join", "data": { "room": "downloads" } } ``` **Leave Room:** ```json { "action": "leave", "data": { "room": "downloads" } } ``` **Available Rooms:** - `downloads` - Download progress and status updates ### Server Message Format ```json { "type": "download_progress", "timestamp": "2025-12-13T10:30:00.000Z", "data": { "download_id": "uuid-here", "key": "attack-on-titan", "folder": "Attack on Titan (2013)", "percent": 45.2, "speed_mbps": 2.5, "eta_seconds": 180 } } ``` ### WebSocket Status Endpoint **GET /ws/status** Returns WebSocket service status. **Response (200 OK):** ```json { "status": "operational", "active_connections": 5, "supported_message_types": [ "download_progress", "download_complete", "download_failed", "queue_status", "connected", "ping", "pong", "error" ] } ``` Source: [src/server/api/websocket.py](../src/server/api/websocket.py#L238-L260) --- ## 10. Data Models ### Download Item ```json { "id": "uuid-string", "serie_id": "attack-on-titan", "serie_folder": "Attack on Titan (2013)", "serie_name": "Attack on Titan", "episode": { "season": 1, "episode": 1, "title": "To You, in 2000 Years" }, "status": "pending", "priority": "NORMAL", "added_at": "2025-12-13T10:00:00Z", "started_at": null, "completed_at": null, "progress": null, "error": null, "retry_count": 0, "source_url": null } ``` **Status Values:** `pending`, `downloading`, `paused`, `completed`, `failed`, `cancelled` **Priority Values:** `LOW`, `NORMAL`, `HIGH` Source: [src/server/models/download.py](../src/server/models/download.py#L63-L118) ### Episode Identifier ```json { "season": 1, "episode": 1, "title": "Episode Title" } ``` Source: [src/server/models/download.py](../src/server/models/download.py#L36-L41) ### Download Progress ```json { "percent": 45.2, "downloaded_mb": 256.0, "total_mb": 512.0, "speed_mbps": 2.5, "eta_seconds": 180 } ``` Source: [src/server/models/download.py](../src/server/models/download.py#L44-L60) --- ## 11. Error Handling ### HTTP Status Codes | Code | Meaning | When Used | | ---- | --------------------- | --------------------------------- | | 200 | OK | Successful request | | 201 | Created | Resource created | | 204 | No Content | Successful deletion | | 400 | Bad Request | Invalid request body/parameters | | 401 | Unauthorized | Missing or invalid authentication | | 403 | Forbidden | Insufficient permissions | | 404 | Not Found | Resource does not exist | | 422 | Unprocessable Entity | Validation error | | 429 | Too Many Requests | Rate limit exceeded | | 500 | Internal Server Error | Server-side error | ### Error Response Format ```json { "success": false, "error": "VALIDATION_ERROR", "message": "Human-readable error message", "details": { "field": "Additional context" }, "request_id": "uuid-for-tracking" } ``` Source: [src/server/middleware/error_handler.py](../src/server/middleware/error_handler.py#L26-L56) ### Common Error Codes | Error Code | HTTP Status | Description | | ---------------------- | ----------- | ------------------------------ | | `AUTHENTICATION_ERROR` | 401 | Invalid or missing credentials | | `AUTHORIZATION_ERROR` | 403 | Insufficient permissions | | `VALIDATION_ERROR` | 422 | Request validation failed | | `NOT_FOUND_ERROR` | 404 | Resource not found | | `CONFLICT_ERROR` | 409 | Resource conflict | | `RATE_LIMIT_ERROR` | 429 | Rate limit exceeded | --- ## 12. Rate Limiting ### Authentication Endpoints | Endpoint | Limit | Window | | ---------------------- | ---------- | ---------- | | `POST /api/auth/login` | 5 requests | 60 seconds | | `POST /api/auth/setup` | 5 requests | 60 seconds | Source: [src/server/middleware/auth.py](../src/server/middleware/auth.py#L143-L162) ### Origin-Based Limiting All endpoints from the same origin are limited to 60 requests per minute per origin. Source: [src/server/middleware/auth.py](../src/server/middleware/auth.py#L115-L133) ### Rate Limit Response ```json { "detail": "Too many authentication attempts, try again later" } ``` HTTP Status: 429 Too Many Requests --- ## 13. Pagination The anime list endpoint supports pagination. **Query Parameters:** | Parameter | Default | Max | Description | |-----------|---------|-----|-------------| | `page` | 1 | - | Page number (1-indexed) | | `per_page` | 20 | 1000 | Items per page | **Example:** ``` GET /api/anime?page=2&per_page=50 ``` Source: [src/server/api/anime.py](../src/server/api/anime.py#L180-L220) ## 7. Anime Settings Endpoints (New) Replaces the old "NFO Diagnostics" page with a per-anime settings UI that views and edits anime metadata directly in the database. Source: [src/server/api/anime.py](../src/server/api/anime.py) ### GET /api/anime/{anime_key}/settings Return the full editable settings payload for a single anime series. **Authentication:** Required **Path Parameters:** - `anime_key` (string): Series unique key (e.g., `attack-on-titan`) **Response (200 OK):** [`AnimeSettingsResponse`](../src/server/models/anime.py) ```json { "key": "attack-on-titan", "name": "Attack on Titan", "site": "aniworld.to", "folder": "Attack on Titan (2013)", "year": 2013, "tmdb_id": 1429, "tvdb_id": 789, "has_nfo": true, "nfo_path": "/anime/Attack on Titan (2013)/tvshow.nfo", "nfo_created_at": "2026-01-15T10:30:00+00:00", "nfo_updated_at": "2026-01-15T10:30:00+00:00", "loading_status": "completed", "episode_count": 25, "missing_episode_count": 0 } ``` **Errors:** - `401 Unauthorized` — Not authenticated. - `404 Not Found` — Series with the given key does not exist. ### PUT /api/anime/{anime_key}/settings Update editable fields for a single anime series. Optional flags control whether the on-disk folder is renamed and whether `tvshow.nfo` is regenerated. **Authentication:** Required **Request Body** ([`AnimeSettingsUpdateRequest`](../src/server/models/anime.py)): | Field | Type | Required | Description | | --- | --- | --- | --- | | `name` | string | no | Display name (1–500 chars) | | `folder` | string | no | Filesystem folder name | | `tmdb_id` | int | no | TMDB ID (positive integer, max 10 digits) | | `tvdb_id` | int | no | TVDB ID (positive integer, max 10 digits) | | `site` | string | no | Provider site URL | | `apply_to_nfo` | bool | no | If true, regenerate `tvshow.nfo` with the new values (requires `tmdb_id`) | | `rename_disk` | bool | no | If true and `folder` changed, rename the folder on disk | **Example:** ```bash curl -X PUT "http://127.0.0.1:8000/api/anime/attack-on-titan/settings" \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{"tmdb_id": 9999, "apply_to_nfo": true}' ``` **Response (200 OK):** Updated [`AnimeSettingsResponse`](../src/server/models/anime.py). **Errors:** - `400 Bad Request` — `apply_to_nfo=true` but the series has no `tmdb_id`. - `401 Unauthorized` — Not authenticated. - `404 Not Found` — Series with the given key does not exist. - `422 Unprocessable Entity` — Validation failure (empty name, invalid folder, non-positive `tmdb_id`/`tvdb_id`, oversized id, path traversal). ### POST /api/anime/{anime_key}/regenerate-nfo Regenerate `tvshow.nfo` for a single anime using TMDB. **Authentication:** Required **Response (200 OK):** [`AnimeSettingsRegenerateNfoResponse`](../src/server/models/anime.py) ```json { "success": true, "message": "NFO regenerated. 2 tags updated.", "nfo_path": "/anime/Attack on Titan (2013)/tvshow.nfo", "repaired_tags": ["title", "tmdbid"] } ``` **Errors:** - `400 Bad Request` — Series has no `tmdb_id`. - `401 Unauthorized` — Not authenticated. - `404 Not Found` — Series with the given key does not exist. - `500 Internal Server Error` — TMDB or NFO regeneration failure.