# 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](../koreader/sync_bookmarks.md)) | \* At least one of `uuid` or `sha256` should be present. The server resolves the book through the shared `BookResolver` with this priority: `uuid` → `sha256` → `file_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 ```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 Accepted) ```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 **Endpoint**: `GET /api/sync/koreader/metadata/{book_uuid}` **Auth**: Device token required ### Example Request ```http GET /api/sync/koreader/metadata/book-uuid Authorization: Bearer device-token ``` ### Response (200 OK) ```json { "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`](../../../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.