Files
bookhoard/docs/developer/api-reference.md
T
john-okeefe 26f1f98736 docs(kobo): mark native Kobo sync as coming soon
Native Kobo sync is implemented server-side but not yet supported on
real devices, so stop documenting it as a working feature.

- Rewrite kobo-setup.md as a coming-soon stub: point users to KOReader
  (which runs on Kobo hardware) as the supported path today, and list
  what native sync will deliver when released
- Add 'Coming Soon' status banners to the Kobo protocol spec, all five
  Kobo endpoint docs, and both API references, noting the endpoints are
  under active development and may change
- Tag the device shelf endpoints as pending native Kobo support
2026-08-20 14:14:43 -04:00

1708 lines
32 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/libraries/scan-settings
Authorization: Bearer <token>
Content-Type: application/json
{
"scan_poll_interval_seconds": 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
}
]
}
```
### 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)
The feed is paginated via standard OPDS link relations. Clients (e.g. KOReader)
walk pages by following the `rel="next"` link until it is absent. OpenSearch
paging metadata (`totalResults`, `itemsPerPage`, `startIndex`) is also included.
**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/"
xmlns:opensearch="http://a9.com/-/spec/opensearch/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?page=2&per_page=50"/>
<link rel="start" href="http://localhost:8765/opds/devices/kobo-id/catalog?page=1&per_page=50"/>
<link rel="first" href="http://localhost:8765/opds/devices/kobo-id/catalog?page=1&per_page=50"/>
<link rel="previous" href="http://localhost:8765/opds/devices/kobo-id/catalog?page=1&per_page=50"/>
<link rel="next" href="http://localhost:8765/opds/devices/kobo-id/catalog?page=3&per_page=50"/>
<link rel="last" href="http://localhost:8765/opds/devices/kobo-id/catalog?page=37&per_page=50"/>
<link rel="search" type="application/opensearchdescription+xml"
href="http://localhost:8765/opds/devices/kobo-id/search"/>
<opensearch:totalResults>1814</opensearch:totalResults>
<opensearch:itemsPerPage>50</opensearch:itemsPerPage>
<opensearch:startIndex>51</opensearch:startIndex>
<entry>
<id>urn:uuid:bookhoard-uuid-123</id>
<title>The Hobbit</title>
<author><name>J.R.R. Tolkien</name></author>
<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 # OpenSearch description
GET /opds/devices/{deviceId}/search?q={query} # search results feed
```
When called **without** a `q` parameter, returns an OpenSearch description
document (`application/opensearchdescription+xml`). OPDS clients fetch this to
learn the search URL template, then substitute `{searchTerms}`:
```xml
<?xml version="1.0" encoding="UTF-8"?>
<OpenSearchDescription xmlns="http://a9.com/-/spec/opensearch/1.1/">
<ShortName>Bookhoard</ShortName>
<Description>Search the Bookhoard library</Description>
<InputEncoding>UTF-8</InputEncoding>
<OutputEncoding>UTF-8</OutputEncoding>
<Url type="application/atom+xml;profile=opds-catalog;kind=acquisition"
template="http://localhost:8765/opds/devices/kobo-id/search?q={searchTerms}"/>
</OpenSearchDescription>
```
When called **with** a `q` parameter, **Response** (200 - OPDS 1.2 XML with
search results, including `opensearch:totalResults`).
### 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
> **Status: Coming Soon** — Native Kobo sync is implemented server-side but not yet supported on real devices. These endpoints are under active development and may change.
### 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