sync_bookmarks.md documented a request shape the handler never accepted. - Document the real body: book_uuid/book_sha256 (either required, SHA-256 is format-aware), plus separate bookmarks/notes/highlights arrays using the shared KOReader annotation shape (pos0/pos1, page, text, type, per-annotation book_sha256, dedup_key, percentage) - Document color semantics from 178fb2e/dafcadd: KOReader palette names map to web hex swatches at the boundary, echoes carry no color so stored web colors survive round-trips, explicit colors are device edits - koreader-protocol.md: cross-link the bookmark shape/color/dedup rules from the progress-sync field table
4.4 KiB
4.4 KiB
Sync Bookmarks
Sync bookmarks, notes, and highlights from a KOReader device (bidirectional — the response also returns the server's current state for the book so the device can reconcile).
Endpoint: POST /api/sync/koreader/bookmarks
Auth: Device token (Bearer)
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
| book_uuid | string | one of uuid/sha | Book UUID (highest-confidence match) |
| book_sha256 | string | one of uuid/sha | Full-file SHA-256 (64 hex chars); format-aware (also matches media_item_formats, so a KEPUB/PDF download matches) |
| bookmarks | array | No | Bookmark objects |
| notes | array | No | Note objects |
| highlights | array | No | Highlight objects |
At least one of book_uuid or book_sha256 is required; book_sha256 resolves through the shared BookResolver.
Bookmark / Note / Highlight Object
All three types share the same KOReader annotation shape:
| Field | Type | Required | Description |
|---|---|---|---|
| chapter | int | No | Chapter index |
| datetime | string | No | ISO 8601 creation/edit timestamp |
| pos0 / pos1 | string | No | Start/end xpointer (or page:N / bare page) |
| page | int | No | Page number (fallback location when pos0 is empty) |
| text | string | No | Highlighted text |
| notes | string | No | Note text attached to the annotation |
| type | string | No | Annotation type (highlight, note, bookmark) |
| color | string | No | Highlight color (highlights only) — KOReader palette name, see below |
| percentage | float | No | Position within the book (0-1) |
| book_sha256 | string | No | Per-annotation SHA-256; overrides the request-level book match |
| dedup_key | string | No | Stable echo key; an entry whose content is unchanged from what the server previously served is recognized as an echo rather than a new edit |
Color Semantics
KOReader paints highlights from a fixed palette of color names; the web reader uses hex swatches. Colors are mapped at the boundary (unmappable values fall back to yellow on both sides):
| KOReader name | Web hex |
|---|---|
| yellow, orange | #ffd54f |
| green, olive | #a5d6a7 |
| cyan, blue | #90caf9 |
| purple | #ce93d8 |
| red | #f48fb1 |
- An echo (device re-reporting an annotation it received from the server) carries no color, so the stored web color is never clobbered.
- A non-empty color means the user edited the highlight on the device; it is mapped to the nearest web swatch.
Example Request
{
"book_sha256": "64-hex-char-sha256",
"bookmarks": [
{
"chapter": 3,
"datetime": "2026-08-20T10:00:00Z",
"pos0": "/body/Doc[4]/Sec[2]",
"page": 25,
"text": "",
"type": "bookmark",
"percentage": 0.125
}
],
"highlights": [
{
"datetime": "2026-08-20T10:05:00Z",
"pos0": "/body/Doc[4]/Sec[2]/text()[3]:0",
"pos1": "/body/Doc[4]/Sec[2]/text()[3]:42",
"text": "Text to remember",
"notes": "Why this matters",
"type": "highlight",
"color": "blue",
"dedup_key": "echo-key-from-server"
}
]
}
Response (200 OK)
{
"sync_status": "ok",
"bookmarks_synced": 1,
"notes_synced": 0,
"highlights_synced": 1
}
Error Responses
| Code | Description |
|---|---|
| 400 | Invalid request, or neither uuid nor SHA provided |
| 401 | Missing/invalid device token |
| 404 | Book not found by SHA-256 |