# Update Media Item Progress Submit reading progress for a media item. This is the position-authority ingest point: the server independently verifies the submission against the book and heals it when the client's projection is wrong. **Endpoint**: `PUT /api/media-items/:id/progress` **Auth**: Required (Bearer token) **Content-Type**: `application/json` See the [Position Contract](position-contract.md) for the three-tier submission model, the verification/healing semantics, and the offset currencies. ## Path Parameters | Parameter | Type | Required | Description | | --------- | ------------- | -------- | ---------------- | | id | string (UUID) | Yes | Media item UUID | ## Request Body All fields are optional; submit what your renderer can observe. | Field | Type | Description | | ----- | ---- | ----------- | | `percentage` | float | Position as a fraction of the whole book (0..1). Tier 1 — the universal field. | | `context_text` | string | Up to 100 whitespace-normalized chars starting at the anchor. Tier 2 — enables verification and healing. | | `epubcfi` | string | Standard wrapped CFI (UTF-16 terminals). Tier 3 — the structural anchor. Note: the server re-derives the stored canonical CFI; a client's own spine numbering is healed if it disagrees with the OPF spine. | | `character_offset` | int | Book-wide rune offset. Accepted but recomputed server-side from the verified anchor on every verified save. | | `current_page`, `total_pages` | int | Fixed-layout position. For fixed-layout formats the page index is the canonical locator. | | `chapter`, `chapter_progress` | int, float | Chapter index and within-chapter fraction. | | `reading_mode` | string | `paged` or `scrolled` (fixed-layout reader state). | | `zoom_level`, `scroll_position_x`, `scroll_position_y` | float | Fixed-layout viewport state. | ### Example Request (reflowable, all three tiers) ```http PUT /api/media-items/774641f9-317b-4087-8e04-53bb4392ae56/progress Authorization: Bearer eyJhbGciOiJIUzI1NiIs... Content-Type: application/json ``` ```json { "percentage": 0.0415, "context_text": "was at war with one of these Powers it was generally at peace with the other. But what was strange w", "epubcfi": "epubcfi(/6/4!/4/8[_idContainer003]/62/1:456)" } ``` ### Example Request (fixed-layout) ```json { "percentage": 0.15, "current_page": 30, "total_pages": 194, "reading_mode": "paged" } ``` ## Behavior - The submission is verified against the book's XHTML when `context_text` is present and the book is convertible reflowable: the CFI is resolved, the text at the anchor is compared with `context_text`, and mismatches heal by text search (percentage disambiguates repeats). See [Position Contract](position-contract.md). - **Anti-clobber guard**: a submission with `percentage < 0.005` is ignored with `{"status": "ignored"}` when the stored row already holds a percentage above `0.01` — re-opening a book at its first page does not wipe real progress. - The response is the stored row after verification, including the healed `epubcfi` and the refreshed `character_offset` when verification ran. ## Response (200 OK) The saved progress row (same shape as [GET](get_media_progress.md), minus the GET-time handles). The `epubcfi` and `percentage` in the response are the server-verified values, which may differ from the submitted ones when healing occurred. ## Error Responses | Code | Description | | ---- | ----------- | | 400 | Invalid request data | | 401 | Invalid or expired token | | 500 | Database error |