Files
bookhoard/docs/developer/api-reference.md
T
john-okeefe 4d321528b2 docs: update comprehensive API documentation and project guides
This commit updates all documentation files throughout the project:

- Updated IMPLEMENTATION_PLAN.md with new implementation details
- Updated PROJECT_GUIDELINES.md with coding standards and practices
- Updated README.md with current project information
- Updated SCREENSHOT_AUTOMATION.md with new automation details
- Added TEST_DATA.md with test fixtures data
- Updated cover_image_serving_plan.md with static URL patterns

Documentation API updates:
- Updated API reference documentation for all endpoints including:
  - Authentication (login, logout, register, refresh_token)
  - Book matching (auto_link, bulk_link, link_book, search)
  - Collections (CRUD operations, shelf mappings, auto-assign rules)
  - Conflicts (bulk operations, resolve/dismiss)
  - Devices (registration, approval, shelf management)
  - Highlights (create, update, delete, get)
  - Kobo sync (bookmark, markup, initialization, sync)
  - KOReader sync (library, metadata, bookmarks, progress)
  - Libraries (CRUD, folders, media items, stats)
  - Media items (bulk operations, CRUD)
  - Notes (CRUD operations)
  - OPDS (acquisition, feeds, publication)
  - Progress (reading progress tracking)
  - Queue (device queue management)
  - Ratings (star ratings)
  - Scanner (watch mode, scan operations)
  - Sync protocols (Kobo, KOReader)
  - Users (profile, password, admin operations)
  - WebSocket protocols

- Updated user guides (admin, dashboard, settings, sync)
- Updated device setup guides (Kobo, KOReader)
- Updated developer guides (testing, contributing, operations)
- Updated scripts/README.md
2026-02-27 17:06:22 -05:00

30 KiB

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.

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
  2. Users & Profiles
  3. Libraries
  4. Media Items
  5. Reading Progress
  6. Notes & Highlights
  7. Ratings
  8. Device Management
  9. Analytics
  10. Book Matching & Linking
  11. Collections → See COLLECTIONS_API.md
  12. OPDS
  13. Sync Protocol - KOReader
  14. Sync Protocol - Kobo
  15. Universal Progress
  16. Conflicts
  17. Sync Queue
  18. 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

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):

{
  "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

POST /api/auth/login
Content-Type: application/json

{
  "email": "user@example.com",
  "password": "SecureP@ss123!"
}

Response (200):

{
  "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "refresh_token": "d4f5g6h7...",
  "user": {
    "id": "uuid-here",
    "email": "user@example.com",
    "username": "john",
    "role": "user"
  }
}

Refresh Token

POST /api/auth/refresh
Content-Type: application/json

{
  "refresh_token": "d4f5g6h7..."
}

Response (200):

{
  "token": "new-jwt-token",
  "refresh_token": "new-refresh-token"
}

Logout

POST /api/auth/logout
Authorization: Bearer <token>

Response (204): No Content

Users & Profiles

Get Current User

GET /api/users/me
Authorization: Bearer <token>

Response (200):

{
  "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

PUT /api/users/me/profile
Authorization: Bearer <token>
Content-Type: application/json

{
  "first_name": "John",
  "last_name": "Smith"
}

Update Theme

PUT /api/users/me/theme
Authorization: Bearer <token>
Content-Type: application/json

{
  "theme": "dracula"
}

Change Password

PUT /api/users/me/password
Authorization: Bearer <token>
Content-Type: application/json

{
  "current_password": "oldPassword",
  "new_password": "NewSecureP@ss123!"
}

Update Scan Settings

PUT /api/libraries/scan-settings
Authorization: Bearer <token>
Content-Type: application/json

{
  "scan_frequency_minutes": 60,
  "auto_scan_enabled": true
}

Libraries

Get Visible Libraries

GET /api/libraries/visible
Authorization: Bearer <token>

Response (200):

{
  "libraries": [
    {
      "id": "uuid",
      "name": "My Ebooks",
      "description": "Ebook collection",
      "type_name": "ebooks",
      "is_visible": true
    }
  ]
}

Get Library Details

GET /api/libraries/{library_id}
Authorization: Bearer <token>

Create Library (Admin Only)

POST /api/libraries
Authorization: Bearer <token>
Content-Type: application/json

{
  "name": "Comics Collection",
  "description": "Digital comics",
  "type": "comics"
}

Add Library Folder (Admin Only)

POST /api/libraries/{library_id}/folders
Authorization: Bearer <token>
Content-Type: application/json

{
  "folder_path": "/path/to/comics"
}

Set Library Visibility (Admin Only)

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

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):

{
  "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

GET /api/media-items/{media_id}
Authorization: Bearer <token>

Search Media Items

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):

{
  "results": [
    {
      "id": "uuid",
      "title": "Book Title",
      "author": "Author Name",
      "match_score": 0.95
    }
  ]
}

Filter & Sort Media Items

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)

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)

DELETE /api/media-items/{media_id}
Authorization: Bearer <token>

Reading Progress

Get Reading Progress

GET /api/media-items/{media_id}/progress
Authorization: Bearer <token>

Response (200):

{
  "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

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):

{
  "sync_status": "success",
  "progress_updated": true,
  "devices_notified": ["device-1", "device-2"],
  "broadcast": true
}

Delete Reading Progress

DELETE /api/media-items/{media_id}/progress
Authorization: Bearer <token>

Notes & Highlights

Get Notes

GET /api/media-items/{media_id}/notes
Authorization: Bearer <token>

Response (200):

