Add comprehensive API_DOCS.md describing the Kyoo backend API (endpoints, request/response examples, image handling, authentication, error handling, CORS, logging) and a README.md with project overview, tech stack, quick start, API endpoints, data models, configuration and Docker instructions. Provides developers with usage examples, directory layout and troubleshooting steps for local deployment.
20 KiB
Kyoo Backend API Documentation
Base URL: http://localhost:6400/api
Content-Type: application/json
Table of Contents
Response Format
Success Response
{
"success": true,
"data": { ... }
}
Error Response
{
"success": false,
"error": "Error message"
}
Paginated Response
{
"success": true,
"data": {
"items": [ ... ],
"total": 100,
"page": 1,
"limit": 20,
"totalPages": 5
}
}
HTTP Status Codes
200- Success201- Created400- Bad Request404- Not Found405- Method Not Allowed500- 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:
{
"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:
{
"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:
GET /api/media?page=1&limit=20&category=movie&search=action
Response:
{
"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:
GET /api/media/1
Response:
{
"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:
{
"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):
{
"success": true,
"data": {
"id": 1
}
}
Response (200 OK - if media already exists):
{
"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:
{
"success": true,
"data": {
"id": 1
}
}
Delete Media
Endpoint: DELETE /api/media/:id
Path Parameters:
id(integer, required) - Media ID
Response:
{
"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:
GET /api/media/1/episodes?season=1
Response:
{
"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 IDepisodeId(integer, required) - Episode ID
Response:
{
"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:
{
"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):
{
"success": true,
"data": {
"id": 1
}
}
Update Episode
Endpoint: PUT /api/media/:id/episodes/:episodeId
Path Parameters:
id(integer, required) - Series media IDepisodeId(integer, required) - Episode ID
Request Body: Same as add episode, but all fields are optional.
Response:
{
"success": true,
"data": {
"id": 1
}
}
Delete Episode
Endpoint: DELETE /api/media/:id/episodes/:episodeId
Path Parameters:
id(integer, required) - Series media IDepisodeId(integer, required) - Episode ID
Response:
{
"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:
{
"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 IDtrackId(integer, required) - Track ID
Response:
{
"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:
{
"track": 1,
"title": "Track Title",
"duration": 240,
"artists": ["Artist1", "Artist2"]
}
Response (201 Created):
{
"success": true,
"data": {
"id": 1
}
}
Update Track
Endpoint: PUT /api/media/:id/tracks/:trackId
Path Parameters:
id(integer, required) - Album media IDtrackId(integer, required) - Track ID
Request Body: Same as add track, but all fields are optional.
Response:
{
"success": true,
"data": {
"id": 1
}
}
Delete Track
Endpoint: DELETE /api/media/:id/tracks/:trackId
Path Parameters:
id(integer, required) - Album media IDtrackId(integer, required) - Track ID
Response:
{
"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:
GET /api/cast?page=1&limit=20&search=john
Response:
{
"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:
{
"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:
{
"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:
{
"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):
{
"success": true,
"data": {
"id": 1
}
}
Response (200 OK - if cast already exists):
{
"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:
{
"success": true,
"data": {
"id": 1
}
}
Delete Cast
Endpoint: DELETE /api/cast/:id
Path Parameters:
id(integer, required) - Cast ID
Response:
{
"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 nameethnicity(string, optional) - Filter by ethnicityhair_color(string, optional) - Filter by hair color
Example Request:
GET /api/cast/adult?page=1&limit=20ðnicity=Asian
Response:
{
"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:
{
"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:
{
"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):
{
"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:
{
"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:
{
"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:
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:
{
"success": true,
"data": {
"id": 1,
"key": "value",
"updatedAt": "2024-01-01 00:00:00"
}
}
Update Settings
Endpoint: PUT /api/settings
Request Body:
{
"key": "value"
}
Response:
{
"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:
- Base64-encoded string: The API will automatically decode and save the image
- 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
{
"success": false,
"error": "Invalid JSON"
}
404 Not Found
{
"success": false,
"error": "Media not found"
}
405 Method Not Allowed
{
"success": false,
"error": "Method not allowed"
}
500 Internal Server Error
{
"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