# Kyoo Backend API Documentation **Base URL**: `http://localhost:6400/api` **Content-Type**: `application/json` --- ## Table of Contents - [Response Format](#response-format) - [Authentication](#authentication) - [Endpoints](#endpoints) - [Root](#root) - [Documentation](#documentation) - [Media](#media) - [Series Episodes](#series-episodes) - [Music Tracks](#music-tracks) - [Cast](#cast) - [Adult Cast](#adult-cast) - [Images](#images) - [Settings](#settings) --- ## Response Format ### Success Response ```json { "success": true, "data": { ... } } ``` ### Error Response ```json { "success": false, "error": "Error message" } ``` ### Paginated Response ```json { "success": true, "data": { "items": [ ... ], "total": 100, "page": 1, "limit": 20, "totalPages": 5 } } ``` ### HTTP Status Codes - `200` - Success - `201` - Created - `400` - Bad Request - `404` - Not Found - `405` - Method Not Allowed - `500` - Internal Server Error --- ## Authentication Currently, the API does not require authentication. All endpoints are publicly accessible. --- ## Endpoints ### Root #### Get API Information **Endpoint**: `GET /api` **Description**: Returns API version and available endpoints. **Response**: ```json { "success": true, "message": "Media API v1.0", "endpoints": { "GET /api/docs": "Automatische API-Dokumentation", "GET /api/images/*": "Bilder abrufen", "GET /api/media": "Alle Medien abrufen", "GET /api/media/:id": "Ein Medium abrufen", "POST /api/media": "Neues Medium erstellen", "PUT /api/media/:id": "Medium aktualisieren", "DELETE /api/media/:id": "Medium löschen", "GET /api/cast": "Alle Cast-Mitglieder abrufen", "GET /api/cast/:id": "Cast-Mitglied abrufen", "GET /api/cast/:id/media": "Alle Medien eines Cast-Mitglieds abrufen", "POST /api/cast": "Neues Cast-Mitglied erstellen", "PUT /api/cast/:id": "Cast-Mitglied aktualisieren", "DELETE /api/cast/:id": "Cast-Mitglied löschen", "GET /api/settings": "Einstellungen abrufen", "PUT /api/settings": "Einstellungen aktualisieren" } } ``` --- ### Documentation #### Get Auto-Generated Documentation **Endpoint**: `GET /api/docs` **Description**: Returns automatically generated API documentation. **Response**: ```json { "success": true, "data": { ... } } ``` --- ### Media #### Get All Media **Endpoint**: `GET /api/media` **Query Parameters**: - `page` (integer, optional) - Page number (default: 1) - `limit` (integer, optional) - Items per page (default: 20) - `category` (string, optional) - Filter by category (e.g., "movie", "tv", "music", "game") - `type` (string, optional) - Filter by type (e.g., "Movie", "TV", "Album", "Game") - `search` (string, optional) - Search in title and description **Example Request**: ```bash GET /api/media?page=1&limit=20&category=movie&search=action ``` **Response**: ```json { "success": true, "data": { "items": [ { "id": 1, "title": "Movie Title", "cleanname": "movie-title", "year": 2024, "poster": "/images/movies/poster_xxx.webp", "banner": "/images/movies/banner_xxx.webp", "description": "Description text", "rating": 8.5, "category": "movie", "type": "Movie", "status": "released", "aspectRatio": "16:9", "runtime": 120, "director": "Director Name", "writer": "Writer Name", "source": "netflix", "releaseDate": "2024-01-01", "createdAt": "2024-01-01 00:00:00", "updatedAt": "2024-01-01 00:00:00" } ], "total": 100, "page": 1, "limit": 20, "totalPages": 5 } } ``` #### Get Single Media **Endpoint**: `GET /api/media/:id` **Path Parameters**: - `id` (integer, required) - Media ID **Example Request**: ```bash GET /api/media/1 ``` **Response**: ```json { "success": true, "data": { "id": 1, "title": "Movie Title", "cleanname": "movie-title", "year": 2024, "poster": "/images/movies/poster_xxx.webp", "banner": "/images/movies/banner_xxx.webp", "description": "Description text", "rating": 8.5, "category": "movie", "type": "Movie", "status": "released", "aspectRatio": "16:9", "runtime": 120, "director": "Director Name", "writer": "Writer Name", "source": "netflix", "releaseDate": "2024-01-01", "genres": ["Action", "Drama"], "tags": ["tag1", "tag2"], "studios": ["Studio1"], "staff": [ { "id": 1, "name": "Actor Name", "photo": "/images/cast/photo_xxx.webp", "role": "Actor", "characterName": "Character", "characterImage": "/images/characters/char_xxx.webp" } ], "createdAt": "2024-01-01 00:00:00", "updatedAt": "2024-01-01 00:00:00" } } ``` #### Create Media **Endpoint**: `POST /api/media` **Request Body**: ```json { "title": "Movie Title", "year": 2024, "poster": "base64_encoded_image_or_url", "banner": "base64_encoded_image_or_url", "description": "Description text", "rating": 8.5, "category": "movie", "type": "Movie", "status": "released", "aspectRatio": "16:9", "runtime": 120, "director": "Director Name", "writer": "Writer Name", "source": "netflix", "releaseDate": "2024-01-01", "genres": ["Action", "Drama"], "tags": ["tag1", "tag2"], "studios": ["Studio1"], "staff": [ { "id": 1, "role": "Actor", "characterName": "Character", "characterImage": "base64_encoded_image_or_url" } ] } ``` **Note**: If `staff` includes a cast member by `name` instead of `id`, the cast member will be automatically created if it doesn't exist. **Response** (201 Created): ```json { "success": true, "data": { "id": 1 } } ``` **Response** (200 OK - if media already exists): ```json { "success": true, "data": { "id": 1, "message": "Media already exists" } } ``` #### Update Media **Endpoint**: `PUT /api/media/:id` **Path Parameters**: - `id` (integer, required) - Media ID **Request Body**: Same as create, but all fields are optional. **Response**: ```json { "success": true, "data": { "id": 1 } } ``` #### Delete Media **Endpoint**: `DELETE /api/media/:id` **Path Parameters**: - `id` (integer, required) - Media ID **Response**: ```json { "success": true, "message": "Media deleted successfully" } ``` --- ### Series Episodes #### Get All Episodes **Endpoint**: `GET /api/media/:id/episodes` **Path Parameters**: - `id` (integer, required) - Series media ID **Query Parameters**: - `season` (integer, optional) - Filter by season number **Example Request**: ```bash GET /api/media/1/episodes?season=1 ``` **Response**: ```json { "success": true, "data": { "items": [ { "id": 1, "media_id": 1, "season": 1, "episode": 1, "title": "Episode Title", "overview": "Episode description", "airDate": "2024-01-01", "runtime": 45, "poster": "/images/episodes/poster_xxx.webp" } ] } } ``` #### Get Single Episode **Endpoint**: `GET /api/media/:id/episodes/:episodeId` **Path Parameters**: - `id` (integer, required) - Series media ID - `episodeId` (integer, required) - Episode ID **Response**: ```json { "success": true, "data": { "id": 1, "media_id": 1, "season": 1, "episode": 1, "title": "Episode Title", "overview": "Episode description", "airDate": "2024-01-01", "runtime": 45, "poster": "/images/episodes/poster_xxx.webp" } } ``` #### Add Episode **Endpoint**: `POST /api/media/:id/episodes` **Path Parameters**: - `id` (integer, required) - Series media ID **Request Body**: ```json { "season": 1, "episode": 1, "title": "Episode Title", "overview": "Episode description", "airDate": "2024-01-01", "runtime": 45, "poster": "base64_encoded_image_or_url" } ``` **Response** (201 Created): ```json { "success": true, "data": { "id": 1 } } ``` #### Update Episode **Endpoint**: `PUT /api/media/:id/episodes/:episodeId` **Path Parameters**: - `id` (integer, required) - Series media ID - `episodeId` (integer, required) - Episode ID **Request Body**: Same as add episode, but all fields are optional. **Response**: ```json { "success": true, "data": { "id": 1 } } ``` #### Delete Episode **Endpoint**: `DELETE /api/media/:id/episodes/:episodeId` **Path Parameters**: - `id` (integer, required) - Series media ID - `episodeId` (integer, required) - Episode ID **Response**: ```json { "success": true, "message": "Episode deleted successfully" } ``` --- ### Music Tracks #### Get All Tracks **Endpoint**: `GET /api/media/:id/tracks` **Path Parameters**: - `id` (integer, required) - Album media ID **Response**: ```json { "success": true, "data": { "items": [ { "id": 1, "media_id": 1, "track": 1, "title": "Track Title", "duration": 240, "artists": ["Artist1", "Artist2"] } ] } } ``` #### Get Single Track **Endpoint**: `GET /api/media/:id/tracks/:trackId` **Path Parameters**: - `id` (integer, required) - Album media ID - `trackId` (integer, required) - Track ID **Response**: ```json { "success": true, "data": { "id": 1, "media_id": 1, "track": 1, "title": "Track Title", "duration": 240, "artists": ["Artist1", "Artist2"] } } ``` #### Add Track **Endpoint**: `POST /api/media/:id/tracks` **Path Parameters**: - `id` (integer, required) - Album media ID **Request Body**: ```json { "track": 1, "title": "Track Title", "duration": 240, "artists": ["Artist1", "Artist2"] } ``` **Response** (201 Created): ```json { "success": true, "data": { "id": 1 } } ``` #### Update Track **Endpoint**: `PUT /api/media/:id/tracks/:trackId` **Path Parameters**: - `id` (integer, required) - Album media ID - `trackId` (integer, required) - Track ID **Request Body**: Same as add track, but all fields are optional. **Response**: ```json { "success": true, "data": { "id": 1 } } ``` #### Delete Track **Endpoint**: `DELETE /api/media/:id/tracks/:trackId` **Path Parameters**: - `id` (integer, required) - Album media ID - `trackId` (integer, required) - Track ID **Response**: ```json { "success": true, "message": "Track deleted successfully" } ``` --- ### Cast #### Get All Cast **Endpoint**: `GET /api/cast` **Query Parameters**: - `page` (integer, optional) - Page number (default: 1) - `limit` (integer, optional) - Items per page (default: 20) - `search` (string, optional) - Search by name **Example Request**: ```bash GET /api/cast?page=1&limit=20&search=john ``` **Response**: ```json { "success": true, "data": { "items": [ { "id": 1, "name": "Actor Name", "cleanname": "actor-name", "photo": "/images/cast/photo_xxx.webp", "bio": "Biography text", "birthDate": "1990-01-01", "birthPlace": "City, Country", "occupations": ["Actor", "Director"], "filmography": [ { "id": 1, "title": "Movie Title", "year": 2024, "poster": "/images/movies/poster_xxx.webp", "category": "movie", "type": "Movie", "role": "Actor", "characterName": "Character" } ], "media_types": ["movie", "tv"], "createdAt": "2024-01-01 00:00:00", "updatedAt": "2024-01-01 00:00:00" } ], "total": 100, "page": 1, "limit": 20 } } ``` #### Get Single Cast **Endpoint**: `GET /api/cast/:id` **Path Parameters**: - `id` (integer, required) - Cast ID **Response**: ```json { "success": true, "data": { "id": 1, "name": "Actor Name", "cleanname": "actor-name", "photo": "/images/cast/photo_xxx.webp", "bio": "Biography text", "birthDate": "1990-01-01", "birthPlace": "City, Country", "occupations": ["Actor", "Director"], "filmography": [ { "id": 1, "title": "Movie Title", "year": 2024, "poster": "/images/movies/poster_xxx.webp", "category": "movie", "type": "Movie", "role": "Actor", "characterName": "Character" } ], "adult_specifics": { "id": 1, "cast_id": 1, "ethnicity": "Asian", "hair_color": "Black", "eye_color": "Brown", "height": 170, "weight": 60 }, "createdAt": "2024-01-01 00:00:00", "updatedAt": "2024-01-01 00:00:00" } } ``` #### Get Cast Media **Endpoint**: `GET /api/cast/:id/media` **Path Parameters**: - `id` (integer, required) - Cast ID **Response**: ```json { "success": true, "data": { "items": [ { "id": 1, "title": "Movie Title", "year": 2024, "poster": "/images/movies/poster_xxx.webp", "category": "movie", "type": "Movie", "role": "Actor", "characterName": "Character" } ] } } ``` #### Create Cast **Endpoint**: `POST /api/cast` **Request Body**: ```json { "name": "Actor Name", "photo": "base64_encoded_image_or_url", "bio": "Biography text", "birthDate": "1990-01-01", "birthPlace": "City, Country", "occupations": ["Actor", "Director"] } ``` **Response** (201 Created): ```json { "success": true, "data": { "id": 1 } } ``` **Response** (200 OK - if cast already exists): ```json { "success": true, "data": { "id": 1, "message": "Cast already exists" } } ``` #### Update Cast **Endpoint**: `PUT /api/cast/:id` **Path Parameters**: - `id` (integer, required) - Cast ID **Request Body**: Same as create, but all fields are optional. **Response**: ```json { "success": true, "data": { "id": 1 } } ``` #### Delete Cast **Endpoint**: `DELETE /api/cast/:id` **Path Parameters**: - `id` (integer, required) - Cast ID **Response**: ```json { "success": true, "message": "Cast member deleted successfully" } ``` --- ### Adult Cast #### Get All Adult Cast **Endpoint**: `GET /api/cast/adult` **Query Parameters**: - `page` (integer, optional) - Page number (default: 1) - `limit` (integer, optional) - Items per page (default: 20) - `search` (string, optional) - Search by name - `ethnicity` (string, optional) - Filter by ethnicity - `hair_color` (string, optional) - Filter by hair color **Example Request**: ```bash GET /api/cast/adult?page=1&limit=20ðnicity=Asian ``` **Response**: ```json { "success": true, "data": { "items": [ { "id": 1, "name": "Actor Name", "cleanname": "actor-name", "photo": "/images/cast/photo_xxx.webp", "bio": "Biography text", "birthDate": "1990-01-01", "birthPlace": "City, Country", "ethnicity": "Asian", "hair_color": "Black", "eye_color": "Brown", "height": 170, "weight": 60, "createdAt": "2024-01-01 00:00:00", "updatedAt": "2024-01-01 00:00:00" } ], "total": 100, "page": 1, "limit": 20 } } ``` #### Get Single Adult Cast **Endpoint**: `GET /api/cast/adult/:id` **Path Parameters**: - `id` (integer, required) - Cast ID **Response**: ```json { "success": true, "data": { "id": 1, "name": "Actor Name", "cleanname": "actor-name", "photo": "/images/cast/photo_xxx.webp", "bio": "Biography text", "birthDate": "1990-01-01", "birthPlace": "City, Country", "ethnicity": "Asian", "hair_color": "Black", "eye_color": "Brown", "height": 170, "weight": 60, "createdAt": "2024-01-01 00:00:00", "updatedAt": "2024-01-01 00:00:00" } } ``` #### Create Adult Cast **Endpoint**: `POST /api/cast/adult` **Request Body**: ```json { "name": "Actor Name", "photo": "base64_encoded_image_or_url", "bio": "Biography text", "birthDate": "1990-01-01", "birthPlace": "City, Country", "ethnicity": "Asian", "hair_color": "Black", "eye_color": "Brown", "height": 170, "weight": 60 } ``` **Response** (201 Created): ```json { "success": true, "data": { "id": 1 } } ``` #### Update Adult Cast **Endpoint**: `PUT /api/cast/adult/:id` **Path Parameters**: - `id` (integer, required) - Cast ID **Request Body**: Same as create, but all fields are optional. **Response**: ```json { "success": true, "data": { "id": 1 } } ``` #### Delete Adult Cast Specifics **Endpoint**: `DELETE /api/cast/adult/:id` **Path Parameters**: - `id` (integer, required) - Cast ID **Note**: This only deletes the adult-specific attributes, not the cast member itself. **Response**: ```json { "success": true, "message": "Adult specifics deleted successfully" } ``` --- ### Images #### Serve Image **Endpoint**: `GET /api/images/*` **Description**: Serves images directly from the storage. Images are organized by type (movies, games, cast, etc.). **Example Requests**: ```bash GET /api/images/movies/poster_xxx.webp GET /api/images/cast/photo_xxx.webp GET /api/images/episodes/poster_xxx.webp ``` **Response**: Binary image data (WebP format) --- ### Settings #### Get Settings **Endpoint**: `GET /api/settings` **Description**: Retrieves application settings. **Response**: ```json { "success": true, "data": { "id": 1, "key": "value", "updatedAt": "2024-01-01 00:00:00" } } ``` #### Update Settings **Endpoint**: `PUT /api/settings` **Request Body**: ```json { "key": "value" } ``` **Response**: ```json { "success": true, "data": { "id": 1, "key": "value", "updatedAt": "2024-01-01 00:00:00" } } ``` --- ## Image Handling ### Image Formats Images can be provided in two ways: 1. **Base64-encoded string**: The API will automatically decode and save the image 2. **URL**: The API will store the URL as-is ### Image Storage Images are stored in the following structure: ``` /images/ ├── movies/ │ ├── poster_*.webp │ └── banner_*.webp ├── games/ │ ├── poster_*.webp │ └── banner_*.webp ├── cast/ │ └── photo_*.webp ├── characters/ │ └── char_*.webp └── episodes/ └── poster_*.webp ``` ### Image Processing - All images are automatically converted to WebP format - Old images are deleted when updating - Images are served directly via the `/api/images/*` endpoint --- ## Search & Filtering ### Clean Names The API uses "clean names" for deduplication. A clean name is generated from the title/name by: - Converting to lowercase - Replacing special characters with hyphens - Removing leading/trailing hyphens - Replacing multiple consecutive hyphens with a single hyphen Example: "The Dark Knight" → "the-dark-knight" ### Deduplication When creating media or cast members: - The API checks if an item with the same clean name already exists - If it exists, the existing ID is returned with a 200 status - If it doesn't exist, a new item is created with a 201 status --- ## Error Handling ### Common Errors #### 400 Bad Request ```json { "success": false, "error": "Invalid JSON" } ``` #### 404 Not Found ```json { "success": false, "error": "Media not found" } ``` #### 405 Method Not Allowed ```json { "success": false, "error": "Method not allowed" } ``` #### 500 Internal Server Error ```json { "success": false, "error": "Error message" } ``` --- ## CORS The API supports CORS with the following headers: ``` Access-Control-Allow-Origin: * Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS Access-Control-Allow-Headers: Content-Type ``` --- ## Rate Limiting Currently, there is no rate limiting implemented. --- ## Logging API logging can be enabled by setting the `API_LOGGING_ENABLED` environment variable to `true`. Logs are written to: - API requests/responses: `/var/www/html/logs/api.log` - PHP errors: `/var/www/html/logs/php_error.log`