Update all references from "Bruno DSL .bru files" to "Bruno OpenCollection YAML .yml files" to reflect the current Bruno format. This includes: - PROJECT_GUIDELINES.md: Update API testing requirements - README.md: Update command examples - TEST_DATA.md: Update test data references - docs/contributing/development.md: Update API testing section - docs/developer/api-reference.md: Update Bruno testing documentation - docs/developer/collections-api.md: Update test file references - scripts/README.md: Update validation script documentation - scripts/verify-guidelines.sh: Update file extension check (.bru → .yml) - bruno/opencollection.yml: Rename collection from "Untitled Collection" to "Bookhoard"
1641 lines
30 KiB
Markdown
1641 lines
30 KiB
Markdown
# Bookhoard API Reference
|
|
|
|
> **⚠️ Legacy Document**: This is a monolithic API reference (1,600+ lines).
|
|
> For updated, split endpoint documentation with interactive API explorer, see [API Documentation Portal](api/api-reference.md).
|
|
>
|
|
> **Use the split docs for:**
|
|
> - Easier navigation by category
|
|
> - Interactive API explorer
|
|
> - Endpoint-specific examples
|
|
> - Latest updates
|
|
|
|
---
|
|
|
|
Complete API documentation for Bookhoard v1.0 with Universal Cross-Platform Sync support.
|
|
|
|
## Table of Contents
|
|
|
|
1. [Authentication](#authentication)
|
|
2. [Users & Profiles](#users--profiles)
|
|
3. [Libraries](#libraries)
|
|
4. [Media Items](#media-items)
|
|
5. [Reading Progress](#reading-progress)
|
|
6. [Notes & Highlights](#notes--highlights)
|
|
7. [Ratings](#ratings)
|
|
8. [Device Management](#device-management)
|
|
9. [Analytics](#analytics)
|
|
10. [Book Matching & Linking](#book-matching--linking)
|
|
11. [Collections](#collections) → See [COLLECTIONS_API.md](COLLECTIONS_API.md)
|
|
12. [OPDS](#opds-open-publication-distribution-system)
|
|
13. [Sync Protocol - KOReader](#sync-protocol---koreader)
|
|
14. [Sync Protocol - Kobo](#sync-protocol---kobo)
|
|
15. [Universal Progress](#universal-progress)
|
|
16. [Conflicts](#conflicts)
|
|
17. [Sync Queue](#sync-queue)
|
|
18. [WebSocket](#websocket)
|
|
|
|
## Base URL
|
|
|
|
```
|
|
Production: https://your-domain.com/api
|
|
Development: http://localhost:8765/api
|
|
```
|
|
|
|
## Authentication
|
|
|
|
Most endpoints require authentication. Include your JWT token in the Authorization header:
|
|
|
|
```
|
|
Authorization: Bearer <your-jwt-token>
|
|
```
|
|
|
|
### Register User
|
|
|
|
```http
|
|
POST /api/auth/register
|
|
Content-Type: application/json
|
|
|
|
{
|
|
"email": "user@example.com",
|
|
"username": "john",
|
|
"password": "SecureP@ss123!",
|
|
"first_name": "John",
|
|
"last_name": "Doe"
|
|
}
|
|
```
|
|
|
|
**Response** (201):
|
|
```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"
|
|
}
|
|
}
|
|
```
|
|
|
|
### Login
|
|
|
|
```http
|
|
POST /api/auth/login
|
|
Content-Type: application/json
|
|
|
|
{
|
|
"email": "user@example.com",
|
|
"password": "SecureP@ss123!"
|
|
}
|
|
```
|
|
|
|
**Response** (200):
|
|
```json
|
|
{
|
|
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
|
|
"refresh_token": "d4f5g6h7...",
|
|
"user": {
|
|
"id": "uuid-here",
|
|
"email": "user@example.com",
|
|
"username": "john",
|
|
"role": "user"
|
|
}
|
|
}
|
|
```
|
|
|
|
### Refresh Token
|
|
|
|
```http
|
|
POST /api/auth/refresh
|
|
Content-Type: application/json
|
|
|
|
{
|
|
"refresh_token": "d4f5g6h7..."
|
|
}
|
|
```
|
|
|
|
**Response** (200):
|
|
```json
|
|
{
|
|
"token": "new-jwt-token",
|
|
"refresh_token": "new-refresh-token"
|
|
}
|
|
```
|
|
|
|
### Logout
|
|
|
|
```http
|
|
POST /api/auth/logout
|
|
Authorization: Bearer <token>
|
|
```
|
|
|
|
**Response** (204): No Content
|
|
|
|
## Users & Profiles
|
|
|
|
### Get Current User
|
|
|
|
```http
|
|
GET /api/users/me
|
|
Authorization: Bearer <token>
|
|
```
|
|
|
|
**Response** (200):
|
|
```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"
|
|
}
|
|
```
|
|
|
|
### Update Profile
|
|
|
|
```http
|
|
PUT /api/users/me/profile
|
|
Authorization: Bearer <token>
|
|
Content-Type: application/json
|
|
|
|
{
|
|
"first_name": "John",
|
|
"last_name": "Smith"
|
|
}
|
|
```
|
|
|
|
### Update Theme
|
|
|
|
```http
|
|
PUT /api/users/me/theme
|
|
Authorization: Bearer <token>
|
|
Content-Type: application/json
|
|
|
|
{
|
|
"theme": "dracula"
|
|
}
|
|
```
|
|
|
|
### Change Password
|
|
|
|
```http
|
|
PUT /api/users/me/password
|
|
Authorization: Bearer <token>
|
|
Content-Type: application/json
|
|
|
|
{
|
|
"current_password": "oldPassword",
|
|
"new_password": "NewSecureP@ss123!"
|
|
}
|
|
```
|
|
|
|
### Update Scan Settings
|
|
|
|
```http
|
|
PUT /api/library/scan-settings
|
|
Authorization: Bearer <token>
|
|
Content-Type: application/json
|
|
|
|
{
|
|
"scan_frequency_minutes": 60,
|
|
"auto_scan_enabled": true
|
|
}
|
|
```
|
|
|
|
## Libraries
|
|
|
|
### Get Visible Libraries
|
|
|
|
```http
|
|
GET /api/libraries/visible
|
|
Authorization: Bearer <token>
|
|
```
|
|
|
|
**Response** (200):
|
|
```json
|
|
{
|
|
"libraries": [
|
|
{
|
|
"id": "uuid",
|
|
"name": "My Ebooks",
|
|
"description": "Ebook collection",
|
|
"type_name": "ebooks",
|
|
"is_visible": true
|
|
}
|
|
]
|
|
}
|
|
```
|
|
|
|
### Get Library Details
|
|
|
|
```http
|
|
GET /api/libraries/{library_id}
|
|
Authorization: Bearer <token>
|
|
```
|
|
|
|
### Create Library (Admin Only)
|
|
|
|
```http
|
|
POST /api/libraries
|
|
Authorization: Bearer <token>
|
|
Content-Type: application/json
|
|
|
|
{
|
|
"name": "Comics Collection",
|
|
"description": "Digital comics",
|
|
"type": "comics"
|
|
}
|
|
```
|
|
|
|
### Add Library Folder (Admin Only)
|
|
|
|
```http
|
|
POST /api/libraries/{library_id}/folders
|
|
Authorization: Bearer <token>
|
|
Content-Type: application/json
|
|
|
|
{
|
|
"folder_path": "/path/to/comics"
|
|
}
|
|
```
|
|
|
|
### Set Library Visibility (Admin Only)
|
|
|
|
```http
|
|
POST /api/libraries/visibility
|
|
Authorization: Bearer <token>
|
|
Content-Type: application/json
|
|
|
|
{
|
|
"user_id": "user-uuid",
|
|
"library_id": "library-uuid",
|
|
"is_visible": true
|
|
}
|
|
```
|
|
|
|
## Media Items
|
|
|
|
### List Media Items
|
|
|
|
```http
|
|
GET /api/media-items?library_id={library_id}&limit=20&offset=0
|
|
Authorization: Bearer <token>
|
|
```
|
|
|
|
**Query Parameters**:
|
|
- `library_id` (required): UUID of library
|
|
- `limit`: Number of items to return (max 100, default 20)
|
|
- `offset`: Number of items to skip
|
|
|
|
**Response** (200):
|
|
```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
|
|
}
|
|
```
|
|
|
|
### Get Media Item
|
|
|
|
```http
|
|
GET /api/media-items/{media_id}
|
|
Authorization: Bearer <token>
|
|
```
|
|
|
|
### Search Media Items
|
|
|
|
```http
|
|
GET /api/media-items/search?q={query}&limit=20&offset=0
|
|
Authorization: Bearer <token>
|
|
```
|
|
|
|
**Query Parameters**:
|
|
- `q` (required): Search query (minimum 2 characters)
|
|
- `limit`: Number of results (default 20)
|
|
- `offset`: Number to skip
|
|
|
|
**Response** (200):
|
|
```json
|
|
{
|
|
"results": [
|
|
{
|
|
"id": "uuid",
|
|
"title": "Book Title",
|
|
"author": "Author Name",
|
|
"match_score": 0.95
|
|
}
|
|
]
|
|
}
|
|
```
|
|
|
|
### Filter & Sort Media Items
|
|
|
|
```http
|
|
GET /api/media-items/filter
|
|
Authorization: Bearer <token>
|
|
Content-Type: application/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
|
|
}
|
|
```
|
|
|
|
### Update Media Item (Admin Only)
|
|
|
|
```http
|
|
PUT /api/media-items/{media_id}
|
|
Authorization: Bearer <token>
|
|
Content-Type: application/json
|
|
|
|
{
|
|
"title": "Updated Title",
|
|
"author": "Updated Author",
|
|
"description": "Updated description",
|
|
"series": "Series",
|
|
"series_number": 2
|
|
}
|
|
```
|
|
|
|
### Delete Media Item (Admin Only)
|
|
|
|
```http
|
|
DELETE /api/media-items/{media_id}
|
|
Authorization: Bearer <token>
|
|
```
|
|
|
|
## Reading Progress
|
|
|
|
### Get Reading Progress
|
|
|
|
```http
|
|
GET /api/media-items/{media_id}/progress
|
|
Authorization: Bearer <token>
|
|
```
|
|
|
|
**Response** (200):
|
|
```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
|
|
}
|
|
```
|
|
|
|
### Update Reading Progress
|
|
|
|
```http
|
|
PUT /api/media-items/{media_id}/progress
|
|
Authorization: Bearer <token>
|
|
Content-Type: application/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):
|
|
```json
|
|
{
|
|
"sync_status": "success",
|
|
"progress_updated": true,
|
|
"devices_notified": ["device-1", "device-2"],
|
|
"broadcast": true
|
|
}
|
|
```
|
|
|
|
### Delete Reading Progress
|
|
|
|
```http
|
|
DELETE /api/media-items/{media_id}/progress
|
|
Authorization: Bearer <token>
|
|
```
|
|
|
|
## Notes & Highlights
|
|
|
|
### Get Notes
|
|
|
|
```http
|
|
GET /api/media-items/{media_id}/notes
|
|
Authorization: Bearer <token>
|
|
```
|
|
|
|
**Response** (200):
|
|
```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"
|
|
}
|
|
]
|
|
}
|
|
```
|
|
|
|
### Create Note
|
|
|
|
```http
|
|
POST /api/media-items/{media_id}/notes
|
|
Authorization: Bearer <token>
|
|
Content-Type: application/json
|
|
|
|
{
|
|
"content": "This is a note",
|
|
"position": "epubcfi(/6/4/2:15)",
|
|
"percentage_location": 0.45,
|
|
"epubcfi_location": "epubcfi(/6/4/2:15)"
|
|
}
|
|
```
|
|
|
|
### Update Note
|
|
|
|
```http
|
|
PUT /api/media-items/notes/{note_id}
|
|
Authorization: Bearer <token>
|
|
Content-Type: application/json
|
|
|
|
{
|
|
"content": "Updated note content",
|
|
"position": "epubcfi(/6/4/2:20)"
|
|
}
|
|
```
|
|
|
|
### Delete Note
|
|
|
|
```http
|
|
DELETE /api/media-items/notes/{note_id}
|
|
Authorization: Bearer <token>
|
|
```
|
|
|
|
### Get Highlights
|
|
|
|
```http
|
|
GET /api/media-items/{media_id}/highlights
|
|
Authorization: Bearer <token>
|
|
```
|
|
|
|
**Response** (200):
|
|
```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"
|
|
}
|
|
]
|
|
}
|
|
```
|
|
|
|
### Create Highlight
|
|
|
|
```http
|
|
POST /api/media-items/{media_id}/highlights
|
|
Authorization: Bearer <token>
|
|
Content-Type: application/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
|
|
}
|
|
```
|
|
|
|
### Update Highlight
|
|
|
|
```http
|
|
PUT /api/media-items/highlights/{highlight_id}
|
|
Authorization: Bearer <token>
|
|
Content-Type: application/json
|
|
|
|
{
|
|
"selection_text": "Updated text",
|
|
"color": "#00ff00"
|
|
}
|
|
```
|
|
|
|
### Delete Highlight
|
|
|
|
```http
|
|
DELETE /api/media-items/highlights/{highlight_id}
|
|
Authorization: Bearer <token>
|
|
```
|
|
|
|
## Ratings
|
|
|
|
### Get Rating
|
|
|
|
```http
|
|
GET /api/media-items/{media_id}/rating
|
|
Authorization: Bearer <token>
|
|
```
|
|
|
|
**Response** (200):
|
|
```json
|
|
{
|
|
"rating": 8,
|
|
"user_id": "uuid",
|
|
"media_item_id": "uuid"
|
|
}
|
|
```
|
|
|
|
### Set Rating
|
|
|
|
```http
|
|
POST /api/media-items/{media_id}/rating
|
|
Authorization: Bearer <token>
|
|
Content-Type: application/json
|
|
|
|
{
|
|
"rating": 8
|
|
}
|
|
```
|
|
|
|
**Rating Scale**: 1-10 (odd numbers = half-stars: 1=0.5★, 2=1★, 3=1.5★, ..., 9=4.5★, 10=5★)
|
|
|
|
### Update Rating
|
|
|
|
```http
|
|
PUT /api/media-items/{media_id}/rating
|
|
Authorization: Bearer <token>
|
|
Content-Type: application/json
|
|
|
|
{
|
|
"rating": 9
|
|
}
|
|
```
|
|
|
|
### Delete Rating
|
|
|
|
```http
|
|
DELETE /api/media-items/{media_id}/rating
|
|
Authorization: Bearer <token>
|
|
```
|
|
|
|
## Device Management
|
|
|
|
### Register Device
|
|
|
|
```http
|
|
POST /api/devices/register
|
|
Content-Type: application/json
|
|
|
|
{
|
|
"device_name": "My Kobo Clara",
|
|
"device_type": "kobo|koreader|web|mobile",
|
|
"device_identifier": "hardware-specific-id"
|
|
}
|
|
```
|
|
|
|
**Response** (201):
|
|
```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
|
|
}
|
|
```
|
|
|
|
### Check Registration Status
|
|
|
|
```http
|
|
POST /api/devices/auth/status
|
|
Content-Type: application/json
|
|
|
|
{
|
|
"registration_id": "registration-uuid"
|
|
}
|
|
```
|
|
|
|
**Response** (200):
|
|
```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"
|
|
}
|
|
}
|
|
```
|
|
|
|
### List User Devices
|
|
|
|
```http
|
|
GET /api/devices
|
|
Authorization: Bearer <token>
|
|
```
|
|
|
|
**Response** (200):
|
|
```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
|
|
}
|
|
]
|
|
}
|
|
```
|
|
|
|
### Update Device Settings
|
|
|
|
```http
|
|
PUT /api/devices/{device_id}
|
|
Authorization: Bearer <token>
|
|
Content-Type: application/json
|
|
|
|
{
|
|
"device_name": "Updated Name",
|
|
"sync_enabled": true,
|
|
"auto_sync": true,
|
|
"sync_frequency_minutes": 5
|
|
}
|
|
```
|
|
|
|
### Revoke Device
|
|
|
|
```http
|
|
DELETE /api/devices/{device_id}
|
|
Authorization: Bearer <token>
|
|
```
|
|
|
|
## Analytics
|
|
|
|
### Get Reading Statistics
|
|
|
|
```http
|
|
GET /api/analytics/reading-stats?start_date={date}&end_date={date}
|
|
Authorization: Bearer <token>
|
|
```
|
|
|
|
**Query Parameters**:
|
|
- `start_date` (optional): Start date (ISO 8601 format)
|
|
- `end_date` (optional): End date (ISO 8601 format)
|
|
|
|
**Response** (200):
|
|
```json
|
|
{
|
|
"pages_read": 1250,
|
|
"books_completed": 5,
|
|
"reading_time_hours": 42.5,
|
|
"sessions_count": 28,
|
|
"average_session_minutes": 91
|
|
}
|
|
```
|
|
|
|
### Get Device Usage Statistics
|
|
|
|
```http
|
|
GET /api/analytics/device-usage
|
|
Authorization: Bearer <token>
|
|
```
|
|
|
|
**Response** (200):
|
|
```json
|
|
{
|
|
"devices": [
|
|
{
|
|
"device_id": "uuid",
|
|
"device_name": "My Kobo Clara",
|
|
"device_type": "kobo",
|
|
"sync_count": 145,
|
|
"last_sync": "2026-02-01T10:00:00Z",
|
|
"pages_synced": 890
|
|
}
|
|
]
|
|
}
|
|
```
|
|
|
|
### Get Popular Books
|
|
|
|
```http
|
|
GET /api/analytics/popular-books?limit={limit}
|
|
Authorization: Bearer <token>
|
|
```
|
|
|
|
**Query Parameters**:
|
|
- `limit` (optional): Number of results (default: 10)
|
|
|
|
**Response** (200):
|
|
```json
|
|
{
|
|
"books": [
|
|
{
|
|
"media_item_id": "uuid",
|
|
"title": "Book Title",
|
|
"author": "Author Name",
|
|
"cover_image_path": "/covers/book.jpg",
|
|
"read_count": 15,
|
|
"average_rating": 8.5
|
|
}
|
|
]
|
|
}
|
|
```
|
|
|
|
## Book Matching & Linking
|
|
|
|
### Query Books for Matching
|
|
|
|
```http
|
|
POST /api/sync/books/query
|
|
Authorization: Bearer <token>
|
|
Content-Type: application/json
|
|
|
|
{
|
|
"identifiers": ["isbn:978-0345391802", "uuid:abc-123"],
|
|
"sha256": "a1b2c3d4e5f6abc123...",
|
|
"title": "The Hobbit",
|
|
"author": "J.R.R. Tolkien",
|
|
"file_size": 2456789
|
|
}
|
|
```
|
|
|
|
**Response** (200):
|
|
```json
|
|
{
|
|
"matches": [
|
|
{
|
|
"media_item_id": "uuid-123",
|
|
"bookhoard_uuid": "uuid-123",
|
|
"confidence": 1.0,
|
|
"match_method": "uuid_match"
|
|
}
|
|
],
|
|
"action": "auto_link"
|
|
}
|
|
```
|
|
|
|
### Bulk Link Books
|
|
|
|
```http
|
|
POST /api/sync/bulk-link-books
|
|
Authorization: Bearer <token>
|
|
Content-Type: application/json
|
|
|
|
{
|
|
"links": [
|
|
{
|
|
"unlinked_book_id": "uuid-1",
|
|
"media_item_id": "uuid-2",
|
|
"confidence_score": 1.0
|
|
}
|
|
]
|
|
}
|
|
```
|
|
|
|
**Response** (200):
|
|
```json
|
|
{
|
|
"results": [
|
|
{
|
|
"unlinked_book_id": "uuid-1",
|
|
"status": "success",
|
|
"media_item_id": "uuid-2"
|
|
}
|
|
],
|
|
"total": 1,
|
|
"successful": 1,
|
|
"failed": 0
|
|
}
|
|
```
|
|
|
|
### Auto-Link Books
|
|
|
|
```http
|
|
POST /api/sync/auto-link-books
|
|
Authorization: Bearer <token>
|
|
Content-Type: application/json
|
|
|
|
{
|
|
"confidence_threshold": 0.8,
|
|
"limit": 50
|
|
}
|
|
```
|
|
|
|
**Response** (200):
|
|
```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"
|
|
}
|
|
]
|
|
}
|
|
```
|
|
|
|
### Get Unlinked Book Suggestions
|
|
|
|
```http
|
|
GET /api/sync/unlinked-books/{id}/suggestions
|
|
Authorization: Bearer <token>
|
|
```
|
|
|
|
**Response** (200):
|
|
```json
|
|
{
|
|
"unlinked_book_id": "uuid-1",
|
|
"title_from_device": "The Hobbit",
|
|
"sha256": "",
|
|
"suggestions": [
|
|
{
|
|
"media_item_id": "uuid-2",
|
|
"bookhoard_uuid": "uuid-2",
|
|
"confidence": 0.95,
|
|
"match_method": "sha256_match"
|
|
}
|
|
],
|
|
"total_suggestions": 1,
|
|
"action": "auto_link"
|
|
}
|
|
```
|
|
|
|
## Collections
|
|
|
|
For complete collection management documentation, see **[COLLECTIONS_API.md](COLLECTIONS_API.md)**.
|
|
|
|
**Quick Reference**:
|
|
- `GET /api/collections` - List all collections
|
|
- `POST /api/collections` - Create new collection
|
|
- `GET /api/collections/{id}` - Get collection details
|
|
- `PUT /api/collections/{id}` - Update collection
|
|
- `DELETE /api/collections/{id}` - Delete collection
|
|
- `POST /api/collections/test-rules` - Test auto-assign rules
|
|
- `POST /api/collections/bulk-add-books` - Bulk add books to collections
|
|
- `GET /api/collections/{id}/books` - Get books in collection
|
|
|
|
**Features**:
|
|
- Auto-assign rules based on genre, author, series, tags, language, publisher, year
|
|
- Device shelf mappings (Kobo shelves, KOReader categories)
|
|
- Test rules before applying
|
|
- Bulk book operations
|
|
|
|
## OPDS (Open Publication Distribution System)
|
|
|
|
### Get Device Catalog
|
|
|
|
```http
|
|
GET /opds/devices/{deviceId}/catalog?page={page}&per_page={per_page}
|
|
```
|
|
|
|
**Query Parameters**:
|
|
- `page` (optional): Page number (default: 1)
|
|
- `per_page` (optional): Items per page (default: 50, max: 200)
|
|
|
|
**Response** (200 - 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>
|
|
```
|
|
|
|
### Download Book with Format Conversion
|
|
|
|
```http
|
|
GET /opds/devices/{deviceId}/download/{bookId}?format={format}
|
|
```
|
|
|
|
**Query Parameters**:
|
|
- `format` (optional): Book format - `epub` (default), `kepub`
|
|
|
|
**Response** (200):
|
|
- 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)
|
|
|
|
### Search OPDS Catalog
|
|
|
|
```http
|
|
GET /opds/devices/{deviceId}/search?q={query}
|
|
```
|
|
|
|
**Response** (200 - OPDS 1.2 XML with search results)
|
|
|
|
### List Available Formats
|
|
|
|
```http
|
|
GET /opds/devices/{deviceId}/formats/{bookId}
|
|
```
|
|
|
|
**Response** (200):
|
|
```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
|
|
}
|
|
]
|
|
}
|
|
```
|
|
|
|
## Sync Protocol - KOReader
|
|
|
|
### KOReader Progress Sync
|
|
|
|
```http
|
|
POST /api/sync/koreader/progress
|
|
Authorization: Bearer <device_token>
|
|
Content-Type: application/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):
|
|
```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
|
|
|
|
```http
|
|
GET /api/sync/koreader/metadata/{book_uuid}
|
|
Authorization: Bearer <device_token>
|
|
```
|
|
|
|
**Response** (200):
|
|
```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"
|
|
}
|
|
```
|
|
|
|
## Sync Protocol - Kobo
|
|
|
|
### Kobo Markup Sync
|
|
|
|
```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):
|
|
```json
|
|
{
|
|
"Status": "Success",
|
|
"MarkupsSynced": 5,
|
|
"BookmarksSynced": 3
|
|
}
|
|
```
|
|
|
|
### Kobo Library Fetch
|
|
|
|
```http
|
|
GET /api/sync/kobo/library
|
|
Authorization: Bearer <device_token>
|
|
```
|
|
|
|
**Response** (200):
|
|
```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"
|
|
}
|
|
]
|
|
}
|
|
```
|
|
|
|
## Universal Progress
|
|
|
|
### Get Universal Progress
|
|
|
|
```http
|
|
GET /api/progress/{book_uuid}
|
|
Authorization: Bearer <token>
|
|
```
|
|
|
|
**Response** (200):
|
|
```json
|
|
{
|
|
"book_id": "book-uuid",
|
|
"format_group": "reflowable",
|
|
"universal_progress": 0.45678,
|
|
"location_references": {
|
|
"percentage": 0.45678,
|
|
"epubcfi": "epubcfi(/6/4/2:15)",
|
|
"character": 15432,
|
|
"chapter": 3,
|
|
"chapter_progress": 0.234,
|
|
"viewport_y": 0.12
|
|
},
|
|
"device_progress": {
|
|
"koreader": {
|
|
"percentage": 0.45678,
|
|
"last_sync": "2026-01-30T20:00:00Z"
|
|
},
|
|
"kobo": {
|
|
"percentage": 45.6,
|
|
"last_sync": "2026-01-30T19:55:00Z"
|
|
},
|
|
"web": {
|
|
"display_page": 89,
|
|
"total_pages": 200,
|
|
"last_sync": "2026-01-30T20:05:00Z"
|
|
}
|
|
},
|
|
"annotations": {
|
|
"highlights": [...],
|
|
"notes": [...],
|
|
"bookmarks": [...]
|
|
},
|
|
"conflicts": [
|
|
{
|
|
"id": "conflict-uuid",
|
|
"type": "progress",
|
|
"resolved": false,
|
|
"sources": ["koreader", "kobo"]
|
|
}
|
|
]
|
|
}
|
|
```
|
|
|
|
### Update Universal Progress
|
|
|
|
```http
|
|
POST /api/progress/{book_uuid}
|
|
Authorization: Bearer <token>
|
|
Content-Type: application/json
|
|
|
|
{
|
|
"source": "web|koreader|kobo|mobile",
|
|
"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": "..."
|
|
}
|
|
}
|
|
```
|
|
|
|
## Conflicts
|
|
|
|
### List Conflicts
|
|
|
|
```http
|
|
GET /api/conflicts?status=unresolved&type=progress
|
|
Authorization: Bearer <token>
|
|
```
|
|
|
|
**Query Parameters**:
|
|
- `status`: "unresolved|all" (default: "unresolved")
|
|
- `type`: "progress|note|highlight|all" (default: "all")
|
|
|
|
**Response** (200):
|
|
```json
|
|
{
|
|
"conflicts": [
|
|
{
|
|
"id": "conflict-uuid",
|
|
"media_item_id": "book-uuid",
|
|
"media_item_title": "Book Title",
|
|
"conflict_type": "progress",
|
|
"conflict_data": {
|
|
"koreader": {
|
|
"source": "koreader",
|
|
"timestamp": "2026-01-30T20:10:00Z",
|
|
"data": {
|
|
"percentage": 0.45,
|
|
"epubcfi": "epubcfi(/6/4/2:15)",
|
|
"character": 15432
|
|
}
|
|
},
|
|
"kobo": {
|
|
"source": "kobo",
|
|
"timestamp": "2026-01-30T20:05:00Z",
|
|
"data": {
|
|
"percentage": 0.42
|
|
}
|
|
}
|
|
},
|
|
"resolution_status": "unresolved",
|
|
"created_at": "2026-01-30T20:10:05Z"
|
|
}
|
|
],
|
|
"total": 1,
|
|
"unresolved": 1
|
|
}
|
|
```
|
|
|
|
### Get Conflict Details
|
|
|
|
```http
|
|
GET /api/conflicts/{conflict_id}
|
|
Authorization: Bearer <token>
|
|
```
|
|
|
|
### Resolve Conflict
|
|
|
|
```http
|
|
POST /api/conflicts/{conflict_id}/resolve
|
|
Authorization: Bearer <token>
|
|
Content-Type: application/json
|
|
|
|
{
|
|
"winner": "koreader|kobo|web|manual",
|
|
"manual_data": {
|
|
"percentage": 0.43,
|
|
"epubcfi": "epubcfi(/6/4/2:20)",
|
|
"character": 15500
|
|
},
|
|
"apply_to_all_future_conflicts": false,
|
|
"reason": "user chose more recent progress"
|
|
}
|
|
```
|
|
|
|
**Response** (200):
|
|
```json
|
|
{
|
|
"conflict_resolved": true,
|
|
"applied_to": {
|
|
"progress": true,
|
|
"annotations": false
|
|
},
|
|
"devices_synced": ["device-1", "device-2"]
|
|
}
|
|
```
|
|
|
|
### Delete Conflict
|
|
|
|
```http
|
|
DELETE /api/conflicts/{conflict_id}
|
|
Authorization: Bearer <token>
|
|
```
|
|
|
|
### Dismiss All Resolved
|
|
|
|
```http
|
|
DELETE /api/conflicts/dismiss-resolved
|
|
Authorization: Bearer <token>
|
|
```
|
|
|
|
## Sync Queue
|
|
|
|
### List Queue Items
|
|
|
|
```http
|
|
GET /api/queue/items?limit=50&offset=0
|
|
Authorization: Bearer <token>
|
|
```
|
|
|
|
**Response** (200):
|
|
```json
|
|
{
|
|
"items": [
|
|
{
|
|
"id": "uuid",
|
|
"device_id": "device-uuid",
|
|
"device_name": "My Kobo",
|
|
"media_item_id": "book-uuid",
|
|
"sync_type": "progress",
|
|
"sync_data": {},
|
|
"priority": 5,
|
|
"attempts": 0,
|
|
"max_attempts": 3,
|
|
"status": "pending",
|
|
"error_message": null,
|
|
"created_at": "2026-01-31T10:00:00Z"
|
|
}
|
|
],
|
|
"total": 100
|
|
}
|
|
```
|
|
|
|
### Process Queue Item
|
|
|
|
```http
|
|
POST /api/queue/items/{queue_item_id}/process
|
|
Authorization: Bearer <token>
|
|
```
|
|
|
|
### Retry Queue Item
|
|
|
|
```http
|
|
POST /api/queue/items/{queue_item_id}/retry
|
|
Authorization: Bearer <token>
|
|
```
|
|
|
|
### Delete Queue Item
|
|
|
|
```http
|
|
DELETE /api/queue/items/{queue_item_id}
|
|
Authorization: Bearer <token>
|
|
```
|
|
|
|
### Clear Queue
|
|
|
|
```http
|
|
DELETE /api/queue/clear
|
|
Authorization: Bearer <token>
|
|
```
|
|
|
|
### Clear Failed Items
|
|
|
|
```http
|
|
DELETE /api/queue/clear-failed
|
|
Authorization: Bearer <token>
|
|
```
|
|
|
|
### Get Queue Stats
|
|
|
|
```http
|
|
GET /api/queue/stats
|
|
Authorization: Bearer <token>
|
|
```
|
|
|
|
**Response** (200):
|
|
```json
|
|
{
|
|
"pending": 15,
|
|
"processing": 2,
|
|
"failed": 3,
|
|
"completed": 100,
|
|
"total": 120
|
|
}
|
|
```
|
|
|
|
## WebSocket
|
|
|
|
### Connect to WebSocket
|
|
|
|
```
|
|
WS /ws/sync?token=<token>
|
|
```
|
|
|
|
### Message Format
|
|
|
|
**Client → Server (Heartbeat)**:
|
|
```json
|
|
{
|
|
"type": "ping"
|
|
}
|
|
```
|
|
|
|
**Server → Client (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"
|
|
}
|
|
}
|
|
```
|
|
|
|
**Server → Client (Conflict Detected)**:
|
|
```json
|
|
{
|
|
"type": "conflict",
|
|
"timestamp": "2026-01-31T10:00:00Z",
|
|
"data": {
|
|
"book_id": "uuid",
|
|
"conflict_id": "uuid",
|
|
"conflict_type": "progress"
|
|
}
|
|
}
|
|
```
|
|
|
|
**Server → Client (Pong)**:
|
|
```json
|
|
{
|
|
"type": "pong"
|
|
}
|
|
```
|
|
|
|
## Error Responses
|
|
|
|
All endpoints return standardized error responses:
|
|
|
|
```json
|
|
{
|
|
"error": "Error message",
|
|
"message": "Detailed error information (if available)",
|
|
"code": "ERROR_CODE"
|
|
}
|
|
```
|
|
|
|
### HTTP Status Codes
|
|
|
|
- **200**: OK - Request successful
|
|
- **201**: Created - Resource created successfully
|
|
- **204**: No Content - Successful deletion or update with no content
|
|
- **400**: Bad Request - Invalid request parameters
|
|
- **401**: Unauthorized - Missing or invalid authentication
|
|
- **403**: Forbidden - Insufficient permissions
|
|
- **404**: Not Found - Resource does not exist
|
|
- **409**: Conflict - Resource conflict (e.g., duplicate)
|
|
- **422**: Unprocessable Entity - Validation error
|
|
- **429**: Too Many Requests - Rate limit exceeded
|
|
- **500**: Internal Server Error - Server error
|
|
|
|
### Rate Limiting
|
|
|
|
**Per-Device Limits**:
|
|
- Sync requests: 60/minute
|
|
- Progress updates: 120/minute
|
|
- Metadata requests: 30/minute
|
|
|
|
**Per-User Limits**:
|
|
- All requests: 300/minute
|
|
- Conflict resolutions: 10/minute
|
|
- Device registrations: 5/hour
|
|
|
|
**Rate Limit Headers**:
|
|
```
|
|
X-RateLimit-Limit: 60
|
|
X-RateLimit-Remaining: 45
|
|
X-RateLimit-Reset: 60
|
|
```
|
|
|
|
## Bruno v3.0 Collections
|
|
|
|
Complete API test collections are available in the `bruno/` directory:
|
|
|
|
```
|
|
bruno/
|
|
├── user/ # Authentication & profiles
|
|
├── admin/ # Admin operations
|
|
├── library/ # Library management
|
|
├── media-items/ # Media content
|
|
├── progress/ # Reading progress
|
|
├── notes/ # Notes API
|
|
├── highlights/ # Highlights API
|
|
├── ratings/ # Ratings API
|
|
├── devices/ # Device management
|
|
├── sync-koreader/ # KOReader sync protocol
|
|
├── sync-kobo/ # Kobo sync protocol
|
|
├── conflicts/ # Conflict resolution
|
|
├── queue/ # Sync queue management
|
|
└── collection.yml # Main collection file
|
|
```
|
|
|
|
## Testing with Bruno OpenCollection YAML
|
|
|
|
Install Bruno CLI:
|
|
```bash
|
|
npm install -g @usebruno/cli
|
|
```
|
|
|
|
Run all tests:
|
|
```bash
|
|
bruno run
|
|
```
|
|
|
|
Run specific collection:
|
|
```bash
|
|
bruno run bruno/devices/
|
|
```
|
|
|
|
## Additional Resources
|
|
|
|
- [README.md](README.md) - Getting started guide
|
|
- [UNIVERSAL_SYNC_IMPLEMENTATION_GUIDE.md](UNIVERSAL_SYNC_IMPLEMENTATION_GUIDE.md) - Sync architecture
|
|
- [KOBOREADER_SETUP.md](KOBOREADER_SETUP.md) - KOReader device setup
|
|
- [KOBO_SETUP.md](KOBO_SETUP.md) - Kobo device setup
|
|
|
|
---
|
|
|
|
**Document Version**: 1.0
|
|
**Last Updated**: 2026-01-31
|
|
**API Version**: v1.0
|