Files
bookhoard/docs/developer/api/sync/koreader-protocol.md
T
john-okeefe 91c8be8562 docs(api): document the KOReader resolve endpoint
Add koreader/resolve_book.md for GET /api/sync/koreader/resolve, list
the endpoint in the API reference, and describe the resolve-then-pull-
then-push linking flow in the KOReader protocol page — including why a
device pushing to bootstrap its identity creates progress conflicts for
books already mid-read from other sources.
2026-08-22 09:57:44 -04:00

6.2 KiB

KOReader Sync Protocol

KOReader uses a custom JSON-based sync protocol.

KOReader Progress Sync

Endpoint: POST /api/sync/koreader/progress Auth: Device token required Content-Type: application/json

Request Headers

Header Type Required Description
Authorization string Yes Bearer device token
Content-Type string Yes application/json

Request Body

Field Type Required Description
library_id string No Library UUID
books array Yes Array of book sync data
books[].uuid string No* Book UUID (highest-confidence match; omitted on first sync of a newly downloaded book)
books[].sha256 string No* Full-file SHA-256 (64 hex chars); used to resolve the book when uuid is absent
books[].file_path string No Device-local file path; used to create/look up a device file alias
books[].title string Yes Book title
books[].authors array Yes Array of author names
books[].progress float Yes Progress percentage (0-1)
books[].percentage float Yes Progress percentage (0-1)
books[].last_read string Yes ISO 8601 timestamp
books[].chapter integer No Current chapter
books[].epubcfi string No EPUB CFI location
books[].character integer No Character offset
books[].bookmarks array No Array of bookmarks/highlights (shape, color mapping, and echo/dedup rules: see Sync Bookmarks)

* At least one of uuid or sha256 should be present. The server resolves the book through the shared BookResolver with this priority: uuidsha256file_path alias → title/author. SHA-256 matching is format-aware: it checks media_items.file_sha256 first, then media_item_formats.file_sha256, so a converted file (e.g. KEPUB or PDF) downloaded via OPDS matches even though its hash differs from the primary format's hash.

Example Request

{
  "library_id": "optional-uuid",
  "books": [
    {
      "uuid": "book-uuid",
      "title": "Book Title",
      "authors": ["Author Name"],
      "progress": 0.45,
      "percentage": 0.45,
      "last_read": "2026-01-30T20:00:00Z",
      "chapter": 3,
      "epubcfi": "epubcfi(/6/4/2:15)",
      "character": 15432,
      "bookmarks": [
        {
          "chapter": 3,
          "datetime": "2026-01-30T19:55:00Z",
          "notes": "highlighted text",
          "pos0": "epubcfi(/6/4/2:15)",
          "pos1": "epubcfi(/6/4/2:20)",
          "page": 45,
          "text": "highlighted text excerpt",
          "type": "highlight"
        }
      ],
      "highlights": [],
      "notes": []
    }
  ]
}

Response (202 Accepted)

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

Book Resolution (UUID lookup)

Endpoint: GET /api/sync/koreader/resolve?sha256={hash} Auth: Device token required

Read-only lookup mapping a file SHA-256 to the book's UUID (format-aware, same BookResolver path as the progress push). Devices call this on the first open of a newly downloaded book to learn the UUID before their first pull. Full details: Resolve Book.

This matters for conflict avoidance: a device that pushes to bootstrap its identity transmits its current (first-page) position, which the server treats as a real progress update — overwriting/conflicting with genuine mid-read progress from other sources. Resolve, then pull, then push.

Example Request

GET /api/sync/koreader/resolve?sha256=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
Authorization: Bearer device-token

Response (200 OK)

{
  "book_uuid": "book-uuid",
  "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
  "title": "Book Title",
  "author": "Author Name"
}

404 when no book in the library matches the hash.

KOReader Metadata Fetch

Endpoint: GET /api/sync/koreader/metadata/{book_uuid} Auth: Device token required

Example Request

GET /api/sync/koreader/metadata/book-uuid
Authorization: Bearer device-token

Response (200 OK)

{
  "uuid": "book-uuid",
  "sha256": "ff3e4501bf9d72dea2ae28731a6cb5b83d7a7532c05b5d2dd083d0dbc9193ebf",
  "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"
}

sha256 is the canonical primary-format hash of the book on the server. It is returned so clients can cache it regardless of how the book was originally obtained. The library list endpoint (GET /api/sync/koreader/library) includes the same sha256 field on each book.

Book identification

Every client/sync interface (KOReader, Kobo, OPDS, the device-link UI, and any future mobile app) resolves books through a single shared service: internal/services/book_resolver.go. The import-time SHA-256 (stored on media_items.file_sha256, plus a per-format hash on media_item_formats.file_sha256 for KEPUB/PDF) is the canonical shared identifier. New clients should resolve by SHA-256 via BookResolver rather than re-implementing their own matcher.