{
  "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

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

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

DELETE /api/media-items/notes/{note_id}
Authorization: Bearer <token>

Get Highlights

GET /api/media-items/{media_id}/highlights
Authorization: Bearer <token>

Response (200):

{
  "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

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

PUT /api/media-items/highlights/{highlight_id}
Authorization: Bearer <token>
Content-Type: application/json

{
  "selection_text": "Updated text",
  "color": "#00ff00"
}

Delete Highlight

DELETE /api/media-items/highlights/{highlight_id}
Authorization: Bearer <token>

Ratings

Get Rating

GET /api/media-items/{media_id}/rating
Authorization: Bearer <token>

Response (200):

{
  "rating": 8,
  "user_id": "uuid",
  "media_item_id": "uuid"
}

Set Rating

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

PUT /api/media-items/{media_id}/rating
Authorization: Bearer <token>
Content-Type: application/json

{
  "rating": 9
}

Delete Rating

DELETE /api/media-items/{media_id}/rating
Authorization: Bearer <token>

Device Management

Register Device

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):

{
  "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

POST /api/devices/auth/status
Content-Type: application/json

{
  "registration_id": "registration-uuid"
}

Response (200):

{
  "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

GET /api/devices
Authorization: Bearer <token>

Response (200):

{
  "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

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

DELETE /api/devices/{device_id}
Authorization: Bearer <token>

Analytics

Get Reading Statistics

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):

{
  "pages_read": 1250,
  "books_completed": 5,
  "reading_time_hours": 42.5,
  "sessions_count": 28,
  "average_session_minutes": 91
}

Get Device Usage Statistics

GET /api/analytics/device-usage
Authorization: Bearer <token>

Response (200):

{
  "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 /api/analytics/popular-books?limit={limit}
Authorization: Bearer <token>

Query Parameters:

  • limit (optional): Number of results (default: 10)

Response (200):

{
  "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

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):

{
  "matches": [
    {
      "media_item_id": "uuid-123",
      "bookhoard_uuid": "uuid-123",
      "confidence": 1.0,
      "match_method": "uuid_match"
    }
  ],
  "action": "auto_link"
}
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):

{
  "results": [
    {
      "unlinked_book_id": "uuid-1",
      "status": "success",
      "media_item_id": "uuid-2"
    }
  ],
  "total": 1,
  "successful": 1,
  "failed": 0
}
POST /api/sync/auto-link-books
Authorization: Bearer <token>
Content-Type: application/json

{
  "confidence_threshold": 0.8,
  "limit": 50
}

Response (200):

{
  "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

GET /api/sync/unlinked-books/{id}/suggestions
Authorization: Bearer <token>

Response (200):

{
  "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.

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

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

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

GET /opds/devices/{deviceId}/search?q={query}

Response (200 - OPDS 1.2 XML with search results)

List Available Formats

GET /opds/devices/{deviceId}/formats/{bookId}

Response (200):

{
  "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

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):

{
  "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

GET /api/sync/koreader/metadata/{book_uuid}
Authorization: Bearer <device_token>

Response (200):

{
  "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

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):

{
  "Status": "Success",
  "MarkupsSynced": 5,
  "BookmarksSynced": 3
}

Kobo Library Fetch

GET /api/sync/kobo/library
Authorization: Bearer <device_token>

Response (200):

{
  "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

GET /api/progress/{book_uuid}
Authorization: Bearer <token>

Response (200):

{
  "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

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

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):

{
  "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

GET /api/conflicts/{conflict_id}
Authorization: Bearer <token>

Resolve Conflict

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):

{
  "conflict_resolved": true,
  "applied_to": {
    "progress": true,
    "annotations": false
  },
  "devices_synced": ["device-1", "device-2"]
}

Delete Conflict

DELETE /api/conflicts/{conflict_id}
Authorization: Bearer <token>

Dismiss All Resolved

DELETE /api/conflicts/dismiss-resolved
Authorization: Bearer <token>

Sync Queue

List Queue Items

GET /api/queue/items?limit=50&offset=0
Authorization: Bearer <token>

Response (200):

{
  "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

POST /api/queue/items/{queue_item_id}/process
Authorization: Bearer <token>

Retry Queue Item

POST /api/queue/items/{queue_item_id}/retry
Authorization: Bearer <token>

Delete Queue Item

DELETE /api/queue/items/{queue_item_id}
Authorization: Bearer <token>

Clear Queue

DELETE /api/queue/clear
Authorization: Bearer <token>

Clear Failed Items

DELETE /api/queue/clear-failed
Authorization: Bearer <token>

Get Queue Stats

GET /api/queue/stats
Authorization: Bearer <token>

Response (200):

{
  "pending": 15,
  "processing": 2,
  "failed": 3,
  "completed": 100,
  "total": 120
}

WebSocket

Connect to WebSocket

WS /ws/sync?token=<token>

Message Format

Client → Server (Heartbeat):

{
  "type": "ping"
}

Server → Client (Progress Update):

{
  "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):

{
  "type": "conflict",
  "timestamp": "2026-01-31T10:00:00Z",
  "data": {
    "book_id": "uuid",
    "conflict_id": "uuid",
    "conflict_type": "progress"
  }
}

Server → Client (Pong):

{
  "type": "pong"
}

Error Responses

All endpoints return standardized error responses:

{
  "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:

npm install -g @usebruno/cli

Run all tests:

bruno run

Run specific collection:

bruno run bruno/devices/

Additional Resources


Document Version: 1.0
Last Updated: 2026-01-31
API Version: v1.0