Files
bookhoard/docs/developer/api/koreader/sync_progress.md
T
John O'Keefe 25ef0d12ba docs(kobo,koreader,collections): Beam WO5 batch 6 — 12 endpoints
Kobo emulation + KOReader calibre-compatible surface backfilled with
device-token auth; collections reorder + update_rule created.

Claude registry: 12 entries. kobo/koreader emulation endpoints all bind
arbitrary protocol POST bodies (markup/bookmark/gettests/sync-from-server,
koreader progress/bookmarks) — registered as AnyJSONObject, not bodyless as
first drafted. Initialization returns the library sync payload, not the init
response.

Contract correction in update_rule.md: the route is SetRuleEnabled ({enabled}
required -> {collection_id, is_smart}); Beam's draft invented rule-field
updates. Reordered response/status: {item_ids} -> {status: reordered}.

make test 9/9.
2026-10-24 16:25:00 -04:00

97 lines
3.3 KiB
Markdown

---
method: POST
path: /api/sync/koreader/progress
auth: device-token
area: koreader
---
<!-- source: internal/handlers/koreader.go:418 KOReaderHandler.SyncProgress -->
# Sync Progress
Push reading progress from a KOReader device.
**Endpoint**: `POST /api/sync/koreader/progress`
**Auth**: Required (Device authentication — `Authorization: Bearer {device_token}`)
This is the device-native tier of the [Position
Contract](../progress/position-contract.md): the KOReader payload carries
a CRE xpointer and the server converts it to the canonical standard CFI,
verifies it against the submitted `context_text`, and heals it on
mismatch — exactly like every other client.
## Request Body
| Field | Type | Required | Description |
| --------- | ------ | -------- | ----------- |
| `books` | array | Yes | One book object (the reference client sends a single-element array). |
| `sync_mode`| string | No | `immediate` (default) or `manual`. |
### Book Object
| Field | Type | Required | Description |
| ----- | ---- | -------- | ----------- |
| `uuid` | string (UUID) | No | Bookhoard UUID, once the device has linked the book via [Resolve Book](resolve_book.md). |
| `sha256` | string | Yes | File content hash (64 hex chars) — the primary book identity. |
| `title` | string | No | Document title. |
| `authors` | array | No | Author names. |
| `percentage` | float | Yes | Position as a fraction of the book (0..1). |
| `context_text` | string | No | Up to 100 whitespace-normalized chars from the current position — enables the server's verification/healing. Strongly recommended. |
| `page` | int | No | Current page (fixed-layout books). |
| `total_pages` | int | No | Page count (fixed-layout books). |
| `epubcfi` | string | Reflowable only | **A CRE xpointer** (`/body/DocFragment[N]/body/...`), not a CFI — the field name is historical. Fixed-layout books must omit it and carry their position in `page`/`total_pages`. |
| `file_path` | string | No | Device-local file path (informational). |
| `device_info` | object | No | `{ koreader_version, device_model }`. |
### Example Request
```http
POST /api/sync/koreader/progress
Authorization: Bearer {device_token}
Content-Type: application/json
```
```json example-request
{
"books": [
{
"uuid": "774641f9-317b-4087-8e04-53bb4392ae56",
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"title": "1984",
"authors": ["George Orwell"],
"percentage": 0.045,
"context_text": "from day to day, but there was none in which Goldstein was not the principal figure. He was the prim",
"page": 14,
"total_pages": 311,
"epubcfi": "/body/DocFragment[1]/body/p[29]/text().598",
"device_info": {
"koreader_version": "v2026.07.1",
"device_model": "emulator"
}
}
],
"sync_mode": "immediate"
}
```
## Response (202 Accepted)
```json example-response
{
"sync_status": "ok",
"books_synced": 1,
"timestamp": "2026-09-26T21:25:09Z",
"device_updated": null
}
```
Per-book results and any detected sync conflicts are carried in
`book_results` and `conflicts` when present.
## Error Responses
| Code | Description |
| ---- | ----------- |
| 400 | Invalid request format |
| 401 | Device authentication failed |
| 500 | Database error |