The progress documentation described GET/POST /api/progress/:id — endpoints that do not exist in the router — while the endpoint every client actually uses (GET/PUT/DELETE /api/media-items/:id/progress) had no field-level docs at all. New: - progress/position-contract.md: the canonical position model — server as position authority, the three-tier submission (percentage / context_text / epubcfi), ingest verification and healing, the restore handles, the OPF spine numbering hazard (canonical CFI spine steps include linear="no" items; clients resolve documents by anchor_href and land by css_selector + char_offset, never by spine step), the offset currencies (UTF-16 at the wire, runes internal), and context_text rules. - progress/get_media_progress.md and update_media_progress.md: the real endpoints with full field tables, conditionality of the restore handles, the first-page anti-clobber guard, and healed-response semantics. - progress/delete_media_progress.md: the real DELETE route. - koreader/sync_progress.md and koreader/get_metadata.md rewritten to the actual payloads: the plugin sends a single-book array whose "epubcfi" field is a CRE xpointer; the metadata response navigates via koreader_xpointer (canonical CFI converted back to CRE), with page as the canonical locator for fixed-layout books. Removed: the five files documenting the nonexistent /api/progress/:id GET/POST/DELETE endpoints. Kept get_progress_history.md (that route exists). The legacy developer/api-reference.md and the indexed api/api-reference.md progress sections now match the wire and link the new docs; the duplicate "Universal Progress" section points at Reading Progress.
88 lines
3.0 KiB
Markdown
88 lines
3.0 KiB
Markdown
# 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
|
|
{
|
|
"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
|
|
{
|
|
"sync_status": "ok",
|
|
"books_synced": 1,
|
|
"timestamp": "2026-09-26T21:25:09Z"
|
|
}
|
|
```
|
|
|
|
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 |
|