- Replace asyncio sleep loop with APScheduler AsyncIOScheduler + CronTrigger
- Add schedule_time (HH:MM), schedule_days (days of week), auto_download_after_rescan fields to SchedulerConfig
- Add _auto_download_missing() to queue missing episodes after rescan
- Reload config live via reload_config(SchedulerConfig) without restart
- Update GET/POST /api/scheduler/config to return {success, config, status} envelope
- Add day-of-week pill toggles to Settings -> Scheduler section in UI
- Update JS loadSchedulerConfig / saveSchedulerConfig for new API shape
- Add 29 unit tests for SchedulerConfig model, 18 unit tests for SchedulerService
- Rewrite 23 endpoint tests and 36 integration tests for APScheduler behaviour
- Coverage: 96% api/scheduler, 95% scheduler_service, 90% total (>= 80% threshold)
- Update docs: API.md, CONFIGURATION.md, features.md, CHANGELOG.md
35 KiB
API Documentation
Document Purpose
This document provides comprehensive REST API and WebSocket reference for the Aniworld application.
Source: src/server/fastapi_app.py
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
Authentication
The API uses JWT Bearer Token authentication.
Header Format:
Authorization: Bearer <jwt_token>
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
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
POST /api/auth/setup
Initial setup endpoint to configure the master password. Can only be called once.
Request Body:
{
"master_password": "string (min 8 chars, mixed case, number, special char)",
"anime_directory": "string (optional, path to anime folder)"
}
Response (201 Created):
{
"status": "ok"
}
Errors:
400 Bad Request- Master password already configured or invalid password
Source: src/server/api/auth.py
POST /api/auth/login
Validate master password and return JWT token.
Request Body:
{
"password": "string",
"remember": false
}
Response (200 OK):
{
"access_token": "eyJ...",
"token_type": "bearer",
"expires_at": "2025-12-14T10:30:00Z"
}
Errors:
401 Unauthorized- Invalid credentials429 Too Many Requests- Account locked due to failed attempts
Source: src/server/api/auth.py
POST /api/auth/logout
Logout by revoking token.
Response (200 OK):
{
"status": "ok",
"message": "Logged out successfully"
}
Source: src/server/api/auth.py
GET /api/auth/status
Return whether master password is configured and if caller is authenticated.
Response (200 OK):
{
"configured": true,
"authenticated": true
}
Source: src/server/api/auth.py
3. Anime Endpoints
Prefix: /api/anime
Source: src/server/api/anime.py
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):
{
"directory": "/path/to/anime",
"series_count": 42
}
Source: src/server/api/anime.py
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: no_episodes (shows only series with missing episodes - episodes in DB that haven't been downloaded yet) |
Filter Details:
no_episodes: Returns series that have at least one episode in the database withis_downloaded=False- Episodes in the database represent MISSING episodes (from episodeDict during scanning)
is_downloaded=Falsemeans the episode file was not found in the folder- This effectively shows series where no video files were found for missing episodes
Response (200 OK):
[
{
"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:
GET /api/anime?filter=no_episodes
Source: src/server/api/anime.py
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):
[
{
"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
POST /api/anime/search
Search via POST body.
Request Body:
{
"query": "attack on titan"
}
Response: Same as GET /api/anime/search
Source: src/server/api/anime.py
POST /api/anime/add
Add a new series to the library with automatic database persistence, folder creation, and episode scanning.
Authentication: Required
Request Body:
{
"link": "https://aniworld.to/anime/stream/attack-on-titan",
"name": "Attack on Titan"
}
Response (200 OK):
{
"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:
- Validates the request (link format, name)
- Creates Serie object with sanitized folder name
- Saves to database via AnimeDBService
- Creates folder using sanitized display name (not internal key)
- Performs targeted episode scan for this anime only
- 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
POST /api/anime/rescan
Trigger a rescan of the local library.
Authentication: Required
Response (200 OK):
{
"success": true,
"message": "Rescan started successfully"
}
Source: src/server/api/anime.py
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):
{
"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
4. Download Queue Endpoints
Prefix: /api/queue
Source: src/server/api/download.py
GET /api/queue/status
Get current download queue status and statistics.
Authentication: Required
Response (200 OK):
{
"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
POST /api/queue/add
Add episodes to the download queue.
Authentication: Required
Request Body:
{
"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):
{
"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
POST /api/queue/start
Start automatic queue processing.
Authentication: Required
Response (200 OK):
{
"status": "success",
"message": "Queue processing started"
}
Source: src/server/api/download.py
POST /api/queue/stop
Stop processing new downloads from queue.
Authentication: Required
Response (200 OK):
{
"status": "success",
"message": "Queue processing stopped (current download will continue)"
}
Source: src/server/api/download.py
POST /api/queue/pause
Pause queue processing (alias for stop).
Authentication: Required
Response (200 OK):
{
"status": "success",
"message": "Queue processing paused"
}
Source: src/server/api/download.py
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
DELETE /api/queue
Remove multiple items from the download queue.
Authentication: Required
Request Body:
{
"item_ids": ["uuid1", "uuid2"]
}
Response (204 No Content)
Source: src/server/api/download.py
DELETE /api/queue/completed
Clear completed downloads from history.
Authentication: Required
Response (200 OK):
{
"status": "success",
"message": "Cleared 5 completed item(s)",
"count": 5
}
Source: src/server/api/download.py
DELETE /api/queue/failed
Clear failed downloads from history.
Authentication: Required
Response (200 OK):
{
"status": "success",
"message": "Cleared 2 failed item(s)",
"count": 2
}
Source: src/server/api/download.py
DELETE /api/queue/pending
Clear all pending downloads from the queue.
Authentication: Required
Response (200 OK):
{
"status": "success",
"message": "Removed 10 pending item(s)",
"count": 10
}
Source: src/server/api/download.py
POST /api/queue/reorder
Reorder items in the pending queue.
Authentication: Required
Request Body:
{
"item_ids": ["uuid3", "uuid1", "uuid2"]
}
Response (200 OK):
{
"status": "success",
"message": "Queue reordered with 3 items"
}
Source: src/server/api/download.py
POST /api/queue/retry
Retry failed downloads.
Authentication: Required
Request Body:
{
"item_ids": ["uuid1", "uuid2"]
}
Pass empty item_ids array to retry all failed items.
Response (200 OK):
{
"status": "success",
"message": "Retrying 2 failed item(s)",
"retried_count": 2,
"retried_ids": ["uuid1", "uuid2"]
}
Source: src/server/api/download.py
5. Configuration Endpoints
Prefix: /api/config
Source: src/server/api/config.py
GET /api/config
Return current application configuration.
Authentication: Required
Response (200 OK):
{
"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
PUT /api/config
Apply an update to the configuration.
Authentication: Required
Request Body:
{
"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
POST /api/config/validate
Validate a configuration without applying it.
Authentication: Required
Request Body: Full AppConfig object
Response (200 OK):
{
"valid": true,
"errors": []
}
Source: src/server/api/config.py
GET /api/config/backups
List all available configuration backups.
Authentication: Required
Response (200 OK):
[
{
"name": "config_backup_20251213_090130.json",
"size": 1024,
"created": "2025-12-13T09:01:30Z"
}
]
Source: src/server/api/config.py
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):
{
"name": "config_backup_20251213_090130.json",
"message": "Backup created successfully"
}
Source: src/server/api/config.py
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
DELETE /api/config/backups/{backup_name}
Delete a configuration backup.
Authentication: Required
Response (200 OK):
{
"message": "Backup 'config_backup_20251213.json' deleted successfully"
}
Source: src/server/api/config.py
POST /api/config/directory
Update anime directory configuration.
Authentication: Required
Request Body:
{
"directory": "/path/to/anime"
}
Response (200 OK):
{
"message": "Anime directory updated successfully",
"synced_series": 15
}
Source: src/server/api/config.py
6. NFO Management Endpoints
Prefix: /api/nfo
Source: src/server/api/nfo.py
These endpoints manage tvshow.nfo metadata files and associated media (poster, logo, fanart) for anime series. NFO files use Kodi/XBMC format and are scraped from TMDB API.
Prerequisites:
- TMDB API key must be configured in settings
- NFO service returns 503 if API key not configured
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):
{
"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 authenticated404 Not Found- Series not found503 Service Unavailable- TMDB API key not configured
Source: src/server/api/nfo.py
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:
{
"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 searchdownload_poster(boolean, default: true): Download poster.jpgdownload_logo(boolean, default: true): Download logo.pngdownload_fanart(boolean, default: true): Download fanart.jpgoverwrite_existing(boolean, default: false): Overwrite existing NFO
Response (200 OK):
{
"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 authenticated404 Not Found- Series not found409 Conflict- NFO already exists (useoverwrite_existing: true)503 Service Unavailable- TMDB API error or key not configured
Source: src/server/api/nfo.py
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):
{
"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 authenticated404 Not Found- Series or NFO not found (use create endpoint)503 Service Unavailable- TMDB API error
Source: src/server/api/nfo.py
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):
{
"serie_id": "one-piece",
"serie_folder": "One Piece (1999)",
"content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<tvshow>...</tvshow>",
"file_size": 2048,
"last_modified": "2026-01-15T10:30:00"
}
Errors:
401 Unauthorized- Not authenticated404 Not Found- Series or NFO not found
Source: src/server/api/nfo.py
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):
{
"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 authenticated404 Not Found- Series not found
Source: src/server/api/nfo.py
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:
{
"download_poster": true,
"download_logo": true,
"download_fanart": true
}
Response (200 OK):
{
"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 authenticated404 Not Found- Series or NFO not found (NFO required for TMDB ID)503 Service Unavailable- TMDB API error
Source: src/server/api/nfo.py
POST /api/nfo/batch/create
Batch create NFO files for multiple series.
Authentication: Required
Request Body:
{
"serie_ids": ["one-piece", "naruto", "bleach"],
"download_media": true,
"skip_existing": true,
"max_concurrent": 3
}
Fields:
serie_ids(array of strings): Series identifiers to processdownload_media(boolean, default: true): Download media filesskip_existing(boolean, default: true): Skip series with existing NFOsmax_concurrent(integer, 1-10, default: 3): Number of concurrent operations
Response (200 OK):
{
"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 authenticated503 Service Unavailable- TMDB API key not configured
Source: src/server/api/nfo.py
GET /api/nfo/missing
Get list of series without NFO files.
Authentication: Required
Response (200 OK):
{
"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 authenticated503 Service Unavailable- TMDB API key not configured
Source: src/server/api/nfo.py
7. Scheduler Endpoints
Prefix: /api/scheduler
All GET/POST config responses share the same envelope:
{
"success": true,
"config": { ... },
"status": { ... }
}
Source: src/server/api/scheduler.py
GET /api/scheduler/config
Get current scheduler configuration and runtime status.
Authentication: Required
Response (200 OK):
{
"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):
{
"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_timemust matchHH:MM(00:00–23:59)schedule_daysentries must be one ofmon tue wed thu fri sat suninterval_minutesmust be ≥ 1
POST /api/scheduler/trigger-rescan
Manually trigger a library rescan (and auto-download if configured).
Authentication: Required
Response (200 OK):
{
"message": "Rescan started successfully"
}
Error responses:
503— SeriesApp not yet initialised500— Rescan failed unexpectedly
8. Health Check Endpoints
Prefix: /health
Source: src/server/api/health.py
GET /health
Basic health check endpoint.
Authentication: Not required
Response (200 OK):
{
"status": "healthy",
"timestamp": "2025-12-13T10:30:00.000Z",
"version": "1.0.0"
}
Source: src/server/api/health.py
GET /health/detailed
Comprehensive health check with database, filesystem, and system metrics.
Authentication: Not required
Response (200 OK):
{
"status": "healthy",
"timestamp": "2025-12-13T10:30:00.000Z",
"version": "1.0.0",
"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
9. WebSocket Protocol
Endpoint: /ws/connect
Source: src/server/api/websocket.py
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
Room Subscriptions
Clients can join/leave rooms to receive specific updates.
Join Room:
{
"action": "join",
"data": { "room": "downloads" }
}
Leave Room:
{
"action": "leave",
"data": { "room": "downloads" }
}
Available Rooms:
downloads- Download progress and status updates
Server Message Format
{
"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):
{
"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
10. Data Models
Download Item
{
"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
Episode Identifier
{
"season": 1,
"episode": 1,
"title": "Episode Title"
}
Source: src/server/models/download.py
Download Progress
{
"percent": 45.2,
"downloaded_mb": 256.0,
"total_mb": 512.0,
"speed_mbps": 2.5,
"eta_seconds": 180
}
Source: src/server/models/download.py
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
{
"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
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
Origin-Based Limiting
All endpoints from the same origin are limited to 60 requests per minute per origin.
Source: src/server/middleware/auth.py
Rate Limit Response
{
"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