New frontmatter'd docs with real shapes (sqlc models pinned directly):
- bookmarks: get/create/update/delete (sync-aware rows: dedup_key,
last_modified_*, tombstones; delete is 204)
- review: upsert (empty string DELETES the review) / get (200 with
{review:null} when absent) / delete
- get single note + highlight (full sqlc row shapes)
- annotation history: restore (POST, kind via query or body) and purge
(DELETE, kind via required query; permanent)
Gate hardening forced by this batch:
- required-field walk now guards non-struct Request types (RawMessage pins)
- update_media_bookmark needed a full example block (no cross-reference
shortcuts — the gate checks every doc standalone)
77 lines
2.0 KiB
Markdown
77 lines
2.0 KiB
Markdown
---
|
||
method: POST
|
||
path: /api/media-items/:id/bookmarks
|
||
auth: jwt
|
||
area: media-items
|
||
---
|
||
<!-- source: internal/handlers/media.go:2206 MediaHandler.CreateMediaBookmark -->
|
||
|
||
# Create Media Bookmark
|
||
|
||
Creates a bookmark through the sync-aware path (dedup + last-write-wins +
|
||
tombstones). Returns **201** with the stored bookmark row.
|
||
|
||
## Example request
|
||
|
||
```json example-request
|
||
{
|
||
"title": "Chapter 3",
|
||
"position": "kindle:pos:1:2",
|
||
"notes": "revisit this",
|
||
"cfi_position": "epubcfi(/6/4)",
|
||
"page_number": 42,
|
||
"chapter_number": 3,
|
||
"percentage": 35.5,
|
||
"chapter_reference": 3,
|
||
"origin": "android"
|
||
}
|
||
```
|
||
|
||
## Example response
|
||
|
||
```json example-response
|
||
{
|
||
"id": "110e8400-e29b-41d4-a716-446655440000",
|
||
"media_item_id": "550e8400-e29b-41d4-a716-446655440000",
|
||
"user_id": "92413c3a-e1b8-4fff-b3f9-95c74478d19a",
|
||
"page_number": 42,
|
||
"chapter_number": 3,
|
||
"cfi_position": "epubcfi(/6/4)",
|
||
"title": "Chapter 3",
|
||
"position": "kindle:pos:1:2",
|
||
"notes": "revisit this",
|
||
"created_at": "2026-10-24T13:57:01Z",
|
||
"dedup_key": "kindle:pos:1:2",
|
||
"last_modified_at": "2026-10-24T13:57:01Z",
|
||
"last_modified_source": "android",
|
||
"device_sync_data": null,
|
||
"percentage_location": 35.5,
|
||
"epubcfi_location": "epubcfi(/6/4)",
|
||
"chapter_reference": 3,
|
||
"deleted": false,
|
||
"deleted_at": null,
|
||
"origin_source": "android"
|
||
}
|
||
```
|
||
|
||
## Request fields
|
||
|
||
| Field | Type | Required | Notes |
|
||
| ----- | ---- | -------- | ----- |
|
||
| title | string | yes | 1–255 chars |
|
||
| position | string | no | ≤100 chars |
|
||
| notes | string | no | ≤100000 chars |
|
||
| cfi_position | string | no | ≤255 chars |
|
||
| page_number | number | no | |
|
||
| chapter_number | number | no | |
|
||
| percentage | number | no | |
|
||
| chapter_reference | number | no | |
|
||
| origin | string | no | client label ("android", "web"); defaults to "web" |
|
||
|
||
## Errors
|
||
|
||
| Status | Body | When |
|
||
| ------ | ---- | ---- |
|
||
| 400 | `{"error": "invalid request"}` / validation error | malformed body |
|
||
| 500 | `{"error": "..."}` | annotation service failure |
|