# 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

### 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
