Aniworld/docs/API.md
2025-12-15 14:07:04 +01:00

1177 lines
24 KiB
Markdown

# 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 <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](../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 term |
**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": ""
}
]
```
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.
**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
}
```
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)
---
## 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
},
"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": 30
},
"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. Scheduler Endpoints
Prefix: `/api/scheduler`
Source: [src/server/api/scheduler.py](../src/server/api/scheduler.py#L1-L122)
### GET /api/scheduler/config
Get current scheduler configuration.
**Authentication:** Required
**Response (200 OK):**
```json
{
"enabled": true,
"interval_minutes": 60
}
```
Source: [src/server/api/scheduler.py](../src/server/api/scheduler.py#L22-L42)
### POST /api/scheduler/config
Update scheduler configuration.
**Authentication:** Required
**Request Body:**
```json
{
"enabled": true,
"interval_minutes": 30
}
```
**Response (200 OK):** Updated scheduler configuration
Source: [src/server/api/scheduler.py](../src/server/api/scheduler.py#L45-L75)
### POST /api/scheduler/trigger-rescan
Manually trigger a library rescan.
**Authentication:** Required
**Response (200 OK):**
```json
{
"success": true,
"message": "Rescan started successfully"
}
```
Source: [src/server/api/scheduler.py](../src/server/api/scheduler.py#L78-L122)
---
## 7. 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.0"
}
```
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.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](../src/server/api/health.py#L164-L200)
---
## 8. 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)
---
## 9. 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)
---
## 10. 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 |
---
## 11. 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
---
## 12. 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)