docs: restructure documentation into audience-based portals
BREAKING CHANGE: Documentation URLs have changed New structure: - user/ - End-user documentation (device setup, sync guides, frontend) - developer/ - Developer documentation (API reference, protocols, specs) - operations/ - Operations documentation (deployment, troubleshooting) - contributing/ - Contribution guides Changes: - Created portal INDEX.md files for each audience section - Moved device guides to user/devices/ (kobo-setup.md, koreader-setup.md) - Moved API docs to developer/ (api-reference.md, collections-api.md) - Moved sync guide to user/sync-guide.md - Moved troubleshooting to operations/troubleshooting.md - Moved all split API docs to developer/api/ - Renamed protocol files (kobo-protocol.md, koreader-protocol.md) - Added placeholder user guides (frontend, user-areas, settings, admin) - Updated all internal links to new paths - Updated Go code (http_handler.go, navigation.go) for new paths - Updated main INDEX.md for audience-based navigation Benefits: - Clear separation of user and developer documentation - Scalable structure for future user guide expansion - Better organization and discoverability - Audience-specific landing pages Related to DOCS_IMPLEMENTATION_PLAN.md Phase 2 completion
This commit is contained in:
@@ -0,0 +1,79 @@
|
||||
# API Documentation
|
||||
|
||||
Complete reference for Bookhoard REST API endpoints.
|
||||
|
||||
## Quick Links
|
||||
|
||||
- [Authentication](authentication/) - User registration, login, tokens
|
||||
- [Users](users/) - Profile management
|
||||
- [Libraries](libraries/) - Library management
|
||||
- [Media Items](media-items/) - Book/ebook operations
|
||||
- [Progress](progress/) - Reading progress tracking
|
||||
- [Notes](notes/) - User notes management
|
||||
- [Highlights](highlights/) - Book highlights
|
||||
- [Ratings](ratings/) - Book ratings
|
||||
- [Devices](devices/) - Device registration and sync
|
||||
- [Analytics](analytics/) - Usage statistics
|
||||
- [Book Matching](book-matching/) - Search and link books
|
||||
- [Collections](collections/) - See [Collections API](../collections-api.md)
|
||||
- [OPDS](opds/) - Open Publication Distribution
|
||||
- [Sync Protocols](sync/) - KOReader and Kobo sync
|
||||
- [WebSocket](websocket/) - Real-time sync events
|
||||
|
||||
---
|
||||
|
||||
## Authentication
|
||||
|
||||
See [Authentication Endpoints](authentication/)
|
||||
|
||||
## Users & Profiles
|
||||
|
||||
See [User Management](users/)
|
||||
|
||||
## Libraries
|
||||
|
||||
See [Library Management](libraries/)
|
||||
|
||||
## Media Items
|
||||
|
||||
See [Media Item Operations](media-items/)
|
||||
|
||||
## Reading Progress
|
||||
|
||||
See [Progress Tracking](progress/)
|
||||
|
||||
## Notes & Highlights
|
||||
|
||||
See [Notes Management](notes/) and [Highlights Management](highlights/)
|
||||
|
||||
## Ratings
|
||||
|
||||
See [Ratings System](ratings/)
|
||||
|
||||
## Device Management
|
||||
|
||||
See [Device Registration & Sync](devices/)
|
||||
|
||||
## Analytics
|
||||
|
||||
See [Usage Analytics](analytics/)
|
||||
|
||||
## Book Matching & Linking
|
||||
|
||||
See [Book Matching](book-matching/)
|
||||
|
||||
## Collections
|
||||
|
||||
See [Collections API](../collections-api.md) or [Collections Endpoints](collections/)
|
||||
|
||||
## OPDS
|
||||
|
||||
See [OPDS Feeds](opds/)
|
||||
|
||||
## Sync Protocols
|
||||
|
||||
See [KOReader Sync](sync/) and [Kobo Sync](sync/)
|
||||
|
||||
## WebSocket
|
||||
|
||||
See [WebSocket Protocol](websocket/)
|
||||
@@ -0,0 +1,49 @@
|
||||
# Get Reading Statistics
|
||||
|
||||
Retrieve reading statistics for a date range.
|
||||
|
||||
**Endpoint**: `GET /api/analytics/reading-stats`
|
||||
**Auth**: Required
|
||||
|
||||
## Query Parameters
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
|-----------|------|-----------|-------------|
|
||||
| start_date | string | No | Start date (ISO 8601 format) |
|
||||
| end_date | string | No | End date (ISO 8601 format) |
|
||||
|
||||
## Request Headers
|
||||
|
||||
| Header | Type | Required | Description |
|
||||
|--------|------|-----------|-------------|
|
||||
| Authorization | string | Yes | Bearer token |
|
||||
|
||||
### Example Request
|
||||
|
||||
```http
|
||||
GET /api/analytics/reading-stats?start_date=2026-01-01&end_date=2026-01-31
|
||||
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
|
||||
```
|
||||
|
||||
## Response (200 OK)
|
||||
|
||||
```json
|
||||
{
|
||||
"pages_read": 1250,
|
||||
"books_completed": 5,
|
||||
"reading_time_hours": 42.5,
|
||||
"sessions_count": 28,
|
||||
"average_session_minutes": 91
|
||||
}
|
||||
```
|
||||
|
||||
## Error Responses
|
||||
|
||||
| Code | Description |
|
||||
|------|-------------|
|
||||
| 400 | Invalid date format |
|
||||
| 401 | Invalid or expired token |
|
||||
|
||||
## Try It Out
|
||||
|
||||
<!-- API Explorer will be inserted here in Phase 3 -->
|
||||
@@ -0,0 +1,49 @@
|
||||
# Login User
|
||||
|
||||
Authenticate with email and password.
|
||||
|
||||
**Endpoint**: `POST /api/auth/login`
|
||||
**Auth**: Not required
|
||||
**Content-Type**: `application/json`
|
||||
|
||||
## Request Body
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|--------|------|-----------|-------------|
|
||||
| email | string | Yes | User's email address |
|
||||
| password | string | Yes | User's password |
|
||||
|
||||
### Example Request
|
||||
|
||||
```json
|
||||
{
|
||||
"email": "user@example.com",
|
||||
"password": "SecureP@ss123!"
|
||||
}
|
||||
```
|
||||
|
||||
## Response (200 OK)
|
||||
|
||||
```json
|
||||
{
|
||||
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
|
||||
"refresh_token": "d4f5g6h7...",
|
||||
"user": {
|
||||
"id": "uuid-here",
|
||||
"email": "user@example.com",
|
||||
"username": "john",
|
||||
"role": "user"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Error Responses
|
||||
|
||||
| Code | Description |
|
||||
|------|-------------|
|
||||
| 401 | Invalid email or password |
|
||||
| 400 | Missing required fields |
|
||||
|
||||
## Try It Out
|
||||
|
||||
<!-- API Explorer will be inserted here in Phase 3 -->
|
||||
@@ -0,0 +1,35 @@
|
||||
# Logout User
|
||||
|
||||
Invalidate the current JWT token.
|
||||
|
||||
**Endpoint**: `POST /api/auth/logout`
|
||||
**Auth**: Required
|
||||
**Content-Type**: `application/json`
|
||||
|
||||
## Request Headers
|
||||
|
||||
| Header | Type | Required | Description |
|
||||
|--------|------|-----------|-------------|
|
||||
| Authorization | string | Yes | Bearer token (e.g., `Bearer eyJhbG...`) |
|
||||
|
||||
### Example Request
|
||||
|
||||
```http
|
||||
POST /api/auth/logout
|
||||
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
|
||||
```
|
||||
|
||||
## Response (204 No Content)
|
||||
|
||||
No response body.
|
||||
|
||||
## Error Responses
|
||||
|
||||
| Code | Description |
|
||||
|------|-------------|
|
||||
| 401 | Invalid or expired token |
|
||||
| 403 | Token already invalidated |
|
||||
|
||||
## Try It Out
|
||||
|
||||
<!-- API Explorer will be inserted here in Phase 3 -->
|
||||
@@ -0,0 +1,41 @@
|
||||
# Refresh Token
|
||||
|
||||
Obtain a new JWT token using a refresh token.
|
||||
|
||||
**Endpoint**: `POST /api/auth/refresh`
|
||||
**Auth**: Not required (uses refresh token)
|
||||
**Content-Type**: `application/json`
|
||||
|
||||
## Request Body
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|--------|------|-----------|-------------|
|
||||
| refresh_token | string | Yes | Valid refresh token |
|
||||
|
||||
### Example Request
|
||||
|
||||
```json
|
||||
{
|
||||
"refresh_token": "d4f5g6h7..."
|
||||
}
|
||||
```
|
||||
|
||||
## Response (200 OK)
|
||||
|
||||
```json
|
||||
{
|
||||
"token": "new-jwt-token",
|
||||
"refresh_token": "new-refresh-token"
|
||||
}
|
||||
```
|
||||
|
||||
## Error Responses
|
||||
|
||||
| Code | Description |
|
||||
|------|-------------|
|
||||
| 401 | Invalid or expired refresh token |
|
||||
| 400 | Missing refresh token |
|
||||
|
||||
## Try It Out
|
||||
|
||||
<!-- API Explorer will be inserted here in Phase 3 -->
|
||||
@@ -0,0 +1,57 @@
|
||||
# Register User
|
||||
|
||||
Create a new user account.
|
||||
|
||||
**Endpoint**: `POST /api/auth/register`
|
||||
**Auth**: Not required
|
||||
**Content-Type**: `application/json`
|
||||
|
||||
## Request Body
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|--------|------|-----------|-------------|
|
||||
| email | string | Yes | User's email address |
|
||||
| username | string | Yes | Desired username |
|
||||
| password | string | Yes | Password (min 8 chars) |
|
||||
| first_name | string | No | User's first name |
|
||||
| last_name | string | No | User's last name |
|
||||
|
||||
### Example Request
|
||||
|
||||
```json
|
||||
{
|
||||
"email": "user@example.com",
|
||||
"username": "john",
|
||||
"password": "SecureP@ss123!",
|
||||
"first_name": "John",
|
||||
"last_name": "Doe"
|
||||
}
|
||||
```
|
||||
|
||||
## Response (201 Created)
|
||||
|
||||
```json
|
||||
{
|
||||
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
|
||||
"refresh_token": "d4f5g6h7...",
|
||||
"user": {
|
||||
"id": "uuid-here",
|
||||
"email": "user@example.com",
|
||||
"username": "john",
|
||||
"role": "user",
|
||||
"theme": "tokyo-night",
|
||||
"created_at": "2026-01-31T10:00:00Z"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Error Responses
|
||||
|
||||
| Code | Description |
|
||||
|------|-------------|
|
||||
| 400 | Invalid email format, weak password, or missing fields |
|
||||
| 409 | Email or username already exists |
|
||||
|
||||
## Try It Out
|
||||
|
||||
<!-- API Explorer will be inserted here in Phase 3 -->
|
||||
@@ -0,0 +1,92 @@
|
||||
# Link Book
|
||||
|
||||
Link a device book to a Bookhoard media item. Supports bulk linking.
|
||||
|
||||
**Endpoint**: `POST /api/sync/bulk-link-books` or `POST /api/sync/auto-link-books`
|
||||
**Auth**: Required
|
||||
**Content-Type**: `application/json`
|
||||
|
||||
## Manual Link Request Body
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|--------|------|-----------|-------------|
|
||||
| links | array | Yes | List of book links |
|
||||
| links[].unlinked_book_id | string | Yes | Device book UUID |
|
||||
| links[].media_item_id | string | Yes | Bookhoard media item UUID |
|
||||
| links[].confidence_score | float | No | Match confidence (0-1) |
|
||||
|
||||
### Example Manual Link Request
|
||||
|
||||
```json
|
||||
{
|
||||
"links": [
|
||||
{
|
||||
"unlinked_book_id": "uuid-1",
|
||||
"media_item_id": "uuid-2",
|
||||
"confidence_score": 1.0
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Auto-Link Request Body
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|--------|------|-----------|-------------|
|
||||
| confidence_threshold | float | No | Minimum confidence for auto-link (default: 0.8) |
|
||||
| limit | integer | No | Maximum books to auto-link (default: 50) |
|
||||
|
||||
### Example Auto-Link Request
|
||||
|
||||
```json
|
||||
{
|
||||
"confidence_threshold": 0.8,
|
||||
"limit": 50
|
||||
}
|
||||
```
|
||||
|
||||
## Response (200 OK) - Manual Link
|
||||
|
||||
```json
|
||||
{
|
||||
"results": [
|
||||
{
|
||||
"unlinked_book_id": "uuid-1",
|
||||
"status": "success",
|
||||
"media_item_id": "uuid-2"
|
||||
}
|
||||
],
|
||||
"total": 1,
|
||||
"successful": 1,
|
||||
"failed": 0
|
||||
}
|
||||
```
|
||||
|
||||
## Response (200 OK) - Auto-Link
|
||||
|
||||
```json
|
||||
{
|
||||
"auto_linked": 15,
|
||||
"results": [
|
||||
{
|
||||
"unlinked_book_id": "uuid-1",
|
||||
"title": "The Hobbit",
|
||||
"matched_media_item_id": "uuid-2",
|
||||
"confidence": 0.95,
|
||||
"match_method": "sha256_match"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Error Responses
|
||||
|
||||
| Code | Description |
|
||||
|------|-------------|
|
||||
| 400 | Invalid link data |
|
||||
| 401 | Invalid or expired token |
|
||||
| 404 | Media item not found |
|
||||
|
||||
## Try It Out
|
||||
|
||||
<!-- API Explorer will be inserted here in Phase 3 -->
|
||||
@@ -0,0 +1,56 @@
|
||||
# Search Books for Matching
|
||||
|
||||
Query books to find potential matches for linking.
|
||||
|
||||
**Endpoint**: `POST /api/sync/books/query`
|
||||
**Auth**: Required
|
||||
**Content-Type**: `application/json`
|
||||
|
||||
## Request Body
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|--------|------|-----------|-------------|
|
||||
| identifiers | array | No | List of identifiers (ISBN, UUID) |
|
||||
| sha256 | string | No | SHA256 hash of book file |
|
||||
| title | string | No | Book title |
|
||||
| author | string | No | Book author |
|
||||
| file_size | integer | No | File size in bytes |
|
||||
|
||||
### Example Request
|
||||
|
||||
```json
|
||||
{
|
||||
"identifiers": ["isbn:978-0345391802", "uuid:abc-123"],
|
||||
"sha256": "a1b2c3d4e5f6abc123...",
|
||||
"title": "The Hobbit",
|
||||
"author": "J.R.R. Tolkien",
|
||||
"file_size": 2456789
|
||||
}
|
||||
```
|
||||
|
||||
## Response (200 OK)
|
||||
|
||||
```json
|
||||
{
|
||||
"matches": [
|
||||
{
|
||||
"media_item_id": "uuid-123",
|
||||
"bookhoard_uuid": "uuid-123",
|
||||
"confidence": 1.0,
|
||||
"match_method": "uuid_match"
|
||||
}
|
||||
],
|
||||
"action": "auto_link"
|
||||
}
|
||||
```
|
||||
|
||||
## Error Responses
|
||||
|
||||
| Code | Description |
|
||||
|------|-------------|
|
||||
| 400 | Invalid query parameters |
|
||||
| 401 | Invalid or expired token |
|
||||
|
||||
## Try It Out
|
||||
|
||||
<!-- API Explorer will be inserted here in Phase 3 -->
|
||||
@@ -0,0 +1,35 @@
|
||||
# Collections API
|
||||
|
||||
See [Collections API](../../collections-api.md) for complete collections documentation.
|
||||
|
||||
## Quick Links
|
||||
|
||||
- [List Collections](list_collections.md) - Get all collections
|
||||
- [Get Collection](get_collection.md) - Get collection details
|
||||
- [Create Collection](create_collection.md) - Create a new collection
|
||||
- [Update Collection](update_collection.md) - Update collection metadata
|
||||
- [Delete Collection](delete_collection.md) - Delete a collection
|
||||
- [Add Auto-Assign Rule](add_auto_assign_rule.md) - Add automatic book assignment rule
|
||||
- [Remove Auto-Assign Rule](remove_auto_assign_rule.md) - Remove assignment rule
|
||||
- [Test Rule](test_rule.md) - Test assignment rule
|
||||
- [Bulk Assign](bulk_assign.md) - Bulk assign books to collection
|
||||
- [Create Shelf Mapping](create_shelf_mapping.md) - Map collection to device shelf
|
||||
- [Delete Shelf Mapping](delete_shelf_mapping.md) - Remove shelf mapping
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
Collections allow you to organize your books into custom groups with automatic assignment rules.
|
||||
|
||||
## Features
|
||||
|
||||
- **Manual Assignment**: Add specific books to collections
|
||||
- **Auto-Assignment**: Define rules to automatically assign books based on metadata
|
||||
- **Device Sync**: Collections sync as shelves on Kobo/KOReader devices
|
||||
- **Flexible Rules**: Filter by genre, series, tags, and more
|
||||
|
||||
## See Also
|
||||
|
||||
- [Collections API](../../collections-api.md) - Complete API reference
|
||||
- [Sync Guide](../../user/sync-guide.md) - How collections sync to devices
|
||||
@@ -0,0 +1,85 @@
|
||||
# Add Auto-Assign Rule
|
||||
|
||||
Add an automatic book assignment rule to a collection.
|
||||
|
||||
**Endpoint**: `POST /api/collections/{id}/rules`
|
||||
**Auth**: Required
|
||||
**Content-Type**: `application/json`
|
||||
|
||||
## Path Parameters
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
|-----------|------|-----------|-------------|
|
||||
| id | string (UUID) | Yes | Collection UUID |
|
||||
|
||||
## Request Body
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|--------|------|-----------|-------------|
|
||||
| field | string | Yes | Field to match on (genre, author, series, language, publisher, copyright_year, tags) |
|
||||
| operator | string | Yes | Comparison operator (equals, not_equals, contains, not_contains, starts_with, ends_with, greater_than, less_than) |
|
||||
| value | string/number | Yes | Value to compare against |
|
||||
| priority | integer | No | Rule priority (1 = highest, default: 1) |
|
||||
| enabled | boolean | No | Whether rule is active (default: true) |
|
||||
|
||||
### Supported Fields
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| genre | string | Book genre |
|
||||
| author | string | Book author |
|
||||
| series | string | Book series name |
|
||||
| language | string | Book language |
|
||||
| publisher | string | Publisher name |
|
||||
| copyright_year | number | Publication year (numeric comparison) |
|
||||
| tags | string | Book tags |
|
||||
|
||||
### Supported Operators
|
||||
|
||||
| Operator | Type | Description |
|
||||
|----------|------|-------------|
|
||||
| equals | all | Exact match |
|
||||
| not_equals | all | Not equal |
|
||||
| contains | string | Contains substring (case-insensitive) |
|
||||
| not_contains | string | Does not contain |
|
||||
| starts_with | string | Starts with (case-insensitive) |
|
||||
| ends_with | string | Ends with (case-insensitive) |
|
||||
| greater_than | number | Greater than |
|
||||
| less_than | number | Less than |
|
||||
|
||||
### Example Request
|
||||
|
||||
```json
|
||||
{
|
||||
"field": "genre",
|
||||
"operator": "equals",
|
||||
"value": "Science Fiction",
|
||||
"priority": 1,
|
||||
"enabled": true
|
||||
}
|
||||
```
|
||||
|
||||
## Response (201 Created)
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "rule-uuid-here",
|
||||
"field": "genre",
|
||||
"operator": "equals",
|
||||
"value": "Science Fiction",
|
||||
"priority": 1,
|
||||
"enabled": true,
|
||||
"created_at": "2026-02-01T10:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
## Error Responses
|
||||
|
||||
| Code | Description |
|
||||
|------|-------------|
|
||||
| 400 | Invalid request (validation failed) |
|
||||
| 401 | Authentication required |
|
||||
| 404 | Collection not found |
|
||||
| 500 | Internal server error |
|
||||
|
||||
## Try It Out
|
||||
@@ -0,0 +1,52 @@
|
||||
# Bulk Assign Books
|
||||
|
||||
Add multiple books to a collection at once.
|
||||
|
||||
**Endpoint**: `POST /api/collections/{id}/books`
|
||||
**Auth**: Required
|
||||
**Content-Type**: `application/json`
|
||||
|
||||
## Path Parameters
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
|-----------|------|-----------|-------------|
|
||||
| id | string (UUID) | Yes | Collection UUID |
|
||||
|
||||
## Request Body
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|--------|------|-----------|-------------|
|
||||
| book_ids | array of UUID | Yes | Array of book IDs to add |
|
||||
|
||||
### Example Request
|
||||
|
||||
```json
|
||||
{
|
||||
"book_ids": [
|
||||
"660e8400-e29b-41d4-a716-446655440000",
|
||||
"770e8400-e29b-41d4-a716-446655440000",
|
||||
"880e8400-e29b-41d4-a716-446655440000"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Response (204 No Content)
|
||||
|
||||
Books added to collection successfully. No response body.
|
||||
|
||||
## Notes
|
||||
|
||||
- Books already in the collection are ignored
|
||||
- This is more efficient than adding books individually
|
||||
- Maximum 1000 books per request
|
||||
|
||||
## Error Responses
|
||||
|
||||
| Code | Description |
|
||||
|------|-------------|
|
||||
| 400 | Invalid request (validation failed) |
|
||||
| 401 | Authentication required |
|
||||
| 404 | Collection or book(s) not found |
|
||||
| 500 | Internal server error |
|
||||
|
||||
## Try It Out
|
||||
@@ -0,0 +1,96 @@
|
||||
# Create Collection
|
||||
|
||||
Create a new collection.
|
||||
|
||||
**Endpoint**: `POST /api/collections`
|
||||
**Auth**: Required
|
||||
**Content-Type**: `application/json`
|
||||
|
||||
## Request Body
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|--------|------|-----------|-------------|
|
||||
| name | string | Yes | Collection name (max 255 chars) |
|
||||
| description | string | No | Collection description |
|
||||
| color | string | No | Hex color code (e.g., "#FF5733") |
|
||||
| icon | string | No | Emoji icon (e.g., "🚀", "📖") |
|
||||
| auto_assign_rules | array | No | Array of rule objects |
|
||||
| view_settings | object | No | Per-device display preferences |
|
||||
|
||||
### Auto-Assign Rule Object
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|--------|------|-----------|-------------|
|
||||
| field | string | Yes | Field to match on (genre, author, series, language, publisher, copyright_year, tags) |
|
||||
| operator | string | Yes | Comparison operator (equals, not_equals, contains, not_contains, starts_with, ends_with, greater_than, less_than) |
|
||||
| value | string/number | Yes | Value to compare against |
|
||||
| priority | integer | No | Rule priority (1 = highest, default: 1) |
|
||||
| enabled | boolean | No | Whether rule is active (default: true) |
|
||||
|
||||
### Example Request
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "To Read",
|
||||
"description": "Books I want to read soon",
|
||||
"color": "#00FF00",
|
||||
"icon": "📖",
|
||||
"auto_assign_rules": [
|
||||
{
|
||||
"field": "tags",
|
||||
"operator": "contains",
|
||||
"value": "to-read",
|
||||
"priority": 1,
|
||||
"enabled": true
|
||||
}
|
||||
],
|
||||
"view_settings": {
|
||||
"kobo": {
|
||||
"view_mode": "grid"
|
||||
},
|
||||
"koreader": {
|
||||
"view_mode": "list"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Response (201 Created)
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "550e8400-e29b-41d4-a716-446655440000",
|
||||
"name": "To Read",
|
||||
"description": "Books I want to read soon",
|
||||
"color": "#00FF00",
|
||||
"icon": "📖",
|
||||
"auto_assign_rules": [
|
||||
{
|
||||
"field": "tags",
|
||||
"operator": "contains",
|
||||
"value": "to-read",
|
||||
"priority": 1,
|
||||
"enabled": true
|
||||
}
|
||||
],
|
||||
"view_settings": {
|
||||
"kobo": {
|
||||
"view_mode": "grid"
|
||||
},
|
||||
"koreader": {
|
||||
"view_mode": "list"
|
||||
}
|
||||
},
|
||||
"created_at": "2026-02-01T10:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
## Error Responses
|
||||
|
||||
| Code | Description |
|
||||
|------|-------------|
|
||||
| 400 | Invalid request (validation failed) |
|
||||
| 401 | Authentication required |
|
||||
| 500 | Internal server error |
|
||||
|
||||
## Try It Out
|
||||
@@ -0,0 +1,69 @@
|
||||
# Create Shelf Mapping
|
||||
|
||||
Map a collection to a device shelf for syncing.
|
||||
|
||||
**Endpoint**: `POST /api/devices/{deviceId}/collections`
|
||||
**Auth**: Required
|
||||
**Content-Type**: `application/json`
|
||||
|
||||
## Path Parameters
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
|-----------|------|-----------|-------------|
|
||||
| deviceId | string (UUID) | Yes | Device UUID |
|
||||
|
||||
## Request Body
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|--------|------|-----------|-------------|
|
||||
| collection_id | string (UUID) | Yes | Collection UUID to map |
|
||||
| device_shelf_name | string | Yes | Name of the shelf on the device |
|
||||
| sync_direction | string | No | Sync direction (default: "bidirectional") |
|
||||
|
||||
### Sync Directions
|
||||
|
||||
| Direction | Description |
|
||||
|-----------|-------------|
|
||||
| bidirectional | Sync both ways between Bookhoard and device |
|
||||
| book_to_hoard | Bookhoard → Device only |
|
||||
| device_to_hoard | Device → Bookhoard only |
|
||||
| none | No sync (mapping only for reference) |
|
||||
|
||||
### Example Request
|
||||
|
||||
```json
|
||||
{
|
||||
"collection_id": "550e8400-e29b-41d4-a716-446655440000",
|
||||
"device_shelf_name": "Sci-Fi",
|
||||
"sync_direction": "bidirectional"
|
||||
}
|
||||
```
|
||||
|
||||
## Response (201 Created)
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "990e8400-e29b-41d4-a716-446655440000",
|
||||
"collection_id": "550e8400-e29b-41d4-a716-446655440000",
|
||||
"collection_name": "Science Fiction",
|
||||
"device_shelf_name": "Sci-Fi",
|
||||
"sync_direction": "bidirectional",
|
||||
"created_at": "2026-02-01T10:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
## Use Case
|
||||
|
||||
Collections can be synced to device-specific shelves (Kobo, KOReader). This allows automatic organization of books on your e-reader based on your Bookhoard collections.
|
||||
|
||||
## Error Responses
|
||||
|
||||
| Code | Description |
|
||||
|------|-------------|
|
||||
| 400 | Invalid request (validation failed) |
|
||||
| 401 | Authentication required |
|
||||
| 404 | Device or collection not found |
|
||||
| 409 | Mapping already exists |
|
||||
| 500 | Internal server error |
|
||||
|
||||
## Try It Out
|
||||
@@ -0,0 +1,27 @@
|
||||
# Delete Collection
|
||||
|
||||
Delete a collection. Books are NOT deleted.
|
||||
|
||||
**Endpoint**: `DELETE /api/collections/{id}`
|
||||
**Auth**: Required
|
||||
**Content-Type**: `application/json`
|
||||
|
||||
## Path Parameters
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
|-----------|------|-----------|-------------|
|
||||
| id | string (UUID) | Yes | Collection UUID |
|
||||
|
||||
## Response (204 No Content)
|
||||
|
||||
Collection deleted successfully. No response body.
|
||||
|
||||
## Error Responses
|
||||
|
||||
| Code | Description |
|
||||
|------|-------------|
|
||||
| 401 | Authentication required |
|
||||
| 404 | Collection not found |
|
||||
| 500 | Internal server error |
|
||||
|
||||
## Try It Out
|
||||
@@ -0,0 +1,34 @@
|
||||
# Delete Shelf Mapping
|
||||
|
||||
Remove a collection-to-shelf mapping for a device.
|
||||
|
||||
**Endpoint**: `DELETE /api/devices/{deviceId}/collections/{collectionId}`
|
||||
**Auth**: Required
|
||||
**Content-Type**: `application/json`
|
||||
|
||||
## Path Parameters
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
|-----------|------|-----------|-------------|
|
||||
| deviceId | string (UUID) | Yes | Device UUID |
|
||||
| collectionId | string (UUID) | Yes | Collection UUID |
|
||||
|
||||
## Response (204 No Content)
|
||||
|
||||
Shelf mapping deleted successfully. No response body.
|
||||
|
||||
## Notes
|
||||
|
||||
- Deleting a mapping does NOT delete the collection
|
||||
- Deleting a mapping does NOT delete books from the device
|
||||
- It only removes the association between the collection and device shelf
|
||||
|
||||
## Error Responses
|
||||
|
||||
| Code | Description |
|
||||
|------|-------------|
|
||||
| 401 | Authentication required |
|
||||
| 404 | Device or collection not found |
|
||||
| 500 | Internal server error |
|
||||
|
||||
## Try It Out
|
||||
@@ -0,0 +1,54 @@
|
||||
# Get Collection
|
||||
|
||||
Get single collection with all books.
|
||||
|
||||
**Endpoint**: `GET /api/collections/{id}`
|
||||
**Auth**: Required
|
||||
**Content-Type**: `application/json`
|
||||
|
||||
## Path Parameters
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
|-----------|------|-----------|-------------|
|
||||
| id | string (UUID) | Yes | Collection UUID |
|
||||
|
||||
## Query Parameters
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
|-----------|------|-----------|-------------|
|
||||
| include_books | boolean | No | Include books in response (default: true) |
|
||||
| limit | integer | No | Number of books to return (default: 50) |
|
||||
| offset | integer | No | Number of books to skip (default: 0) |
|
||||
|
||||
## Response (200 OK)
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "550e8400-e29b-41d4-a716-446655440000",
|
||||
"name": "Science Fiction",
|
||||
"description": "My sci-fi collection",
|
||||
"color": "#FF5733",
|
||||
"icon": "🚀",
|
||||
"books": [
|
||||
{
|
||||
"media_item_id": "660e8400-e29b-41d4-a716-446655440000",
|
||||
"title": "Foundation",
|
||||
"author": "Isaac Asimov",
|
||||
"cover_image_path": "/covers/foundation.jpg"
|
||||
}
|
||||
],
|
||||
"auto_assign_rules": [],
|
||||
"view_settings": {},
|
||||
"created_at": "2026-02-01T10:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
## Error Responses
|
||||
|
||||
| Code | Description |
|
||||
|------|-------------|
|
||||
| 401 | Authentication required |
|
||||
| 404 | Collection not found |
|
||||
| 500 | Internal server error |
|
||||
|
||||
## Try It Out
|
||||
@@ -0,0 +1,57 @@
|
||||
# List Collections
|
||||
|
||||
Get all collections for the authenticated user.
|
||||
|
||||
**Endpoint**: `GET /api/collections`
|
||||
**Auth**: Required
|
||||
**Content-Type**: `application/json`
|
||||
|
||||
## Query Parameters
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
|-----------|------|-----------|-------------|
|
||||
| limit | integer | No | Number of collections to return (default: 50) |
|
||||
| offset | integer | No | Number of collections to skip (default: 0) |
|
||||
|
||||
## Response (200 OK)
|
||||
|
||||
```json
|
||||
{
|
||||
"collections": [
|
||||
{
|
||||
"id": "550e8400-e29b-41d4-a716-446655440000",
|
||||
"name": "Science Fiction",
|
||||
"description": "My sci-fi collection",
|
||||
"color": "#FF5733",
|
||||
"icon": "🚀",
|
||||
"auto_assign_rules": [
|
||||
{
|
||||
"field": "genre",
|
||||
"operator": "equals",
|
||||
"value": "Science Fiction",
|
||||
"priority": 1,
|
||||
"enabled": true
|
||||
}
|
||||
],
|
||||
"view_settings": {
|
||||
"kobo": {
|
||||
"view_mode": "grid",
|
||||
"sort_order": "name",
|
||||
"items_per_page": 24
|
||||
}
|
||||
},
|
||||
"created_at": "2026-02-01T10:00:00Z"
|
||||
}
|
||||
],
|
||||
"total": 1
|
||||
}
|
||||
```
|
||||
|
||||
## Error Responses
|
||||
|
||||
| Code | Description |
|
||||
|------|-------------|
|
||||
| 401 | Authentication required |
|
||||
| 500 | Internal server error |
|
||||
|
||||
## Try It Out
|
||||
@@ -0,0 +1,28 @@
|
||||
# Remove Auto-Assign Rule
|
||||
|
||||
Remove an automatic book assignment rule from a collection.
|
||||
|
||||
**Endpoint**: `DELETE /api/collections/{collectionId}/rules/{ruleId}`
|
||||
**Auth**: Required
|
||||
**Content-Type**: `application/json`
|
||||
|
||||
## Path Parameters
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
|-----------|------|-----------|-------------|
|
||||
| collectionId | string (UUID) | Yes | Collection UUID |
|
||||
| ruleId | string (UUID) | Yes | Rule UUID |
|
||||
|
||||
## Response (204 No Content)
|
||||
|
||||
Rule deleted successfully. No response body.
|
||||
|
||||
## Error Responses
|
||||
|
||||
| Code | Description |
|
||||
|------|-------------|
|
||||
| 401 | Authentication required |
|
||||
| 404 | Collection or rule not found |
|
||||
| 500 | Internal server error |
|
||||
|
||||
## Try It Out
|
||||
@@ -0,0 +1,71 @@
|
||||
# Test Rule
|
||||
|
||||
Test which books would match given rules without saving.
|
||||
|
||||
**Endpoint**: `POST /api/collections/test-rules`
|
||||
**Auth**: Required
|
||||
**Content-Type**: `application/json`
|
||||
|
||||
## Request Body
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|--------|------|-----------|-------------|
|
||||
| rules | array | Yes | Array of rule objects to test |
|
||||
|
||||
### Rule Object
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|--------|------|-----------|-------------|
|
||||
| field | string | Yes | Field to match on (genre, author, series, language, publisher, copyright_year, tags) |
|
||||
| operator | string | Yes | Comparison operator (equals, not_equals, contains, not_contains, starts_with, ends_with, greater_than, less_than) |
|
||||
| value | string/number | Yes | Value to compare against |
|
||||
|
||||
### Example Request
|
||||
|
||||
```json
|
||||
{
|
||||
"rules": [
|
||||
{
|
||||
"field": "genre",
|
||||
"operator": "equals",
|
||||
"value": "Science Fiction"
|
||||
},
|
||||
{
|
||||
"field": "author",
|
||||
"operator": "contains",
|
||||
"value": "Asimov"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Response (200 OK)
|
||||
|
||||
```json
|
||||
{
|
||||
"matches": [
|
||||
{
|
||||
"media_item_id": "660e8400-e29b-41d4-a716-446655440000",
|
||||
"title": "Foundation",
|
||||
"author": "Isaac Asimov",
|
||||
"cover_image_path": "/covers/foundation.jpg",
|
||||
"match_reason": "Matched rule: genre equals Science Fiction"
|
||||
}
|
||||
],
|
||||
"total": 42
|
||||
}
|
||||
```
|
||||
|
||||
## Use Case
|
||||
|
||||
Test rules before creating a collection to verify correct book matching. This endpoint shows which books would be added without actually modifying any collections.
|
||||
|
||||
## Error Responses
|
||||
|
||||
| Code | Description |
|
||||
|------|-------------|
|
||||
| 400 | Invalid request (validation failed) |
|
||||
| 401 | Authentication required |
|
||||
| 500 | Internal server error |
|
||||
|
||||
## Try It Out
|
||||
@@ -0,0 +1,63 @@
|
||||
# Update Collection
|
||||
|
||||
Update collection details.
|
||||
|
||||
**Endpoint**: `PUT /api/collections/{id}`
|
||||
**Auth**: Required
|
||||
**Content-Type**: `application/json`
|
||||
|
||||
## Path Parameters
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
|-----------|------|-----------|-------------|
|
||||
| id | string (UUID) | Yes | Collection UUID |
|
||||
|
||||
## Request Body
|
||||
|
||||
All fields are optional. Include only fields you want to update.
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|--------|------|-----------|-------------|
|
||||
| name | string | No | Collection name (max 255 chars) |
|
||||
| description | string | No | Collection description |
|
||||
| color | string | No | Hex color code (e.g., "#FF5733") |
|
||||
| icon | string | No | Emoji icon (e.g., "🚀", "📖") |
|
||||
| auto_assign_rules | array | No | Array of rule objects (replaces existing rules) |
|
||||
| view_settings | object | No | Per-device display preferences |
|
||||
|
||||
### Example Request
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "Sci-Fi Favorites",
|
||||
"description": "Updated description",
|
||||
"color": "#7aa2f7",
|
||||
"icon": "🌟"
|
||||
}
|
||||
```
|
||||
|
||||
## Response (200 OK)
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "550e8400-e29b-41d4-a716-446655440000",
|
||||
"name": "Sci-Fi Favorites",
|
||||
"description": "Updated description",
|
||||
"color": "#7aa2f7",
|
||||
"icon": "🌟",
|
||||
"auto_assign_rules": [],
|
||||
"view_settings": {},
|
||||
"created_at": "2026-02-01T10:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
## Error Responses
|
||||
|
||||
| Code | Description |
|
||||
|------|-------------|
|
||||
| 400 | Invalid request (validation failed) |
|
||||
| 401 | Authentication required |
|
||||
| 404 | Collection not found |
|
||||
| 500 | Internal server error |
|
||||
|
||||
## Try It Out
|
||||
@@ -0,0 +1,62 @@
|
||||
# Get Device / Check Registration Status
|
||||
|
||||
Check device registration status or get device details.
|
||||
|
||||
**Endpoint**: `POST /api/devices/auth/status` or `GET /api/devices/{device_id}`
|
||||
**Auth**: Not required for status check, Required for device details
|
||||
**Content-Type**: `application/json` (for status check)
|
||||
|
||||
## Request Body (Status Check)
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|--------|------|-----------|-------------|
|
||||
| registration_id | string | Yes | Registration UUID |
|
||||
|
||||
### Example Request (Status Check)
|
||||
|
||||
```json
|
||||
{
|
||||
"registration_id": "registration-uuid"
|
||||
}
|
||||
```
|
||||
|
||||
## Response (200 OK)
|
||||
|
||||
```json
|
||||
{
|
||||
"status": "pending|approved|expired",
|
||||
"auth_token": "device-bearer-token...",
|
||||
"device_id": "uuid",
|
||||
"sync_endpoints": {
|
||||
"progress": "https://bookhoard.com/api/sync/progress",
|
||||
"metadata": "https://bookhoard.com/api/sync/metadata",
|
||||
"annotations": "https://bookhoard.com/api/sync/annotations"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Response (200 OK) - Device Details
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "uuid",
|
||||
"device_name": "My Kobo Clara",
|
||||
"device_type": "kobo",
|
||||
"last_sync": "2026-01-31T10:00:00Z",
|
||||
"last_seen": "2026-01-31T10:05:00Z",
|
||||
"sync_enabled": true,
|
||||
"auto_sync": true,
|
||||
"sync_frequency_minutes": 5
|
||||
}
|
||||
```
|
||||
|
||||
## Error Responses
|
||||
|
||||
| Code | Description |
|
||||
|------|-------------|
|
||||
| 401 | Invalid or expired token (for device details) |
|
||||
| 404 | Device or registration not found |
|
||||
|
||||
## Try It Out
|
||||
|
||||
<!-- API Explorer will be inserted here in Phase 3 -->
|
||||
@@ -0,0 +1,48 @@
|
||||
# List User Devices
|
||||
|
||||
Retrieve all devices registered to the current user.
|
||||
|
||||
**Endpoint**: `GET /api/devices`
|
||||
**Auth**: Required
|
||||
|
||||
## Request Headers
|
||||
|
||||
| Header | Type | Required | Description |
|
||||
|--------|------|-----------|-------------|
|
||||
| Authorization | string | Yes | Bearer token |
|
||||
|
||||
### Example Request
|
||||
|
||||
```http
|
||||
GET /api/devices
|
||||
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
|
||||
```
|
||||
|
||||
## Response (200 OK)
|
||||
|
||||
```json
|
||||
{
|
||||
"devices": [
|
||||
{
|
||||
"id": "uuid",
|
||||
"device_name": "My Kobo Clara",
|
||||
"device_type": "kobo",
|
||||
"last_sync": "2026-01-31T10:00:00Z",
|
||||
"last_seen": "2026-01-31T10:05:00Z",
|
||||
"sync_enabled": true,
|
||||
"auto_sync": true,
|
||||
"sync_frequency_minutes": 5
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Error Responses
|
||||
|
||||
| Code | Description |
|
||||
|------|-------------|
|
||||
| 401 | Invalid or expired token |
|
||||
|
||||
## Try It Out
|
||||
|
||||
<!-- API Explorer will be inserted here in Phase 3 -->
|
||||
@@ -0,0 +1,48 @@
|
||||
# Register Device
|
||||
|
||||
Register a new device for sync.
|
||||
|
||||
**Endpoint**: `POST /api/devices/register`
|
||||
**Auth**: Not required (device registration flow)
|
||||
**Content-Type**: `application/json`
|
||||
|
||||
## Request Body
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|--------|------|-----------|-------------|
|
||||
| device_name | string | Yes | Device name |
|
||||
| device_type | string | Yes | Device type: kobo, koreader, web, mobile |
|
||||
| device_identifier | string | Yes | Hardware-specific ID |
|
||||
|
||||
### Example Request
|
||||
|
||||
```json
|
||||
{
|
||||
"device_name": "My Kobo Clara",
|
||||
"device_type": "kobo",
|
||||
"device_identifier": "hardware-specific-id"
|
||||
}
|
||||
```
|
||||
|
||||
## Response (201 Created)
|
||||
|
||||
```json
|
||||
{
|
||||
"device_id": "uuid",
|
||||
"registration_id": "registration-uuid",
|
||||
"auth_url": "https://bookhoard.com/devices/auth/confirm/abc123",
|
||||
"qr_code": "data:image/png;base64,iVBORw0KG...",
|
||||
"expires_in": 300
|
||||
}
|
||||
```
|
||||
|
||||
## Error Responses
|
||||
|
||||
| Code | Description |
|
||||
|------|-------------|
|
||||
| 400 | Invalid device data |
|
||||
| 409 | Device already registered |
|
||||
|
||||
## Try It Out
|
||||
|
||||
<!-- API Explorer will be inserted here in Phase 3 -->
|
||||
@@ -0,0 +1,41 @@
|
||||
# Revoke Device
|
||||
|
||||
Revoke access to a device.
|
||||
|
||||
**Endpoint**: `DELETE /api/devices/{device_id}`
|
||||
**Auth**: Required
|
||||
|
||||
## Path Parameters
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
|-----------|------|-----------|-------------|
|
||||
| device_id | string | Yes | Device UUID |
|
||||
|
||||
## Request Headers
|
||||
|
||||
| Header | Type | Required | Description |
|
||||
|--------|------|-----------|-------------|
|
||||
| Authorization | string | Yes | Bearer token |
|
||||
|
||||
### Example Request
|
||||
|
||||
```http
|
||||
DELETE /api/devices/uuid
|
||||
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
|
||||
```
|
||||
|
||||
## Response (204 No Content)
|
||||
|
||||
Device revoked successfully.
|
||||
|
||||
## Error Responses
|
||||
|
||||
| Code | Description |
|
||||
|------|-------------|
|
||||
| 401 | Invalid or expired token |
|
||||
| 403 | User does not own this device |
|
||||
| 404 | Device not found |
|
||||
|
||||
## Try It Out
|
||||
|
||||
<!-- API Explorer will be inserted here in Phase 3 -->
|
||||
@@ -0,0 +1,66 @@
|
||||
# Create Highlight
|
||||
|
||||
Create a new highlight for a media item.
|
||||
|
||||
**Endpoint**: `POST /api/media-items/{media_id}/highlights`
|
||||
**Auth**: Required
|
||||
**Content-Type**: `application/json`
|
||||
|
||||
## Path Parameters
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
|-----------|------|-----------|-------------|
|
||||
| media_id | string | Yes | Media item UUID |
|
||||
|
||||
## Request Body
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|--------|------|-----------|-------------|
|
||||
| selection_text | string | Yes | Highlighted text |
|
||||
| start_position | string | No | Start position (e.g., epubcfi) |
|
||||
| end_position | string | No | End position (e.g., epubcfi) |
|
||||
| color | string | No | Highlight color (hex, default: "#ffff00") |
|
||||
| percentage_start | float | No | Start percentage (0-1) |
|
||||
| percentage_end | float | No | End percentage (0-1) |
|
||||
|
||||
### Example Request
|
||||
|
||||
```json
|
||||
{
|
||||
"selection_text": "Highlighted text...",
|
||||
"start_position": "epubcfi(/6/4/2:15)",
|
||||
"end_position": "epubcfi(/6/4/2:20)",
|
||||
"color": "#ffff00",
|
||||
"percentage_start": 0.45,
|
||||
"percentage_end": 0.47
|
||||
}
|
||||
```
|
||||
|
||||
## Response (201 Created)
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "uuid",
|
||||
"media_item_id": "uuid",
|
||||
"user_id": "uuid",
|
||||
"selection_text": "Highlighted text...",
|
||||
"start_position": "epubcfi(/6/4/2:15)",
|
||||
"end_position": "epubcfi(/6/4/2:20)",
|
||||
"color": "#ffff00",
|
||||
"percentage_start": 0.45,
|
||||
"percentage_end": 0.47,
|
||||
"created_at": "2026-01-31T10:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
## Error Responses
|
||||
|
||||
| Code | Description |
|
||||
|------|-------------|
|
||||
| 400 | Invalid highlight data |
|
||||
| 401 | Invalid or expired token |
|
||||
| 404 | Media item not found |
|
||||
|
||||
## Try It Out
|
||||
|
||||
<!-- API Explorer will be inserted here in Phase 3 -->
|
||||
@@ -0,0 +1,41 @@
|
||||
# Delete Highlight
|
||||
|
||||
Delete a highlight.
|
||||
|
||||
**Endpoint**: `DELETE /api/media-items/highlights/{highlight_id}`
|
||||
**Auth**: Required
|
||||
|
||||
## Path Parameters
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
|-----------|------|-----------|-------------|
|
||||
| highlight_id | string | Yes | Highlight UUID |
|
||||
|
||||
## Request Headers
|
||||
|
||||
| Header | Type | Required | Description |
|
||||
|--------|------|-----------|-------------|
|
||||
| Authorization | string | Yes | Bearer token |
|
||||
|
||||
### Example Request
|
||||
|
||||
```http
|
||||
DELETE /api/media-items/highlights/uuid
|
||||
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
|
||||
```
|
||||
|
||||
## Response (204 No Content)
|
||||
|
||||
Highlight deleted successfully.
|
||||
|
||||
## Error Responses
|
||||
|
||||
| Code | Description |
|
||||
|------|-------------|
|
||||
| 401 | Invalid or expired token |
|
||||
| 403 | User does not own this highlight |
|
||||
| 404 | Highlight not found |
|
||||
|
||||
## Try It Out
|
||||
|
||||
<!-- API Explorer will be inserted here in Phase 3 -->
|
||||
@@ -0,0 +1,61 @@
|
||||
# Get Highlights
|
||||
|
||||
Retrieve all highlights for a specific media item.
|
||||
|
||||
**Endpoint**: `GET /api/media-items/{media_id}/highlights`
|
||||
**Auth**: Required
|
||||
|
||||
## Path Parameters
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
|-----------|------|-----------|-------------|
|
||||
| media_id | string | Yes | Media item UUID |
|
||||
|
||||
## Request Headers
|
||||
|
||||
| Header | Type | Required | Description |
|
||||
|--------|------|-----------|-------------|
|
||||
| Authorization | string | Yes | Bearer token |
|
||||
|
||||
### Example Request
|
||||
|
||||
```http
|
||||
GET /api/media-items/uuid/highlights
|
||||
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
|
||||
```
|
||||
|
||||
## Response (200 OK)
|
||||
|
||||
```json
|
||||
{
|
||||
"highlights": [
|
||||
{
|
||||
"id": "uuid",
|
||||
"media_item_id": "uuid",
|
||||
"user_id": "uuid",
|
||||
"selection_text": "Highlighted text passage...",
|
||||
"start_position": "epubcfi(/6/4/2:15)",
|
||||
"end_position": "epubcfi(/6/4/2:20)",
|
||||
"color": "#ffff00",
|
||||
"percentage_start": 0.45,
|
||||
"percentage_end": 0.47,
|
||||
"character_start": 15432,
|
||||
"character_end": 15480,
|
||||
"epubcfi_start": "epubcfi(/6/4/2:15)",
|
||||
"epubcfi_end": "epubcfi(/6/4/2:20)",
|
||||
"created_at": "2026-01-31T10:00:00Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Error Responses
|
||||
|
||||
| Code | Description |
|
||||
|------|-------------|
|
||||
| 401 | Invalid or expired token |
|
||||
| 404 | Media item not found |
|
||||
|
||||
## Try It Out
|
||||
|
||||
<!-- API Explorer will be inserted here in Phase 3 -->
|
||||
@@ -0,0 +1,55 @@
|
||||
# Update Highlight
|
||||
|
||||
Update an existing highlight.
|
||||
|
||||
**Endpoint**: `PUT /api/media-items/highlights/{highlight_id}`
|
||||
**Auth**: Required
|
||||
**Content-Type**: `application/json`
|
||||
|
||||
## Path Parameters
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
|-----------|------|-----------|-------------|
|
||||
| highlight_id | string | Yes | Highlight UUID |
|
||||
|
||||
## Request Body
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|--------|------|-----------|-------------|
|
||||
| selection_text | string | No | Updated highlighted text |
|
||||
| color | string | No | Updated highlight color (hex) |
|
||||
|
||||
### Example Request
|
||||
|
||||
```json
|
||||
{
|
||||
"selection_text": "Updated text",
|
||||
"color": "#00ff00"
|
||||
}
|
||||
```
|
||||
|
||||
## Response (200 OK)
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "uuid",
|
||||
"media_item_id": "uuid",
|
||||
"user_id": "uuid",
|
||||
"selection_text": "Updated text",
|
||||
"color": "#00ff00",
|
||||
"updated_at": "2026-01-31T10:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
## Error Responses
|
||||
|
||||
| Code | Description |
|
||||
|------|-------------|
|
||||
| 400 | Invalid highlight data |
|
||||
| 401 | Invalid or expired token |
|
||||
| 403 | User does not own this highlight |
|
||||
| 404 | Highlight not found |
|
||||
|
||||
## Try It Out
|
||||
|
||||
<!-- API Explorer will be inserted here in Phase 3 -->
|
||||
@@ -0,0 +1,51 @@
|
||||
# Add Library Folder
|
||||
|
||||
Add a folder to an existing library (Admin only).
|
||||
|
||||
**Endpoint**: `POST /api/libraries/{library_id}/folders`
|
||||
**Auth**: Required (Admin only)
|
||||
**Content-Type**: `application/json`
|
||||
|
||||
## Path Parameters
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
|-----------|------|-----------|-------------|
|
||||
| library_id | string | Yes | Library UUID |
|
||||
|
||||
## Request Body
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|--------|------|-----------|-------------|
|
||||
| folder_path | string | Yes | Absolute path to folder |
|
||||
|
||||
### Example Request
|
||||
|
||||
```json
|
||||
{
|
||||
"folder_path": "/path/to/comics"
|
||||
}
|
||||
```
|
||||
|
||||
## Response (201 Created)
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "uuid",
|
||||
"library_id": "uuid",
|
||||
"folder_path": "/path/to/comics",
|
||||
"created_at": "2026-01-31T10:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
## Error Responses
|
||||
|
||||
| Code | Description |
|
||||
|------|-------------|
|
||||
| 400 | Invalid folder path |
|
||||
| 401 | Invalid or expired token |
|
||||
| 403 | User is not an admin |
|
||||
| 404 | Library not found |
|
||||
|
||||
## Try It Out
|
||||
|
||||
<!-- API Explorer will be inserted here in Phase 3 -->
|
||||
@@ -0,0 +1,49 @@
|
||||
# Create Library
|
||||
|
||||
Create a new library (Admin only).
|
||||
|
||||
**Endpoint**: `POST /api/libraries`
|
||||
**Auth**: Required (Admin only)
|
||||
**Content-Type**: `application/json`
|
||||
|
||||
## Request Body
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|--------|------|-----------|-------------|
|
||||
| name | string | Yes | Library name |
|
||||
| description | string | No | Library description |
|
||||
| type | string | Yes | Library type (e.g., "ebooks", "comics", "audiobooks") |
|
||||
|
||||
### Example Request
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "Comics Collection",
|
||||
"description": "Digital comics",
|
||||
"type": "comics"
|
||||
}
|
||||
```
|
||||
|
||||
## Response (201 Created)
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "uuid",
|
||||
"name": "Comics Collection",
|
||||
"description": "Digital comics",
|
||||
"type": "comics",
|
||||
"created_at": "2026-01-31T10:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
## Error Responses
|
||||
|
||||
| Code | Description |
|
||||
|------|-------------|
|
||||
| 400 | Invalid input data |
|
||||
| 401 | Invalid or expired token |
|
||||
| 403 | User is not an admin |
|
||||
|
||||
## Try It Out
|
||||
|
||||
<!-- API Explorer will be inserted here in Phase 3 -->
|
||||
@@ -0,0 +1,55 @@
|
||||
# Get Library Details
|
||||
|
||||
Retrieve details of a specific library.
|
||||
|
||||
**Endpoint**: `GET /api/libraries/{library_id}`
|
||||
**Auth**: Required
|
||||
|
||||
## Path Parameters
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
|-----------|------|-----------|-------------|
|
||||
| library_id | string | Yes | Library UUID |
|
||||
|
||||
## Request Headers
|
||||
|
||||
| Header | Type | Required | Description |
|
||||
|--------|------|-----------|-------------|
|
||||
| Authorization | string | Yes | Bearer token |
|
||||
|
||||
### Example Request
|
||||
|
||||
```http
|
||||
GET /api/libraries/uuid-here
|
||||
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
|
||||
```
|
||||
|
||||
## Response (200 OK)
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "uuid",
|
||||
"name": "My Ebooks",
|
||||
"description": "Ebook collection",
|
||||
"type": "ebooks",
|
||||
"folders": [
|
||||
{
|
||||
"id": "uuid",
|
||||
"folder_path": "/path/to/ebooks"
|
||||
}
|
||||
],
|
||||
"is_visible": true
|
||||
}
|
||||
```
|
||||
|
||||
## Error Responses
|
||||
|
||||
| Code | Description |
|
||||
|------|-------------|
|
||||
| 401 | Invalid or expired token |
|
||||
| 403 | User does not have access to this library |
|
||||
| 404 | Library not found |
|
||||
|
||||
## Try It Out
|
||||
|
||||
<!-- API Explorer will be inserted here in Phase 3 -->
|
||||
@@ -0,0 +1,45 @@
|
||||
# Get Visible Libraries
|
||||
|
||||
Retrieve all libraries visible to the current user.
|
||||
|
||||
**Endpoint**: `GET /api/libraries/visible`
|
||||
**Auth**: Required
|
||||
|
||||
## Request Headers
|
||||
|
||||
| Header | Type | Required | Description |
|
||||
|--------|------|-----------|-------------|
|
||||
| Authorization | string | Yes | Bearer token |
|
||||
|
||||
### Example Request
|
||||
|
||||
```http
|
||||
GET /api/libraries/visible
|
||||
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
|
||||
```
|
||||
|
||||
## Response (200 OK)
|
||||
|
||||
```json
|
||||
{
|
||||
"libraries": [
|
||||
{
|
||||
"id": "uuid",
|
||||
"name": "My Ebooks",
|
||||
"description": "Ebook collection",
|
||||
"type_name": "ebooks",
|
||||
"is_visible": true
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Error Responses
|
||||
|
||||
| Code | Description |
|
||||
|------|-------------|
|
||||
| 401 | Invalid or expired token |
|
||||
|
||||
## Try It Out
|
||||
|
||||
<!-- API Explorer will be inserted here in Phase 3 -->
|
||||
@@ -0,0 +1,48 @@
|
||||
# Set Library Visibility
|
||||
|
||||
Set library visibility for a specific user (Admin only).
|
||||
|
||||
**Endpoint**: `POST /api/libraries/visibility`
|
||||
**Auth**: Required (Admin only)
|
||||
**Content-Type**: `application/json`
|
||||
|
||||
## Request Body
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|--------|------|-----------|-------------|
|
||||
| user_id | string | Yes | User UUID |
|
||||
| library_id | string | Yes | Library UUID |
|
||||
| is_visible | boolean | Yes | Whether library is visible to user |
|
||||
|
||||
### Example Request
|
||||
|
||||
```json
|
||||
{
|
||||
"user_id": "user-uuid",
|
||||
"library_id": "library-uuid",
|
||||
"is_visible": true
|
||||
}
|
||||
```
|
||||
|
||||
## Response (200 OK)
|
||||
|
||||
```json
|
||||
{
|
||||
"user_id": "user-uuid",
|
||||
"library_id": "library-uuid",
|
||||
"is_visible": true
|
||||
}
|
||||
```
|
||||
|
||||
## Error Responses
|
||||
|
||||
| Code | Description |
|
||||
|------|-------------|
|
||||
| 400 | Invalid input data |
|
||||
| 401 | Invalid or expired token |
|
||||
| 403 | User is not an admin |
|
||||
| 404 | User or library not found |
|
||||
|
||||
## Try It Out
|
||||
|
||||
<!-- API Explorer will be inserted here in Phase 3 -->
|
||||
@@ -0,0 +1,41 @@
|
||||
# Delete Media Item
|
||||
|
||||
Delete a media item from the library (Admin only).
|
||||
|
||||
**Endpoint**: `DELETE /api/media-items/{media_id}`
|
||||
**Auth**: Required (Admin only)
|
||||
|
||||
## Path Parameters
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
|-----------|------|-----------|-------------|
|
||||
| media_id | string | Yes | Media item UUID |
|
||||
|
||||
## Request Headers
|
||||
|
||||
| Header | Type | Required | Description |
|
||||
|--------|------|-----------|-------------|
|
||||
| Authorization | string | Yes | Bearer token |
|
||||
|
||||
### Example Request
|
||||
|
||||
```http
|
||||
DELETE /api/media-items/uuid-here
|
||||
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
|
||||
```
|
||||
|
||||
## Response (204 No Content)
|
||||
|
||||
Media item deleted successfully.
|
||||
|
||||
## Error Responses
|
||||
|
||||
| Code | Description |
|
||||
|------|-------------|
|
||||
| 401 | Invalid or expired token |
|
||||
| 403 | User is not an admin |
|
||||
| 404 | Media item not found |
|
||||
|
||||
## Try It Out
|
||||
|
||||
<!-- API Explorer will be inserted here in Phase 3 -->
|
||||
@@ -0,0 +1,67 @@
|
||||
# Filter & Sort Media Items
|
||||
|
||||
Filter and sort media items with advanced criteria.
|
||||
|
||||
**Endpoint**: `POST /api/media-items/filter`
|
||||
**Auth**: Required
|
||||
**Content-Type**: `application/json`
|
||||
|
||||
## Request Body
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|--------|------|-----------|-------------|
|
||||
| library_id | string | Yes | Library UUID |
|
||||
| author_filter | string | No | Filter by author name |
|
||||
| series_filter | string | No | Filter by series name |
|
||||
| genre_filter | string | No | Filter by genre |
|
||||
| year_min | integer | No | Minimum copyright year |
|
||||
| year_max | integer | No | Maximum copyright year |
|
||||
| has_cover | boolean | No | Filter by cover image existence |
|
||||
| sort | string | No | Sort field and order (e.g., "title ASC", "created_at DESC") |
|
||||
| limit | integer | No | Number of results (default 20) |
|
||||
| offset | integer | No | Number to skip |
|
||||
|
||||
### Example Request
|
||||
|
||||
```json
|
||||
{
|
||||
"library_id": "uuid",
|
||||
"author_filter": "Rowling",
|
||||
"series_filter": "Harry Potter",
|
||||
"genre_filter": "Fantasy",
|
||||
"year_min": 1997,
|
||||
"year_max": 2007,
|
||||
"has_cover": true,
|
||||
"sort": "title ASC",
|
||||
"limit": 20,
|
||||
"offset": 0
|
||||
}
|
||||
```
|
||||
|
||||
## Response (200 OK)
|
||||
|
||||
```json
|
||||
{
|
||||
"media_items": [
|
||||
{
|
||||
"id": "uuid",
|
||||
"title": "Book Title",
|
||||
"author": "Author Name",
|
||||
"match_score": 0.95
|
||||
}
|
||||
],
|
||||
"total": 7
|
||||
}
|
||||
```
|
||||
|
||||
## Error Responses
|
||||
|
||||
| Code | Description |
|
||||
|------|-------------|
|
||||
| 400 | Invalid filter parameters |
|
||||
| 401 | Invalid or expired token |
|
||||
| 403 | User does not have access to this library |
|
||||
|
||||
## Try It Out
|
||||
|
||||
<!-- API Explorer will be inserted here in Phase 3 -->
|
||||
@@ -0,0 +1,61 @@
|
||||
# Get Media Item
|
||||
|
||||
Retrieve details of a specific media item.
|
||||
|
||||
**Endpoint**: `GET /api/media-items/{media_id}`
|
||||
**Auth**: Required
|
||||
|
||||
## Path Parameters
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
|-----------|------|-----------|-------------|
|
||||
| media_id | string | Yes | Media item UUID |
|
||||
|
||||
## Request Headers
|
||||
|
||||
| Header | Type | Required | Description |
|
||||
|--------|------|-----------|-------------|
|
||||
| Authorization | string | Yes | Bearer token |
|
||||
|
||||
### Example Request
|
||||
|
||||
```http
|
||||
GET /api/media-items/uuid-here
|
||||
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
|
||||
```
|
||||
|
||||
## Response (200 OK)
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "uuid",
|
||||
"library_id": "uuid",
|
||||
"title": "Book Title",
|
||||
"author": "Author Name",
|
||||
"description": "Book description",
|
||||
"file_path": "/path/to/book.epub",
|
||||
"file_size": 1024000,
|
||||
"mime_type": "application/epub+zip",
|
||||
"cover_image_path": "/path/to/cover.jpg",
|
||||
"series": "Series Name",
|
||||
"series_number": 1,
|
||||
"tags": "sci-fi, space opera",
|
||||
"language": "en",
|
||||
"page_count": 350,
|
||||
"genre": "Science Fiction",
|
||||
"copyright_year": 2023,
|
||||
"created_at": "2026-01-31T10:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
## Error Responses
|
||||
|
||||
| Code | Description |
|
||||
|------|-------------|
|
||||
| 401 | Invalid or expired token |
|
||||
| 403 | User does not have access to this media item |
|
||||
| 404 | Media item not found |
|
||||
|
||||
## Try It Out
|
||||
|
||||
<!-- API Explorer will be inserted here in Phase 3 -->
|
||||
@@ -0,0 +1,68 @@
|
||||
# List Media Items
|
||||
|
||||
Retrieve a paginated list of media items from a library.
|
||||
|
||||
**Endpoint**: `GET /api/media-items`
|
||||
**Auth**: Required
|
||||
|
||||
## Query Parameters
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
|-----------|------|-----------|-------------|
|
||||
| library_id | string | Yes | Library UUID |
|
||||
| limit | integer | No | Number of items to return (max 100, default 20) |
|
||||
| offset | integer | No | Number of items to skip |
|
||||
|
||||
## Request Headers
|
||||
|
||||
| Header | Type | Required | Description |
|
||||
|--------|------|-----------|-------------|
|
||||
| Authorization | string | Yes | Bearer token |
|
||||
|
||||
### Example Request
|
||||
|
||||
```http
|
||||
GET /api/media-items?library_id=uuid&limit=20&offset=0
|
||||
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
|
||||
```
|
||||
|
||||
## Response (200 OK)
|
||||
|
||||
```json
|
||||
{
|
||||
"media_items": [
|
||||
{
|
||||
"id": "uuid",
|
||||
"library_id": "uuid",
|
||||
"title": "Book Title",
|
||||
"author": "Author Name",
|
||||
"description": "Book description",
|
||||
"file_path": "/path/to/book.epub",
|
||||
"file_size": 1024000,
|
||||
"mime_type": "application/epub+zip",
|
||||
"cover_image_path": "/path/to/cover.jpg",
|
||||
"series": "Series Name",
|
||||
"series_number": 1,
|
||||
"tags": "sci-fi, space opera",
|
||||
"language": "en",
|
||||
"page_count": 350,
|
||||
"genre": "Science Fiction",
|
||||
"copyright_year": 2023,
|
||||
"created_at": "2026-01-31T10:00:00Z"
|
||||
}
|
||||
],
|
||||
"total": 100
|
||||
}
|
||||
```
|
||||
|
||||
## Error Responses
|
||||
|
||||
| Code | Description |
|
||||
|------|-------------|
|
||||
| 400 | Invalid query parameters |
|
||||
| 401 | Invalid or expired token |
|
||||
| 403 | User does not have access to this library |
|
||||
|
||||
## Try It Out
|
||||
|
||||
<!-- API Explorer will be inserted here in Phase 3 -->
|
||||
@@ -0,0 +1,54 @@
|
||||
# Search Media Items
|
||||
|
||||
Search for media items by title, author, or description.
|
||||
|
||||
**Endpoint**: `GET /api/media-items/search`
|
||||
**Auth**: Required
|
||||
|
||||
## Query Parameters
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
|-----------|------|-----------|-------------|
|
||||
| q | string | Yes | Search query (minimum 2 characters) |
|
||||
| limit | integer | No | Number of results (default 20) |
|
||||
| offset | integer | No | Number to skip |
|
||||
|
||||
## Request Headers
|
||||
|
||||
| Header | Type | Required | Description |
|
||||
|--------|------|-----------|-------------|
|
||||
| Authorization | string | Yes | Bearer token |
|
||||
|
||||
### Example Request
|
||||
|
||||
```http
|
||||
GET /api/media-items/search?q=Harry+Potter&limit=20&offset=0
|
||||
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
|
||||
```
|
||||
|
||||
## Response (200 OK)
|
||||
|
||||
```json
|
||||
{
|
||||
"results": [
|
||||
{
|
||||
"id": "uuid",
|
||||
"title": "Book Title",
|
||||
"author": "Author Name",
|
||||
"match_score": 0.95
|
||||
}
|
||||
],
|
||||
"total": 15
|
||||
}
|
||||
```
|
||||
|
||||
## Error Responses
|
||||
|
||||
| Code | Description |
|
||||
|------|-------------|
|
||||
| 400 | Invalid search query (too short) |
|
||||
| 401 | Invalid or expired token |
|
||||
|
||||
## Try It Out
|
||||
|
||||
<!-- API Explorer will be inserted here in Phase 3 -->
|
||||
@@ -0,0 +1,62 @@
|
||||
# Update Media Item
|
||||
|
||||
Update media item metadata (Admin only).
|
||||
|
||||
**Endpoint**: `PUT /api/media-items/{media_id}`
|
||||
**Auth**: Required (Admin only)
|
||||
**Content-Type**: `application/json`
|
||||
|
||||
## Path Parameters
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
|-----------|------|-----------|-------------|
|
||||
| media_id | string | Yes | Media item UUID |
|
||||
|
||||
## Request Body
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|--------|------|-----------|-------------|
|
||||
| title | string | No | Updated title |
|
||||
| author | string | No | Updated author |
|
||||
| description | string | No | Updated description |
|
||||
| series | string | No | Series name |
|
||||
| series_number | integer | No | Number in series |
|
||||
|
||||
### Example Request
|
||||
|
||||
```json
|
||||
{
|
||||
"title": "Updated Title",
|
||||
"author": "Updated Author",
|
||||
"description": "Updated description",
|
||||
"series": "Series",
|
||||
"series_number": 2
|
||||
}
|
||||
```
|
||||
|
||||
## Response (200 OK)
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "uuid",
|
||||
"title": "Updated Title",
|
||||
"author": "Updated Author",
|
||||
"description": "Updated description",
|
||||
"series": "Series",
|
||||
"series_number": 2,
|
||||
"updated_at": "2026-01-31T10:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
## Error Responses
|
||||
|
||||
| Code | Description |
|
||||
|------|-------------|
|
||||
| 400 | Invalid input data |
|
||||
| 401 | Invalid or expired token |
|
||||
| 403 | User is not an admin |
|
||||
| 404 | Media item not found |
|
||||
|
||||
## Try It Out
|
||||
|
||||
<!-- API Explorer will be inserted here in Phase 3 -->
|
||||
@@ -0,0 +1,60 @@
|
||||
# Create Note
|
||||
|
||||
Create a new note for a media item.
|
||||
|
||||
**Endpoint**: `POST /api/media-items/{media_id}/notes`
|
||||
**Auth**: Required
|
||||
**Content-Type**: `application/json`
|
||||
|
||||
## Path Parameters
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
|-----------|------|-----------|-------------|
|
||||
| media_id | string | Yes | Media item UUID |
|
||||
|
||||
## Request Body
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|--------|------|-----------|-------------|
|
||||
| content | string | Yes | Note content |
|
||||
| position | string | No | Location reference (e.g., epubcfi) |
|
||||
| percentage_location | float | No | Location as percentage (0-1) |
|
||||
| epubcfi_location | string | No | EPUB CFI location |
|
||||
|
||||
### Example Request
|
||||
|
||||
```json
|
||||
{
|
||||
"content": "This is a note",
|
||||
"position": "epubcfi(/6/4/2:15)",
|
||||
"percentage_location": 0.45,
|
||||
"epubcfi_location": "epubcfi(/6/4/2:15)"
|
||||
}
|
||||
```
|
||||
|
||||
## Response (201 Created)
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "uuid",
|
||||
"media_item_id": "uuid",
|
||||
"user_id": "uuid",
|
||||
"content": "This is a note",
|
||||
"position": "epubcfi(/6/4/2:15)",
|
||||
"percentage_location": 0.45,
|
||||
"epubcfi_location": "epubcfi(/6/4/2:15)",
|
||||
"created_at": "2026-01-31T10:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
## Error Responses
|
||||
|
||||
| Code | Description |
|
||||
|------|-------------|
|
||||
| 400 | Invalid note data |
|
||||
| 401 | Invalid or expired token |
|
||||
| 404 | Media item not found |
|
||||
|
||||
## Try It Out
|
||||
|
||||
<!-- API Explorer will be inserted here in Phase 3 -->
|
||||
@@ -0,0 +1,41 @@
|
||||
# Delete Note
|
||||
|
||||
Delete a note.
|
||||
|
||||
**Endpoint**: `DELETE /api/media-items/notes/{note_id}`
|
||||
**Auth**: Required
|
||||
|
||||
## Path Parameters
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
|-----------|------|-----------|-------------|
|
||||
| note_id | string | Yes | Note UUID |
|
||||
|
||||
## Request Headers
|
||||
|
||||
| Header | Type | Required | Description |
|
||||
|--------|------|-----------|-------------|
|
||||
| Authorization | string | Yes | Bearer token |
|
||||
|
||||
### Example Request
|
||||
|
||||
```http
|
||||
DELETE /api/media-items/notes/uuid
|
||||
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
|
||||
```
|
||||
|
||||
## Response (204 No Content)
|
||||
|
||||
Note deleted successfully.
|
||||
|
||||
## Error Responses
|
||||
|
||||
| Code | Description |
|
||||
|------|-------------|
|
||||
| 401 | Invalid or expired token |
|
||||
| 403 | User does not own this note |
|
||||
| 404 | Note not found |
|
||||
|
||||
## Try It Out
|
||||
|
||||
<!-- API Explorer will be inserted here in Phase 3 -->
|
||||
@@ -0,0 +1,58 @@
|
||||
# Get Notes
|
||||
|
||||
Retrieve all notes for a specific media item.
|
||||
|
||||
**Endpoint**: `GET /api/media-items/{media_id}/notes`
|
||||
**Auth**: Required
|
||||
|
||||
## Path Parameters
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
|-----------|------|-----------|-------------|
|
||||
| media_id | string | Yes | Media item UUID |
|
||||
|
||||
## Request Headers
|
||||
|
||||
| Header | Type | Required | Description |
|
||||
|--------|------|-----------|-------------|
|
||||
| Authorization | string | Yes | Bearer token |
|
||||
|
||||
### Example Request
|
||||
|
||||
```http
|
||||
GET /api/media-items/uuid/notes
|
||||
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
|
||||
```
|
||||
|
||||
## Response (200 OK)
|
||||
|
||||
```json
|
||||
{
|
||||
"notes": [
|
||||
{
|
||||
"id": "uuid",
|
||||
"media_item_id": "uuid",
|
||||
"user_id": "uuid",
|
||||
"content": "This is an interesting passage...",
|
||||
"position": "epubcfi(/6/4/2:15)",
|
||||
"percentage_location": 0.45,
|
||||
"character_start": 15432,
|
||||
"character_end": 15480,
|
||||
"epubcfi_location": "epubcfi(/6/4/2:15)",
|
||||
"created_at": "2026-01-31T10:00:00Z",
|
||||
"updated_at": "2026-01-31T10:00:00Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Error Responses
|
||||
|
||||
| Code | Description |
|
||||
|------|-------------|
|
||||
| 401 | Invalid or expired token |
|
||||
| 404 | Media item not found |
|
||||
|
||||
## Try It Out
|
||||
|
||||
<!-- API Explorer will be inserted here in Phase 3 -->
|
||||
@@ -0,0 +1,55 @@
|
||||
# Update Note
|
||||
|
||||
Update an existing note.
|
||||
|
||||
**Endpoint**: `PUT /api/media-items/notes/{note_id}`
|
||||
**Auth**: Required
|
||||
**Content-Type**: `application/json`
|
||||
|
||||
## Path Parameters
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
|-----------|------|-----------|-------------|
|
||||
| note_id | string | Yes | Note UUID |
|
||||
|
||||
## Request Body
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|--------|------|-----------|-------------|
|
||||
| content | string | No | Updated note content |
|
||||
| position | string | No | Updated location reference |
|
||||
|
||||
### Example Request
|
||||
|
||||
```json
|
||||
{
|
||||
"content": "Updated note content",
|
||||
"position": "epubcfi(/6/4/2:20)"
|
||||
}
|
||||
```
|
||||
|
||||
## Response (200 OK)
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "uuid",
|
||||
"media_item_id": "uuid",
|
||||
"user_id": "uuid",
|
||||
"content": "Updated note content",
|
||||
"position": "epubcfi(/6/4/2:20)",
|
||||
"updated_at": "2026-01-31T10:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
## Error Responses
|
||||
|
||||
| Code | Description |
|
||||
|------|-------------|
|
||||
| 400 | Invalid note data |
|
||||
| 401 | Invalid or expired token |
|
||||
| 403 | User does not own this note |
|
||||
| 404 | Note not found |
|
||||
|
||||
## Try It Out
|
||||
|
||||
<!-- API Explorer will be inserted here in Phase 3 -->
|
||||
@@ -0,0 +1,72 @@
|
||||
# OPDS Acquisition
|
||||
|
||||
Download books and list available formats.
|
||||
|
||||
## Download Book with Format Conversion
|
||||
|
||||
**Endpoint**: `GET /opds/devices/{deviceId}/download/{bookId}`
|
||||
**Auth**: Device token required
|
||||
|
||||
### Query Parameters
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
|-----------|------|-----------|-------------|
|
||||
| format | string | No | Book format: `epub` (default), `kepub` |
|
||||
|
||||
### Example Request
|
||||
|
||||
```http
|
||||
GET /opds/devices/kobo-id/download/uuid-123?format=kepub
|
||||
```
|
||||
|
||||
### Response (200 OK)
|
||||
|
||||
**Headers:**
|
||||
- `Content-Type`: `application/epub+zip` or `application/vnd.kobo+xml+zip`
|
||||
- `Content-Disposition`: attachment; filename="The Hobbit.epub"
|
||||
- `X-Bookhoard-UUID`: uuid-123
|
||||
- `X-Bookhoard-SHA256`: abc123... (original hash)
|
||||
- `X-Bookhoard-KEPUB-SHA256`: xyz789... (KEPUB hash if format=kepub)
|
||||
|
||||
**Body:** Book file binary data
|
||||
|
||||
## List Available Formats
|
||||
|
||||
**Endpoint**: `GET /opds/devices/{deviceId}/formats/{bookId}`
|
||||
**Auth**: Device token required
|
||||
|
||||
### Example Request
|
||||
|
||||
```http
|
||||
GET /opds/devices/kobo-id/formats/uuid-123
|
||||
```
|
||||
|
||||
### Response (200 OK)
|
||||
|
||||
```json
|
||||
{
|
||||
"media_item_id": "uuid-123",
|
||||
"formats": [
|
||||
{
|
||||
"format_type": "epub",
|
||||
"file_path": "/path/to/book.epub",
|
||||
"file_sha256": "abc123...",
|
||||
"file_size_bytes": 2456789,
|
||||
"mime_type": "application/epub+zip",
|
||||
"available": true
|
||||
},
|
||||
{
|
||||
"format_type": "kepub",
|
||||
"file_path": "/cache/book.kepub.epub",
|
||||
"file_sha256": "xyz789...",
|
||||
"file_size_bytes": 2478932,
|
||||
"mime_type": "application/vnd.kobo+xml+zip",
|
||||
"available": true
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Try It Out
|
||||
|
||||
<!-- API Explorer will be inserted here in Phase 3 -->
|
||||
@@ -0,0 +1,81 @@
|
||||
# OPDS Feeds
|
||||
|
||||
Bookhoard provides OPDS 1.2 feeds for device compatibility.
|
||||
|
||||
## Get Device Catalog
|
||||
|
||||
**Endpoint**: `GET /opds/devices/{deviceId}/catalog`
|
||||
**Auth**: Device token required
|
||||
|
||||
### Query Parameters
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
|-----------|------|-----------|-------------|
|
||||
| page | integer | No | Page number (default: 1) |
|
||||
| per_page | integer | No | Items per page (default: 50, max: 200) |
|
||||
|
||||
### Example Request
|
||||
|
||||
```http
|
||||
GET /opds/devices/kobo-id/catalog?page=1&per_page=50
|
||||
```
|
||||
|
||||
### Response (200 OK - OPDS 1.2 XML)
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<feed xmlns="http://www.w3.org/2005/Atom"
|
||||
xmlns:opds="http://opds-spec.org/2010/"
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/">
|
||||
<id>urn:uuid:device-id</id>
|
||||
<title>Bookhoard Library</title>
|
||||
<updated>2026-02-01T12:00:00Z</updated>
|
||||
|
||||
<link rel="self" href="http://localhost:8765/opds/devices/kobo-id/catalog"/>
|
||||
<link rel="search" href="http://localhost:8765/opds/devices/kobo-id/search"/>
|
||||
<link rel="start" href="http://localhost:8765/opds/devices/kobo-id/nav"/>
|
||||
|
||||
<entry>
|
||||
<id>urn:uuid:bookhoard-uuid-123</id>
|
||||
<dc:title>The Hobbit</dc:title>
|
||||
<dc:creator>J.R.R. Tolkien</dc:creator>
|
||||
<updated>2026-02-01T10:00:00Z</updated>
|
||||
|
||||
<link href="http://localhost:8765/opds/devices/kobo-id/download/uuid-123"
|
||||
type="application/epub+zip"
|
||||
rel="http://opds-spec.org/acquisition/open-access"/>
|
||||
|
||||
<link href="http://localhost:8765/opds/devices/kobo-id/download/uuid-123?format=kepub"
|
||||
type="application/vnd.kobo+xml+zip"
|
||||
rel="alternate"/>
|
||||
|
||||
<dc:identifier id="bookhoard">uuid-123</dc:identifier>
|
||||
<meta property="bookhoard:sha256">abc123...</meta>
|
||||
</entry>
|
||||
</feed>
|
||||
```
|
||||
|
||||
## Search OPDS Catalog
|
||||
|
||||
**Endpoint**: `GET /opds/devices/{deviceId}/search`
|
||||
**Auth**: Device token required
|
||||
|
||||
### Query Parameters
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
|-----------|------|-----------|-------------|
|
||||
| q | string | Yes | Search query |
|
||||
|
||||
### Example Request
|
||||
|
||||
```http
|
||||
GET /opds/devices/kobo-id/search?q=Hobbit
|
||||
```
|
||||
|
||||
### Response (200 OK - OPDS 1.2 XML with search results)
|
||||
|
||||
Returns OPDS feed with matching books.
|
||||
|
||||
## Try It Out
|
||||
|
||||
<!-- API Explorer will be inserted here in Phase 3 -->
|
||||
@@ -0,0 +1,56 @@
|
||||
# OPDS Publication
|
||||
|
||||
OPDS navigation and publication feeds.
|
||||
|
||||
## Navigation Feed
|
||||
|
||||
**Endpoint**: `GET /opds/devices/{deviceId}/nav`
|
||||
**Auth**: Device token required
|
||||
|
||||
### Example Request
|
||||
|
||||
```http
|
||||
GET /opds/devices/kobo-id/nav
|
||||
```
|
||||
|
||||
### Response (200 OK - OPDS 1.2 Navigation XML)
|
||||
|
||||
Returns OPDS navigation feed with links to:
|
||||
- Root catalog
|
||||
- Search
|
||||
- Collections/shelves
|
||||
- Filtered views (by author, series, etc.)
|
||||
|
||||
## Publication Feeds by Collection
|
||||
|
||||
**Endpoint**: `GET /opds/devices/{deviceId}/collections/{collectionId}`
|
||||
**Auth**: Device token required
|
||||
|
||||
### Example Request
|
||||
|
||||
```http
|
||||
GET /opds/devices/kobo-id/collections/collection-uuid
|
||||
```
|
||||
|
||||
### Response (200 OK - OPDS 1.2 XML)
|
||||
|
||||
Returns OPDS feed with books in the specified collection.
|
||||
|
||||
## Publication Feeds by Shelf
|
||||
|
||||
**Endpoint**: `GET /opds/devices/{deviceId}/shelves/{shelfName}`
|
||||
**Auth**: Device token required
|
||||
|
||||
### Example Request
|
||||
|
||||
```http
|
||||
GET /opds/devices/kobo-id/shelves/Favorites
|
||||
```
|
||||
|
||||
### Response (200 OK - OPDS 1.2 XML)
|
||||
|
||||
Returns OPDS feed with books on the specified device shelf.
|
||||
|
||||
## Try It Out
|
||||
|
||||
<!-- API Explorer will be inserted here in Phase 3 -->
|
||||
@@ -0,0 +1,40 @@
|
||||
# Delete Reading Progress
|
||||
|
||||
Delete reading progress for a media item.
|
||||
|
||||
**Endpoint**: `DELETE /api/media-items/{media_id}/progress`
|
||||
**Auth**: Required
|
||||
|
||||
## Path Parameters
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
|-----------|------|-----------|-------------|
|
||||
| media_id | string | Yes | Media item UUID |
|
||||
|
||||
## Request Headers
|
||||
|
||||
| Header | Type | Required | Description |
|
||||
|--------|------|-----------|-------------|
|
||||
| Authorization | string | Yes | Bearer token |
|
||||
|
||||
### Example Request
|
||||
|
||||
```http
|
||||
DELETE /api/media-items/uuid/progress
|
||||
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
|
||||
```
|
||||
|
||||
## Response (204 No Content)
|
||||
|
||||
Progress deleted successfully.
|
||||
|
||||
## Error Responses
|
||||
|
||||
| Code | Description |
|
||||
|------|-------------|
|
||||
| 401 | Invalid or expired token |
|
||||
| 404 | Media item not found |
|
||||
|
||||
## Try It Out
|
||||
|
||||
<!-- API Explorer will be inserted here in Phase 3 -->
|
||||
@@ -0,0 +1,56 @@
|
||||
# Get Reading Progress
|
||||
|
||||
Retrieve reading progress for a specific media item.
|
||||
|
||||
**Endpoint**: `GET /api/media-items/{media_id}/progress`
|
||||
**Auth**: Required
|
||||
|
||||
## Path Parameters
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
|-----------|------|-----------|-------------|
|
||||
| media_id | string | Yes | Media item UUID |
|
||||
|
||||
## Request Headers
|
||||
|
||||
| Header | Type | Required | Description |
|
||||
|--------|------|-----------|-------------|
|
||||
| Authorization | string | Yes | Bearer token |
|
||||
|
||||
### Example Request
|
||||
|
||||
```http
|
||||
GET /api/media-items/uuid/progress
|
||||
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
|
||||
```
|
||||
|
||||
## Response (200 OK)
|
||||
|
||||
```json
|
||||
{
|
||||
"media_item_id": "uuid",
|
||||
"user_id": "uuid",
|
||||
"current_page": 45,
|
||||
"total_pages": 200,
|
||||
"percentage": 0.225,
|
||||
"character_offset": 15432,
|
||||
"epubcfi": "epubcfi(/6/4/2:15)",
|
||||
"chapter": 3,
|
||||
"chapter_progress": 0.5,
|
||||
"last_read_at": "2026-01-31T10:00:00Z",
|
||||
"format_group": "reflowable",
|
||||
"viewport_y": 0.12,
|
||||
"zoom_level": 1.0
|
||||
}
|
||||
```
|
||||
|
||||
## Error Responses
|
||||
|
||||
| Code | Description |
|
||||
|------|-------------|
|
||||
| 401 | Invalid or expired token |
|
||||
| 404 | Media item not found |
|
||||
|
||||
## Try It Out
|
||||
|
||||
<!-- API Explorer will be inserted here in Phase 3 -->
|
||||
@@ -0,0 +1,72 @@
|
||||
# Update Reading Progress
|
||||
|
||||
Update reading progress for a media item. This will sync across all devices via WebSocket.
|
||||
|
||||
**Endpoint**: `PUT /api/media-items/{media_id}/progress`
|
||||
**Auth**: Required
|
||||
**Content-Type**: `application/json`
|
||||
|
||||
## Path Parameters
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
|-----------|------|-----------|-------------|
|
||||
| media_id | string | Yes | Media item UUID |
|
||||
|
||||
## Request Body
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|--------|------|-----------|-------------|
|
||||
| source | string | Yes | Progress source (e.g., "web", "koreader", "kobo") |
|
||||
| location | object | Yes | Location information |
|
||||
| location.percentage | float | No | Progress percentage (0-1) |
|
||||
| location.epubcfi | string | No | EPUB CFI location |
|
||||
| location.character | integer | No | Character offset |
|
||||
| location.chapter | integer | No | Chapter number |
|
||||
| location.page | integer | No | Current page |
|
||||
| location.total_pages | integer | No | Total pages |
|
||||
| device_metadata | object | No | Device metadata |
|
||||
| device_metadata.device_type | string | No | Device type |
|
||||
| device_metadata.user_agent | string | No | User agent string |
|
||||
|
||||
### Example Request
|
||||
|
||||
```json
|
||||
{
|
||||
"source": "web",
|
||||
"location": {
|
||||
"percentage": 0.45678,
|
||||
"epubcfi": "epubcfi(/6/4/2:15)",
|
||||
"character": 15432,
|
||||
"chapter": 3,
|
||||
"page": 89,
|
||||
"total_pages": 200
|
||||
},
|
||||
"device_metadata": {
|
||||
"device_type": "web",
|
||||
"user_agent": "Mozilla/5.0..."
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Response (200 OK)
|
||||
|
||||
```json
|
||||
{
|
||||
"sync_status": "success",
|
||||
"progress_updated": true,
|
||||
"devices_notified": ["device-1", "device-2"],
|
||||
"broadcast": true
|
||||
}
|
||||
```
|
||||
|
||||
## Error Responses
|
||||
|
||||
| Code | Description |
|
||||
|------|-------------|
|
||||
| 400 | Invalid location data |
|
||||
| 401 | Invalid or expired token |
|
||||
| 404 | Media item not found |
|
||||
|
||||
## Try It Out
|
||||
|
||||
<!-- API Explorer will be inserted here in Phase 3 -->
|
||||
@@ -0,0 +1,52 @@
|
||||
# Create Rating
|
||||
|
||||
Set a rating for a media item. Creates a new rating or updates an existing one.
|
||||
|
||||
**Endpoint**: `POST /api/media-items/{media_id}/rating`
|
||||
**Auth**: Required
|
||||
**Content-Type**: `application/json`
|
||||
|
||||
## Path Parameters
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
|-----------|------|-----------|-------------|
|
||||
| media_id | string | Yes | Media item UUID |
|
||||
|
||||
## Request Body
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|--------|------|-----------|-------------|
|
||||
| rating | integer | Yes | Rating from 1-10 |
|
||||
|
||||
### Example Request
|
||||
|
||||
```json
|
||||
{
|
||||
"rating": 8
|
||||
}
|
||||
```
|
||||
|
||||
## Response (201 Created)
|
||||
|
||||
```json
|
||||
{
|
||||
"rating": 8,
|
||||
"user_id": "uuid",
|
||||
"media_item_id": "uuid",
|
||||
"created_at": "2026-01-31T10:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
**Rating Scale**: 1-10 (odd numbers = half-stars: 1=0.5★, 2=1★, 3=1.5★, ..., 9=4.5★, 10=5★)
|
||||
|
||||
## Error Responses
|
||||
|
||||
| Code | Description |
|
||||
|------|-------------|
|
||||
| 400 | Invalid rating (must be 1-10) |
|
||||
| 401 | Invalid or expired token |
|
||||
| 404 | Media item not found |
|
||||
|
||||
## Try It Out
|
||||
|
||||
<!-- API Explorer will be inserted here in Phase 3 -->
|
||||
@@ -0,0 +1,48 @@
|
||||
# Get Rating
|
||||
|
||||
Retrieve the current user's rating for a media item.
|
||||
|
||||
**Endpoint**: `GET /api/media-items/{media_id}/rating`
|
||||
**Auth**: Required
|
||||
|
||||
## Path Parameters
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
|-----------|------|-----------|-------------|
|
||||
| media_id | string | Yes | Media item UUID |
|
||||
|
||||
## Request Headers
|
||||
|
||||
| Header | Type | Required | Description |
|
||||
|--------|------|-----------|-------------|
|
||||
| Authorization | string | Yes | Bearer token |
|
||||
|
||||
### Example Request
|
||||
|
||||
```http
|
||||
GET /api/media-items/uuid/rating
|
||||
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
|
||||
```
|
||||
|
||||
## Response (200 OK)
|
||||
|
||||
```json
|
||||
{
|
||||
"rating": 8,
|
||||
"user_id": "uuid",
|
||||
"media_item_id": "uuid"
|
||||
}
|
||||
```
|
||||
|
||||
**Rating Scale**: 1-10 (odd numbers = half-stars: 1=0.5★, 2=1★, 3=1.5★, ..., 9=4.5★, 10=5★)
|
||||
|
||||
## Error Responses
|
||||
|
||||
| Code | Description |
|
||||
|------|-------------|
|
||||
| 401 | Invalid or expired token |
|
||||
| 404 | Media item not found or no rating set |
|
||||
|
||||
## Try It Out
|
||||
|
||||
<!-- API Explorer will be inserted here in Phase 3 -->
|
||||
@@ -0,0 +1,103 @@
|
||||
# Kobo Sync Protocol
|
||||
|
||||
Kobo uses a proprietary sync protocol with JSON payloads.
|
||||
|
||||
## Kobo Markup Sync
|
||||
|
||||
**Endpoint**: `POST /api/sync/kobo/markup`
|
||||
**Auth**: Device token required
|
||||
**Content-Type**: `application/json`
|
||||
|
||||
### Request Headers
|
||||
|
||||
| Header | Type | Required | Description |
|
||||
|--------|------|-----------|-------------|
|
||||
| Authorization | string | Yes | Bearer device token |
|
||||
| x-kobo-device | string | Yes | JSON device info |
|
||||
| Content-Type | string | Yes | application/json |
|
||||
|
||||
### Request Body
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|--------|------|-----------|-------------|
|
||||
| ReadingSync | array | No | Array of reading progress data |
|
||||
| ReadingSync[].ContentId | string | Yes | Book UUID |
|
||||
| ReadingSync[].PercentRead | float | Yes | Progress percentage (0-100) |
|
||||
| ReadingSync[].EntitlementId | string | Yes | Kobo entitlement ID |
|
||||
| ReadingSync[].RemainingTimeMinutes | integer | No | Estimated remaining time |
|
||||
| ReadingSync[].LastModified | string | Yes | ISO 8601 timestamp |
|
||||
| BookmarkSync | array | No | Array of bookmarks/highlights |
|
||||
|
||||
### Example Request
|
||||
|
||||
```http
|
||||
POST /api/sync/kobo/markup
|
||||
Authorization: Bearer device-token
|
||||
x-kobo-device: {"DeviceId":"device-id","Model":"Kobo Clara"}
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"ReadingSync": [
|
||||
{
|
||||
"ContentId": "book-uuid",
|
||||
"PercentRead": 45.6,
|
||||
"EntitlementId": "entitlement-id",
|
||||
"RemainingTimeMinutes": 120,
|
||||
"LastModified": "2026-01-30T20:00:00Z"
|
||||
}
|
||||
],
|
||||
"BookmarkSync": [
|
||||
{
|
||||
"ContentId": "book-uuid",
|
||||
"BookmarkText": "highlighted text",
|
||||
"BookmarkType": "annotation",
|
||||
"BookmarkTitle": "Chapter 3"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Response (200 OK)
|
||||
|
||||
```json
|
||||
{
|
||||
"Status": "Success",
|
||||
"MarkupsSynced": 5,
|
||||
"BookmarksSynced": 3
|
||||
}
|
||||
```
|
||||
|
||||
## Kobo Library Fetch
|
||||
|
||||
**Endpoint**: `GET /api/sync/kobo/library`
|
||||
**Auth**: Device token required
|
||||
|
||||
### Example Request
|
||||
|
||||
```http
|
||||
GET /api/sync/kobo/library
|
||||
Authorization: Bearer device-token
|
||||
```
|
||||
|
||||
### Response (200 OK)
|
||||
|
||||
```json
|
||||
{
|
||||
"library_sync": [
|
||||
{
|
||||
"ContentId": "book-uuid",
|
||||
"ContentType": "6",
|
||||
"Title": "Book Title",
|
||||
"Author": "Author Name",
|
||||
"PercentRead": 42.3,
|
||||
"PagesRemaining": 115,
|
||||
"BookmarkCount": 3,
|
||||
"LastModified": "2026-01-30T20:00:00Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Try It Out
|
||||
|
||||
<!-- API Explorer will be inserted here in Phase 3 -->
|
||||
@@ -0,0 +1,125 @@
|
||||
# KOReader Sync Protocol
|
||||
|
||||
KOReader uses a custom JSON-based sync protocol.
|
||||
|
||||
## KOReader Progress Sync
|
||||
|
||||
**Endpoint**: `POST /api/sync/koreader/progress`
|
||||
**Auth**: Device token required
|
||||
**Content-Type**: `application/json`
|
||||
|
||||
### Request Headers
|
||||
|
||||
| Header | Type | Required | Description |
|
||||
|--------|------|-----------|-------------|
|
||||
| Authorization | string | Yes | Bearer device token |
|
||||
| Content-Type | string | Yes | application/json |
|
||||
|
||||
### Request Body
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|--------|------|-----------|-------------|
|
||||
| library_id | string | No | Library UUID |
|
||||
| books | array | Yes | Array of book sync data |
|
||||
| books[].uuid | string | Yes | Book UUID |
|
||||
| books[].title | string | Yes | Book title |
|
||||
| books[].authors | array | Yes | Array of author names |
|
||||
| books[].progress | float | Yes | Progress percentage (0-1) |
|
||||
| books[].percentage | float | Yes | Progress percentage (0-1) |
|
||||
| books[].last_read | string | Yes | ISO 8601 timestamp |
|
||||
| books[].chapter | integer | No | Current chapter |
|
||||
| books[].epubcfi | string | No | EPUB CFI location |
|
||||
| books[].character | integer | No | Character offset |
|
||||
| books[].bookmarks | array | No | Array of bookmarks/highlights |
|
||||
|
||||
### Example Request
|
||||
|
||||
```json
|
||||
{
|
||||
"library_id": "optional-uuid",
|
||||
"books": [
|
||||
{
|
||||
"uuid": "book-uuid",
|
||||
"title": "Book Title",
|
||||
"authors": ["Author Name"],
|
||||
"progress": 0.45,
|
||||
"percentage": 0.45,
|
||||
"last_read": "2026-01-30T20:00:00Z",
|
||||
"chapter": 3,
|
||||
"epubcfi": "epubcfi(/6/4/2:15)",
|
||||
"character": 15432,
|
||||
"bookmarks": [
|
||||
{
|
||||
"chapter": 3,
|
||||
"datetime": "2026-01-30T19:55:00Z",
|
||||
"notes": "highlighted text",
|
||||
"pos0": "epubcfi(/6/4/2:15)",
|
||||
"pos1": "epubcfi(/6/4/2:20)",
|
||||
"page": 45,
|
||||
"text": "highlighted text excerpt",
|
||||
"type": "highlight"
|
||||
}
|
||||
],
|
||||
"highlights": [],
|
||||
"notes": []
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Response (202 Accepted)
|
||||
|
||||
```json
|
||||
{
|
||||
"sync_status": "accepted",
|
||||
"books_synced": 1,
|
||||
"conflicts": [
|
||||
{
|
||||
"book_uuid": "book-uuid",
|
||||
"conflict_type": "progress_mismatch",
|
||||
"device_progress": 0.45,
|
||||
"server_progress": 0.42,
|
||||
"resolution": "device_wins"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## KOReader Metadata Fetch
|
||||
|
||||
**Endpoint**: `GET /api/sync/koreader/metadata/{book_uuid}`
|
||||
**Auth**: Device token required
|
||||
|
||||
### Example Request
|
||||
|
||||
```http
|
||||
GET /api/sync/koreader/metadata/book-uuid
|
||||
Authorization: Bearer device-token
|
||||
```
|
||||
|
||||
### Response (200 OK)
|
||||
|
||||
```json
|
||||
{
|
||||
"uuid": "book-uuid",
|
||||
"title": "Book Title",
|
||||
"authors": ["Author Name"],
|
||||
"progress": {
|
||||
"percentage": 0.42,
|
||||
"character": 15432,
|
||||
"epubcfi": "epubcfi(/6/4/2:15)",
|
||||
"chapter": 3,
|
||||
"chapter_progress": 0.234
|
||||
},
|
||||
"annotations": {
|
||||
"highlights": [...],
|
||||
"notes": [...],
|
||||
"bookmarks": [...]
|
||||
},
|
||||
"last_sync": "2026-01-30T20:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
## Try It Out
|
||||
|
||||
<!-- API Explorer will be inserted here in Phase 3 -->
|
||||
@@ -0,0 +1,39 @@
|
||||
# Change Password
|
||||
|
||||
Change the current user's password.
|
||||
|
||||
**Endpoint**: `PUT /api/users/me/password`
|
||||
**Auth**: Required
|
||||
**Content-Type**: `application/json`
|
||||
|
||||
## Request Body
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|--------|------|-----------|-------------|
|
||||
| current_password | string | Yes | Current password |
|
||||
| new_password | string | Yes | New password (min 8 chars) |
|
||||
|
||||
### Example Request
|
||||
|
||||
```json
|
||||
{
|
||||
"current_password": "oldPassword",
|
||||
"new_password": "NewSecureP@ss123!"
|
||||
}
|
||||
```
|
||||
|
||||
## Response (204 No Content)
|
||||
|
||||
Password changed successfully.
|
||||
|
||||
## Error Responses
|
||||
|
||||
| Code | Description |
|
||||
|------|-------------|
|
||||
| 400 | Invalid input or weak password |
|
||||
| 401 | Current password is incorrect |
|
||||
| 401 | Invalid or expired token |
|
||||
|
||||
## Try It Out
|
||||
|
||||
<!-- API Explorer will be inserted here in Phase 3 -->
|
||||
@@ -0,0 +1,45 @@
|
||||
# Get Current User Profile
|
||||
|
||||
Retrieve the current authenticated user's profile.
|
||||
|
||||
**Endpoint**: `GET /api/users/me`
|
||||
**Auth**: Required
|
||||
|
||||
## Request Headers
|
||||
|
||||
| Header | Type | Required | Description |
|
||||
|--------|------|-----------|-------------|
|
||||
| Authorization | string | Yes | Bearer token |
|
||||
|
||||
### Example Request
|
||||
|
||||
```http
|
||||
GET /api/users/me
|
||||
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
|
||||
```
|
||||
|
||||
## Response (200 OK)
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "uuid",
|
||||
"email": "user@example.com",
|
||||
"username": "john",
|
||||
"first_name": "John",
|
||||
"last_name": "Doe",
|
||||
"theme": "tokyo-night",
|
||||
"role": "user",
|
||||
"max_devices": 10,
|
||||
"created_at": "2026-01-31T10:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
## Error Responses
|
||||
|
||||
| Code | Description |
|
||||
|------|-------------|
|
||||
| 401 | Invalid or expired token |
|
||||
|
||||
## Try It Out
|
||||
|
||||
<!-- API Explorer will be inserted here in Phase 3 -->
|
||||
@@ -0,0 +1,50 @@
|
||||
# Update Profile
|
||||
|
||||
Update the current user's profile information.
|
||||
|
||||
**Endpoint**: `PUT /api/users/me/profile`
|
||||
**Auth**: Required
|
||||
**Content-Type**: `application/json`
|
||||
|
||||
## Request Body
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|--------|------|-----------|-------------|
|
||||
| first_name | string | No | User's first name |
|
||||
| last_name | string | No | User's last name |
|
||||
|
||||
### Example Request
|
||||
|
||||
```json
|
||||
{
|
||||
"first_name": "John",
|
||||
"last_name": "Smith"
|
||||
}
|
||||
```
|
||||
|
||||
## Response (200 OK)
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "uuid",
|
||||
"email": "user@example.com",
|
||||
"username": "john",
|
||||
"first_name": "John",
|
||||
"last_name": "Smith",
|
||||
"theme": "tokyo-night",
|
||||
"role": "user",
|
||||
"max_devices": 10,
|
||||
"created_at": "2026-01-31T10:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
## Error Responses
|
||||
|
||||
| Code | Description |
|
||||
|------|-------------|
|
||||
| 400 | Invalid input data |
|
||||
| 401 | Invalid or expired token |
|
||||
|
||||
## Try It Out
|
||||
|
||||
<!-- API Explorer will be inserted here in Phase 3 -->
|
||||
@@ -0,0 +1,44 @@
|
||||
# Update Theme
|
||||
|
||||
Update the current user's theme preference.
|
||||
|
||||
**Endpoint**: `PUT /api/users/me/theme`
|
||||
**Auth**: Required
|
||||
**Content-Type**: `application/json`
|
||||
|
||||
## Request Body
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|--------|------|-----------|-------------|
|
||||
| theme | string | Yes | Theme name (e.g., "tokyo-night", "dracula") |
|
||||
|
||||
### Example Request
|
||||
|
||||
```json
|
||||
{
|
||||
"theme": "dracula"
|
||||
}
|
||||
```
|
||||
|
||||
## Response (200 OK)
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "uuid",
|
||||
"email": "user@example.com",
|
||||
"username": "john",
|
||||
"theme": "dracula",
|
||||
"role": "user"
|
||||
}
|
||||
```
|
||||
|
||||
## Error Responses
|
||||
|
||||
| Code | Description |
|
||||
|------|-------------|
|
||||
| 400 | Invalid theme name |
|
||||
| 401 | Invalid or expired token |
|
||||
|
||||
## Try It Out
|
||||
|
||||
<!-- API Explorer will be inserted here in Phase 3 -->
|
||||
@@ -0,0 +1,109 @@
|
||||
# WebSocket Protocol
|
||||
|
||||
Real-time sync events broadcast to connected clients.
|
||||
|
||||
## Connect to WebSocket
|
||||
|
||||
**Endpoint**: `WS /ws/sync?token=<token>`
|
||||
|
||||
### Connection Parameters
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
|-----------|------|-----------|-------------|
|
||||
| token | string | Yes | JWT authentication token |
|
||||
|
||||
### Example Connection
|
||||
|
||||
```javascript
|
||||
const ws = new WebSocket('wss://bookhoard.com/ws/sync?token=eyJhbG...');
|
||||
```
|
||||
|
||||
## Message Format
|
||||
|
||||
All messages are JSON objects with a `type` field.
|
||||
|
||||
### Client → Server Messages
|
||||
|
||||
#### Ping (Heartbeat)
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "ping"
|
||||
}
|
||||
```
|
||||
|
||||
Keep connection alive. Server responds with `pong`.
|
||||
|
||||
### Server → Client Messages
|
||||
|
||||
#### Progress Update
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "progress_update",
|
||||
"timestamp": "2026-01-31T10:00:00Z",
|
||||
"data": {
|
||||
"book_id": "uuid",
|
||||
"progress": {
|
||||
"percentage": 0.45678,
|
||||
"epubcfi": "epubcfi(/6/4/2:15)",
|
||||
"chapter": 3
|
||||
},
|
||||
"annotations": {}
|
||||
},
|
||||
"source_device": {
|
||||
"id": "device-uuid",
|
||||
"name": "My Kobo",
|
||||
"type": "kobo"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Broadcast when any device updates reading progress.
|
||||
|
||||
#### Conflict Detected
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "conflict",
|
||||
"timestamp": "2026-01-31T10:00:00Z",
|
||||
"data": {
|
||||
"book_id": "uuid",
|
||||
"conflict_id": "uuid",
|
||||
"conflict_type": "progress"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Broadcast when a sync conflict is detected.
|
||||
|
||||
#### Pong
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "pong"
|
||||
}
|
||||
```
|
||||
|
||||
Server response to client `ping`.
|
||||
|
||||
## Connection Management
|
||||
|
||||
- **Heartbeat**: Send `ping` every 30 seconds
|
||||
- **Reconnect**: Use exponential backoff if connection drops
|
||||
- **Authentication**: Token must be valid for connection
|
||||
- **Rate Limits**: 60 messages/minute per connection
|
||||
|
||||
## Error Handling
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "error",
|
||||
"message": "Invalid token",
|
||||
"code": "AUTH_FAILED"
|
||||
}
|
||||
```
|
||||
|
||||
## Try It Out
|
||||
|
||||
<!-- API Explorer will be inserted here in Phase 3 -->
|
||||
Reference in New Issue
Block a user