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

\* 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.
