# 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: `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)

---

## 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#L1-L684)

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):**

```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": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<tvshow>...</tvshow>",
    "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)
