{"openapi":"3.1.0","info":{"title":"Leafle API","description":"Use Leafle to discover books, manage your reading history, and find books in public\nlibraries worldwide. Coverage varies by library; use the directory endpoints to\ncheck whether live availability is supported.\n\n## Get started\n\n1. Sign in to [Leafle](/) and open **API access** in the account menu.\n2. Create a personal access token (PAT). Choose **read** to retrieve your data, or\n   **write** to also record books and request recommendations. Save the token when\n   it is shown; it cannot be displayed again. You can revoke it in the same menu.\n3. Send it as `Authorization: Bearer leafle_pat_...`. For example, set the\n   `LEAFLE_TOKEN` environment variable to your token, then run:\n\n```sh\ncurl https://leafle.org/api/me \\\n  -H \"Authorization: Bearer $LEAFLE_TOKEN\"\n\ncurl 'https://leafle.org/api/books?status=to_read' \\\n  -H \"Authorization: Bearer $LEAFLE_TOKEN\"\n```\n\nFor a self-hosted instance, replace the origin with your instance URL.\n\nWith a write token, record a finished book:\n\n```sh\ncurl https://leafle.org/api/reads \\\n  -H \"Authorization: Bearer $LEAFLE_TOKEN\" \\\n  -H 'Content-Type: application/json' \\\n  -d '{\"title\":\"Piranesi\",\"author\":\"Susanna Clarke\",\"rating\":\"loved\"}'\n```\n\n## Access\n\nEach operation states its access requirement: **read token**, **write token**,\n**no authentication**, or **signed link**. A write token includes read access.\nTokens are limited to the operations documented here; they cannot manage accounts,\nreaders, invitations, tokens, or create/revoke gift and portrait links. Manage those\nin the web app. Refreshing recommendation sets and editing/enriching portraits are\nalso web-session operations and are not available to PAT clients. The per-book\nexplanation endpoint is web-session-only too; a set_id does not grant access to it.\n\nThe browser app also accepts its `la_session` cookie and authorised Kinde JWTs\nwith `app:full`. Guest browser conversations have separate limits. These browser\nflows are outside this integration guide. MCP has its own OAuth handshake and\ntools: an MCP access token does **not** authorise this REST API.\n\nA signed gift or portrait link is itself a credential. Use its `token` path value\nonly for that link's endpoints; it is not a PAT. Anyone holding it can access the\nshared content. An unavailable link returns `410` without revealing why.\n\n## Choose a reader\n\n`GET /api/me` returns `readers` and `active_reader_id`. Unless an operation says\notherwise, omitting `reader_id` selects your account's self reader. To act for a\nchild reader, pass their ID on **every** request. The parameter or request schema\nbelow shows its location: query string for reads, file uploads and bodyless\noperations; JSON body for most writes. Hardcover uses the query string for writes\ntoo. A reader outside your account is refused with `403`; `/api/me` instead falls\nback to self for an invalid or unavailable selection.\n\n## Responses and errors\n\nJSON responses may take time to complete. Some send leading whitespace while\nworking; parse the complete JSON document. Chat uses server-sent events instead.\nFields not listed as required may be absent. A nullable field may contain `null`;\nclients should handle both absence and null where the schema allows them. Resolve\nrelative media URLs against the API origin.\n\nErrors normally contain `{\"detail\":\"…\"}`. Validation failures may instead contain\na `detail` array describing invalid fields. Relevant errors are listed per operation.\n`401` means the credential is missing or unusable; `403` means insufficient scope\nor access to the reader is refused. `/api/me` reports a missing/unusable credential\nwith `200` and `user: null` instead. `429` means a request or usage limit was reached;\n`503` means capacity is unavailable or the service is restarting. Limits can vary\nby deployment. Honour `Retry-After` when present. Avoid automatically repeating\nmutations: a lost connection does not mean the operation was cancelled.\n\n## Stream a conversation\n\n```sh\ncurl -N https://leafle.org/api/chat \\\n  -H \"Authorization: Bearer $LEAFLE_TOKEN\" \\\n  -H 'Content-Type: application/json' \\\n  -d '{\"message\":\"I loved Piranesi. What should I read next?\"}'\n```\n\nThe response is `text/event-stream`. Buffer across network chunks and split events\non blank lines. Each `data:` value is a JSON frame; comment lines beginning with\n`:` are keepalives. For example:\n\n```text\ndata: {\"type\":\"status\",\"label\":\"Thinking…\"}\n\ndata: {\"type\":\"token\",\"text\":\"Hello!\"}\n\ndata: {\"type\":\"done\",\"reply\":\"Hello!\",\"messages\":[{\"role\":\"assistant\",\"content\":\"Hello!\"}]}\n\n```\n\n| Frame type | What to do |\n| --- | --- |\n| `status`, `tool` | Show transient progress using `label`; optional status fields add detail. |\n| `token` | Append `text` to the provisional reply. |\n| `discard_tokens` | Clear the provisional reply. |\n| `candidate` | Update a candidate by title/author; interpret its state as explained below. |\n| `pick_preview` | Show a provisional, confirmed-available book; replace it with the final set. |\n| `selection_progress` | Replace the transient picks and disclosures; explanations may be absent. |\n| `recommendation` | Commit final picks and their `source`: library, buy, or shelf. |\n| `done` | Finish the reply and restore the authoritative `messages`. |\n| `error` | End the turn and display `detail`. The HTTP status is already 200 if streaming began. |\n\nThe recommendation frame and `/api/me`'s latest_session may carry an optional\n`set_id`, an opaque saved-set identifier. It is absent when no set was saved. Only\nthe final recommendation frame commits a set; selection_progress can be discarded\non error. The optional `explainable_books` identity list is for the web app's\ndeferred writing; PAT clients cannot call that endpoint and may ignore the list.\n\nFor candidates, `not_held` is a confirmed absence; `unchecked` is an unknown result;\n`grounded` makes no stock claim. A done frame's optional `action` is a web navigation\nhint, not another API operation.\n\nIgnore unknown frame types and optional progress values for forward compatibility.\nA `done` frame with `profile_ready: true` asks you to continue with a new chat\nrequest containing `{\"message\":null}`. Post only the new message, not the transcript.\nIf disconnected, read `/api/me` before retrying: the turn may still be running or\nalready saved. When `discovery_handoff_pending` is true and `chat_inflight` is false,\nsend `{\"message\":null,\"resume_handoff\":true}` for the same reader. That recovery\nrequest rechecks the pending handoff and avoids repeating a completed one.\n","version":"dev"},"paths":{"/api/coverage":{"get":{"tags":["coverage"],"summary":"Get library coverage","description":"**Access: no authentication.**\n\nReturns covered services and country counts. Service kinds distinguish live physical availability, holdings without current shelf status, and digital coverage. Located libraries are directory entries, not a promise of live availability. Responses may be cached for an hour.","operationId":"api_coverage_api_coverage_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CoverageDirectory"}}}}},"security":[]}},"/api/coverage/branches":{"get":{"tags":["coverage"],"summary":"List libraries in a map area","description":"**Access: no authentication.**\n\nPass `bbox` as west,south,east,north in degrees. A missing or malformed bbox returns an empty branches list. Covered branches include their service; kind uncovered means location only. Small viewports show individual uncovered points; larger views use unnamed grid cells with a count of libraries.","operationId":"api_coverage_branches_api_coverage_branches_get","parameters":[{"name":"bbox","in":"query","required":false,"schema":{"type":"string","default":"","title":"Bbox"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CoverageBranches"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"429":{"description":"Request or usage limit reached. Wait before retrying.","content":{"application/json":{"schema":{"type":"object","required":["detail"],"properties":{"detail":{"type":"string"}}}}}}},"security":[]}},"/api/coverage/resolve":{"get":{"tags":["coverage"],"summary":"Check coverage by library name","description":"**Access: no authentication.**\n\nResolve a free-text service or branch name to coverage, or receive suggestions. The caller's approximate country breaks name ties. Only the first 120 characters of `q` are considered.","operationId":"api_coverage_resolve_api_coverage_resolve_get","parameters":[{"name":"q","in":"query","required":false,"schema":{"type":"string","default":"","title":"Q"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CoverageResolution"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"429":{"description":"Request or usage limit reached. Wait before retrying.","content":{"application/json":{"schema":{"type":"object","required":["detail"],"properties":{"detail":{"type":"string"}}}}}}},"security":[]}},"/api/find/{date}":{"get":{"tags":["finds"],"summary":"Get a daily find","description":"**Access: no authentication.**\n\nUse a YYYY-MM-DD date or `today` for the latest published find. Returns frozen editorial and library evidence plus purchase/review links; availability was observed at `checked_at`, not freshly checked. An unavailable date returns 404.","operationId":"api_daily_find_api_find__date__get","parameters":[{"name":"date","in":"path","required":true,"schema":{"type":"string","title":"Date"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DailyFind"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"404":{"description":"No such resource for this reader, or the requested daily find is unavailable.","content":{"application/json":{"schema":{"type":"object","required":["detail"],"properties":{"detail":{"type":"string"}}}}}}},"security":[]}},"/api/find/{date}/mine":{"get":{"tags":["finds"],"summary":"Check a daily find at your library","description":"**Access: read token.**\n\nChecks the daily find at the account's self reader's library; this operation has no `reader_id` selector. `library: null` means no usable library. `checked: false` means the check failed. A null available-copy count means holdings are known but current shelf status is not.","operationId":"api_daily_find_mine_api_find__date__mine_get","parameters":[{"name":"date","in":"path","required":true,"schema":{"type":"string","title":"Date"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DailyFindAvailability"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"Missing or unusable credential.","content":{"application/json":{"schema":{"type":"object","required":["detail"],"properties":{"detail":{"type":"string"}}}}}},"403":{"description":"Insufficient token scope or the reader is outside your account.","content":{"application/json":{"schema":{"type":"object","required":["detail"],"properties":{"detail":{"type":"string"}}}}}},"404":{"description":"No such resource for this reader, or the requested daily find is unavailable.","content":{"application/json":{"schema":{"type":"object","required":["detail"],"properties":{"detail":{"type":"string"}}}}}}},"security":[{"PersonalAccessToken":[]}],"x-leafle-pat-scope":"read"}},"/api/sets":{"get":{"tags":["picks"],"summary":"List recommendation sets","description":"**Access: read token.**\n\nReturns newest-first set summaries and pick counts. Use `offset` and `limit` to page; fetch a set by ID for its books. The effective limit is clamped to 1–50.","operationId":"api_sets_api_sets_get","parameters":[{"name":"offset","in":"query","required":false,"schema":{"type":"integer","default":0,"title":"Offset"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":20,"title":"Limit"}},{"name":"reader_id","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Reader Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SetsIndexPage"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"Missing or unusable credential.","content":{"application/json":{"schema":{"type":"object","required":["detail"],"properties":{"detail":{"type":"string"}}}}}},"403":{"description":"Insufficient token scope or the reader is outside your account.","content":{"application/json":{"schema":{"type":"object","required":["detail"],"properties":{"detail":{"type":"string"}}}}}}},"security":[{"PersonalAccessToken":[]}],"x-leafle-pat-scope":"read"}},"/api/sets/{set_id}":{"get":{"tags":["picks"],"summary":"Get a recommendation set","description":"**Access: read token.**\n\nReturns one saved set with picks, reserve and other suggestions. Past sets are historical records; their saved availability is not a fresh stock check. Returns 404 if the set does not belong to this reader.","operationId":"api_set_api_sets__set_id__get","parameters":[{"name":"set_id","in":"path","required":true,"schema":{"type":"integer","title":"Set Id"}},{"name":"reader_id","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Reader Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RecSet"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"Missing or unusable credential.","content":{"application/json":{"schema":{"type":"object","required":["detail"],"properties":{"detail":{"type":"string"}}}}}},"403":{"description":"Insufficient token scope or the reader is outside your account.","content":{"application/json":{"schema":{"type":"object","required":["detail"],"properties":{"detail":{"type":"string"}}}}}},"404":{"description":"No such resource for this reader, or the requested daily find is unavailable.","content":{"application/json":{"schema":{"type":"object","required":["detail"],"properties":{"detail":{"type":"string"}}}}}}},"security":[{"PersonalAccessToken":[]}],"x-leafle-pat-scope":"read"}},"/api/reads":{"post":{"tags":["books"],"summary":"Record a finished book","operationId":"add_read_api_reads_post","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReadIn"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Ok"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"Missing or unusable credential.","content":{"application/json":{"schema":{"type":"object","required":["detail"],"properties":{"detail":{"type":"string"}}}}}},"403":{"description":"Insufficient token scope or the reader is outside your account.","content":{"application/json":{"schema":{"type":"object","required":["detail"],"properties":{"detail":{"type":"string"}}}}}}},"security":[{"PersonalAccessToken":[]}],"x-leafle-pat-scope":"write","description":"**Access: write token.**\n\nRecord a title and optional author, reaction and loved/liked rating as reading history. Repeating a title updates its record. This history informs future recommendations."},"get":{"tags":["books"],"summary":"List finished books","operationId":"list_reads_api_reads_get","parameters":[{"name":"reader_id","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Reader Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReadsList"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"Missing or unusable credential.","content":{"application/json":{"schema":{"type":"object","required":["detail"],"properties":{"detail":{"type":"string"}}}}}},"403":{"description":"Insufficient token scope or the reader is outside your account.","content":{"application/json":{"schema":{"type":"object","required":["detail"],"properties":{"detail":{"type":"string"}}}}}}},"security":[{"PersonalAccessToken":[]}],"x-leafle-pat-scope":"read","description":"**Access: read token.**\n\nReturns titles and authors recorded as read. Use `/api/books` for the complete ledger with notes, ratings and statuses."}},"/api/books":{"get":{"tags":["books"],"summary":"Browse your books","description":"**Access: read token.**\n\nFilter the reading ledger by `status` or title/author substring `q`. Use `limit` and the returned `next_cursor` to page; stop when it is null. An invalid cursor returns 400.","operationId":"list_books_api_books_get","parameters":[{"name":"reader_id","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Reader Id"}},{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status"}},{"name":"q","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Q"}},{"name":"limit","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Limit"}},{"name":"cursor","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cursor"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BooksPage"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"Missing or unusable credential.","content":{"application/json":{"schema":{"type":"object","required":["detail"],"properties":{"detail":{"type":"string"}}}}}},"403":{"description":"Insufficient token scope or the reader is outside your account.","content":{"application/json":{"schema":{"type":"object","required":["detail"],"properties":{"detail":{"type":"string"}}}}}},"400":{"description":"Invalid input; see detail for the reason.","content":{"application/json":{"schema":{"type":"object","required":["detail"],"properties":{"detail":{"type":"string"}}}}}}},"security":[{"PersonalAccessToken":[]}],"x-leafle-pat-scope":"read"}},"/api/books/{book_id}/status":{"post":{"tags":["books"],"summary":"Change a book's status","description":"**Access: write token.**\n\nSet reading, read, owned, to_read or dnf. Entering to_read adds the book to the queue; leaving it removes its queue position. Owned means shelf inventory, not evidence that you have read the book.","operationId":"set_book_status_api_books__book_id__status_post","parameters":[{"name":"book_id","in":"path","required":true,"schema":{"type":"integer","title":"Book Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BookStatusIn"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Ok"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"Missing or unusable credential.","content":{"application/json":{"schema":{"type":"object","required":["detail"],"properties":{"detail":{"type":"string"}}}}}},"403":{"description":"Insufficient token scope or the reader is outside your account.","content":{"application/json":{"schema":{"type":"object","required":["detail"],"properties":{"detail":{"type":"string"}}}}}},"404":{"description":"No such resource for this reader, or the requested daily find is unavailable.","content":{"application/json":{"schema":{"type":"object","required":["detail"],"properties":{"detail":{"type":"string"}}}}}}},"security":[{"PersonalAccessToken":[]}],"x-leafle-pat-scope":"write"}},"/api/books/{book_id}":{"delete":{"tags":["books"],"summary":"Remove a book","description":"**Access: write token.**\n\nRemove this reader's book record. Returns 404 if the book is absent or belongs to another reader.","operationId":"remove_book_api_books__book_id__delete","parameters":[{"name":"book_id","in":"path","required":true,"schema":{"type":"integer","title":"Book Id"}},{"name":"reader_id","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Reader Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Ok"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"Missing or unusable credential.","content":{"application/json":{"schema":{"type":"object","required":["detail"],"properties":{"detail":{"type":"string"}}}}}},"403":{"description":"Insufficient token scope or the reader is outside your account.","content":{"application/json":{"schema":{"type":"object","required":["detail"],"properties":{"detail":{"type":"string"}}}}}},"404":{"description":"No such resource for this reader, or the requested daily find is unavailable.","content":{"application/json":{"schema":{"type":"object","required":["detail"],"properties":{"detail":{"type":"string"}}}}}}},"security":[{"PersonalAccessToken":[]}],"x-leafle-pat-scope":"write"}},"/api/books/{book_id}/note":{"post":{"tags":["books"],"summary":"Update a book note","description":"**Access: write token.**\n\nSave your reaction or note (up to 2,000 characters). A blank note clears it; a changed note informs future recommendations.","operationId":"set_book_note_api_books__book_id__note_post","parameters":[{"name":"book_id","in":"path","required":true,"schema":{"type":"integer","title":"Book Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BookNoteIn"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Ok"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"Missing or unusable credential.","content":{"application/json":{"schema":{"type":"object","required":["detail"],"properties":{"detail":{"type":"string"}}}}}},"403":{"description":"Insufficient token scope or the reader is outside your account.","content":{"application/json":{"schema":{"type":"object","required":["detail"],"properties":{"detail":{"type":"string"}}}}}},"404":{"description":"No such resource for this reader, or the requested daily find is unavailable.","content":{"application/json":{"schema":{"type":"object","required":["detail"],"properties":{"detail":{"type":"string"}}}}}}},"security":[{"PersonalAccessToken":[]}],"x-leafle-pat-scope":"write"}},"/api/books/{book_id}/rating":{"post":{"tags":["books"],"summary":"Rate a book","description":"**Access: write token.**\n\nSet loved or liked, or use none to clear the rating. Setting loved/liked also records the book as read.","operationId":"set_book_rating_api_books__book_id__rating_post","parameters":[{"name":"book_id","in":"path","required":true,"schema":{"type":"integer","title":"Book Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BookRatingIn"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Ok"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"Missing or unusable credential.","content":{"application/json":{"schema":{"type":"object","required":["detail"],"properties":{"detail":{"type":"string"}}}}}},"403":{"description":"Insufficient token scope or the reader is outside your account.","content":{"application/json":{"schema":{"type":"object","required":["detail"],"properties":{"detail":{"type":"string"}}}}}},"404":{"description":"No such resource for this reader, or the requested daily find is unavailable.","content":{"application/json":{"schema":{"type":"object","required":["detail"],"properties":{"detail":{"type":"string"}}}}}}},"security":[{"PersonalAccessToken":[]}],"x-leafle-pat-scope":"write"}},"/api/dislike":{"post":{"tags":["picks"],"summary":"Reject a book","description":"**Access: write token.**\n\nExclude a book from future recommendations, with an optional reason. Posting the same book again updates its reason.","operationId":"add_dislike_api_dislike_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DislikeIn"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Ok"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"Missing or unusable credential.","content":{"application/json":{"schema":{"type":"object","required":["detail"],"properties":{"detail":{"type":"string"}}}}}},"403":{"description":"Insufficient token scope or the reader is outside your account.","content":{"application/json":{"schema":{"type":"object","required":["detail"],"properties":{"detail":{"type":"string"}}}}}}},"security":[{"PersonalAccessToken":[]}],"x-leafle-pat-scope":"write"}},"/api/dislike/remove":{"post":{"tags":["picks"],"summary":"Undo a rejection","description":"**Access: write token.**\n\nRemove the reader's rejection of this title. Other exclusions, such as reading history and past recommendations, still apply.","operationId":"remove_dislike_api_dislike_remove_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DislikeIn"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Ok"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"Missing or unusable credential.","content":{"application/json":{"schema":{"type":"object","required":["detail"],"properties":{"detail":{"type":"string"}}}}}},"403":{"description":"Insufficient token scope or the reader is outside your account.","content":{"application/json":{"schema":{"type":"object","required":["detail"],"properties":{"detail":{"type":"string"}}}}}}},"security":[{"PersonalAccessToken":[]}],"x-leafle-pat-scope":"write"}},"/api/dislikes":{"get":{"tags":["picks"],"summary":"List rejected books","operationId":"list_dislikes_api_dislikes_get","parameters":[{"name":"reader_id","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Reader Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DislikesList"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"Missing or unusable credential.","content":{"application/json":{"schema":{"type":"object","required":["detail"],"properties":{"detail":{"type":"string"}}}}}},"403":{"description":"Insufficient token scope or the reader is outside your account.","content":{"application/json":{"schema":{"type":"object","required":["detail"],"properties":{"detail":{"type":"string"}}}}}}},"security":[{"PersonalAccessToken":[]}],"x-leafle-pat-scope":"read","description":"**Access: read token.**\n\nReturns the reader's rejected titles, authors and reasons."}},"/api/plan-next":{"post":{"tags":["picks"],"summary":"Choose the next read","description":"**Access: write token.**\n\nAdd or move a title to queue position 1. A book already read keeps its reading status and is not queued. For a new queue integration, prefer `/api/books/to-read` and `/api/books/{book_id}/queue-position`.","operationId":"plan_next_api_plan_next_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PlanNextIn"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Ok"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"Missing or unusable credential.","content":{"application/json":{"schema":{"type":"object","required":["detail"],"properties":{"detail":{"type":"string"}}}}}},"403":{"description":"Insufficient token scope or the reader is outside your account.","content":{"application/json":{"schema":{"type":"object","required":["detail"],"properties":{"detail":{"type":"string"}}}}}}},"security":[{"PersonalAccessToken":[]}],"x-leafle-pat-scope":"write"}},"/api/plan-next/remove":{"post":{"tags":["picks"],"summary":"Clear a planned read","description":"**Access: write token.**\n\nRemove a title while it is still to-read. A book already read, owned or moved to another status keeps its record.","operationId":"plan_next_remove_api_plan_next_remove_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PlanNextIn"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Ok"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"Missing or unusable credential.","content":{"application/json":{"schema":{"type":"object","required":["detail"],"properties":{"detail":{"type":"string"}}}}}},"403":{"description":"Insufficient token scope or the reader is outside your account.","content":{"application/json":{"schema":{"type":"object","required":["detail"],"properties":{"detail":{"type":"string"}}}}}}},"security":[{"PersonalAccessToken":[]}],"x-leafle-pat-scope":"write"}},"/api/plan-next/ack":{"post":{"tags":["picks"],"summary":"Acknowledge the next-read greeting","description":"**Access: write token.**\n\nMark the pending next-read greeting as shown. This is a bodyless POST; select the reader with the query parameter.","operationId":"plan_next_ack_api_plan_next_ack_post","parameters":[{"name":"reader_id","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Reader Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Ok"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"Missing or unusable credential.","content":{"application/json":{"schema":{"type":"object","required":["detail"],"properties":{"detail":{"type":"string"}}}}}},"403":{"description":"Insufficient token scope or the reader is outside your account.","content":{"application/json":{"schema":{"type":"object","required":["detail"],"properties":{"detail":{"type":"string"}}}}}}},"security":[{"PersonalAccessToken":[]}],"x-leafle-pat-scope":"write"}},"/api/books/to-read":{"post":{"tags":["books"],"summary":"Add to the reading queue","description":"**Access: write token.**\n\nAppend a book to the queue, optionally with its recommendation rationale in `why`. An already-queued book keeps its position. Returns 409 if the book is already read.","operationId":"add_to_read_api_books_to_read_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ToReadIn"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueueChanged"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"Missing or unusable credential.","content":{"application/json":{"schema":{"type":"object","required":["detail"],"properties":{"detail":{"type":"string"}}}}}},"403":{"description":"Insufficient token scope or the reader is outside your account.","content":{"application/json":{"schema":{"type":"object","required":["detail"],"properties":{"detail":{"type":"string"}}}}}},"409":{"description":"Conflicting state: a turn is running, the book is already read, or the library offer has been settled.","content":{"application/json":{"schema":{"type":"object","required":["detail"],"properties":{"detail":{"type":"string"}}}}}}},"security":[{"PersonalAccessToken":[]}],"x-leafle-pat-scope":"write"}},"/api/books/{book_id}/queue-position":{"post":{"tags":["books"],"summary":"Reorder the reading queue","description":"**Access: write token.**\n\nMove a queued book to a 1-based position, clamped to the queue length. Returns 404 if the book is not queued for this reader.","operationId":"set_queue_position_api_books__book_id__queue_position_post","parameters":[{"name":"book_id","in":"path","required":true,"schema":{"type":"integer","title":"Book Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueuePositionIn"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueueChanged"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"Missing or unusable credential.","content":{"application/json":{"schema":{"type":"object","required":["detail"],"properties":{"detail":{"type":"string"}}}}}},"403":{"description":"Insufficient token scope or the reader is outside your account.","content":{"application/json":{"schema":{"type":"object","required":["detail"],"properties":{"detail":{"type":"string"}}}}}},"404":{"description":"No such resource for this reader, or the requested daily find is unavailable.","content":{"application/json":{"schema":{"type":"object","required":["detail"],"properties":{"detail":{"type":"string"}}}}}}},"security":[{"PersonalAccessToken":[]}],"x-leafle-pat-scope":"write"}},"/api/books/availability":{"get":{"tags":["books"],"summary":"Check queued books","description":"**Access: read token.**\n\nReturns library, digital and purchase links for queued books. `unknown` means a check failed or has not run; `checked_at` is null then. Results use a shared cache. `capped` indicates some books exceeded the request's checking budget.","operationId":"queue_availability_api_books_availability_get","parameters":[{"name":"reader_id","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Reader Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueueAvailability"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"Missing or unusable credential.","content":{"application/json":{"schema":{"type":"object","required":["detail"],"properties":{"detail":{"type":"string"}}}}}},"403":{"description":"Insufficient token scope or the reader is outside your account.","content":{"application/json":{"schema":{"type":"object","required":["detail"],"properties":{"detail":{"type":"string"}}}}}}},"security":[{"PersonalAccessToken":[]}],"x-leafle-pat-scope":"read"},"post":{"tags":["books"],"summary":"Recheck one queued book","description":"**Access: write token.**\n\nCheck the requested `book_id` again, bypassing the availability cache. Returns the same shape as the GET with one book, or 404 if it is not in this reader's queue.","operationId":"queue_recheck_api_books_availability_post","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueueRecheckIn"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueueAvailability"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"Missing or unusable credential.","content":{"application/json":{"schema":{"type":"object","required":["detail"],"properties":{"detail":{"type":"string"}}}}}},"403":{"description":"Insufficient token scope or the reader is outside your account.","content":{"application/json":{"schema":{"type":"object","required":["detail"],"properties":{"detail":{"type":"string"}}}}}},"404":{"description":"No such resource for this reader, or the requested daily find is unavailable.","content":{"application/json":{"schema":{"type":"object","required":["detail"],"properties":{"detail":{"type":"string"}}}}}}},"security":[{"PersonalAccessToken":[]}],"x-leafle-pat-scope":"write"}},"/api/libraries/search":{"get":{"tags":["libraries"],"summary":"Search the library directory","operationId":"search_libraries_api_libraries_search_get","parameters":[{"name":"q","in":"query","required":false,"schema":{"type":"string","maxLength":240,"default":"","title":"Q"}},{"name":"country","in":"query","required":false,"schema":{"anyOf":[{"type":"string","maxLength":2},{"type":"null"}],"title":"Country"}},{"name":"lat","in":"query","required":false,"schema":{"anyOf":[{"type":"number","maximum":90,"minimum":-90},{"type":"null"}],"title":"Lat"}},{"name":"lng","in":"query","required":false,"schema":{"anyOf":[{"type":"number","maximum":180,"minimum":-180},{"type":"null"}],"title":"Lng"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LibrarySearch"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"429":{"description":"Request or usage limit reached. Wait before retrying.","content":{"application/json":{"schema":{"type":"object","required":["detail"],"properties":{"detail":{"type":"string"}}}}}}},"security":[],"description":"**Access: no authentication.**\n\nSearch by name or place. `country` is a two-letter country code; omit it for an IP-based hint or send an empty string to search worldwide. Location and live coverage are separate: check each result's coverage status."}},"/api/reader/libraries":{"get":{"tags":["libraries"],"summary":"Get your saved libraries","operationId":"reader_libraries_api_reader_libraries_get","parameters":[{"name":"reader_id","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Reader Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SavedLibraries"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"Missing or unusable credential.","content":{"application/json":{"schema":{"type":"object","required":["detail"],"properties":{"detail":{"type":"string"}}}}}},"403":{"description":"Insufficient token scope or the reader is outside your account.","content":{"application/json":{"schema":{"type":"object","required":["detail"],"properties":{"detail":{"type":"string"}}}}}}},"security":[{"PersonalAccessToken":[]}],"x-leafle-pat-scope":"read","description":"**Access: read token.**\n\nReturns ordered library selections, whether the choice was explicit, and any pending chat offer ID. A legacy profile selection can appear when no explicit choice has been saved."},"put":{"tags":["libraries"],"summary":"Save your library choices","operationId":"set_reader_libraries_api_reader_libraries_put","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SaveLibrariesIn"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SavedLibraries"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"anyOf":[{"$ref":"#/components/schemas/HTTPValidationError"},{"type":"object","required":["detail"],"properties":{"detail":{"type":"string"}}}]}}}},"401":{"description":"Missing or unusable credential.","content":{"application/json":{"schema":{"type":"object","required":["detail"],"properties":{"detail":{"type":"string"}}}}}},"403":{"description":"Insufficient token scope or the reader is outside your account.","content":{"application/json":{"schema":{"type":"object","required":["detail"],"properties":{"detail":{"type":"string"}}}}}},"409":{"description":"Conflicting state: a turn is running, the book is already read, or the library offer has been settled.","content":{"application/json":{"schema":{"type":"object","required":["detail"],"properties":{"detail":{"type":"string"}}}}}}},"security":[{"PersonalAccessToken":[]}],"x-leafle-pat-scope":"write","description":"**Access: write token.**\n\nSend up to eight ordered library IDs from directory results. `ids: []` explicitly declines library use; `ids: null` clears the choice. If confirming a chat offer, include its `offer_id`; a superseded offer returns 409. Buying preferences are unchanged."}},"/api/libraries/nearby":{"post":{"tags":["libraries"],"summary":"Find nearby libraries","operationId":"nearby_libraries_api_libraries_nearby_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/NearbyIn"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LibrarySearch"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"429":{"description":"Request or usage limit reached. Wait before retrying.","content":{"application/json":{"schema":{"type":"object","required":["detail"],"properties":{"detail":{"type":"string"}}}}}}},"security":[],"description":"**Access: no authentication.**\n\nSend latitude and longitude in the JSON body. Returns nearby library identities with their coverage status; coordinates need not appear in a URL."}},"/api/reader/library-offer/dismiss":{"post":{"tags":["libraries"],"summary":"Dismiss a library offer","operationId":"dismiss_library_offer_api_reader_library_offer_dismiss_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DismissOfferIn"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Ok"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"Missing or unusable credential.","content":{"application/json":{"schema":{"type":"object","required":["detail"],"properties":{"detail":{"type":"string"}}}}}},"403":{"description":"Insufficient token scope or the reader is outside your account.","content":{"application/json":{"schema":{"type":"object","required":["detail"],"properties":{"detail":{"type":"string"}}}}}}},"security":[{"PersonalAccessToken":[]}],"x-leafle-pat-scope":"write","description":"**Access: write token.**\n\nDismiss the named pending chat offer for this reader. A stale offer ID has no effect on a newer offer."}},"/api/import":{"post":{"tags":["books"],"summary":"Import reading history","description":"**Access: write token.**\n\nUpload a Goodreads or StoryGraph `.csv` export in multipart field `file`. It merges reading history with existing records. Taste updates continue in the background. Pass `reader_id` in the query string.","operationId":"import_shelf_api_import_post","parameters":[{"name":"reader_id","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Reader Id"}}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/Body_import_shelf_api_import_post"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ImportResult"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"Missing or unusable credential.","content":{"application/json":{"schema":{"type":"object","required":["detail"],"properties":{"detail":{"type":"string"}}}}}},"403":{"description":"Insufficient token scope or the reader is outside your account.","content":{"application/json":{"schema":{"type":"object","required":["detail"],"properties":{"detail":{"type":"string"}}}}}},"400":{"description":"Invalid input; see detail for the reason.","content":{"application/json":{"schema":{"type":"object","required":["detail"],"properties":{"detail":{"type":"string"}}}}}},"413":{"description":"The uploaded file exceeds this deployment's size limit.","content":{"application/json":{"schema":{"type":"object","required":["detail"],"properties":{"detail":{"type":"string"}}}}}}},"security":[{"PersonalAccessToken":[]}],"x-leafle-pat-scope":"write"}},"/api/photo":{"post":{"tags":["photo"],"summary":"Identify a book or shelf photo","description":"**Access: write token.**\n\nUpload an image in multipart field `file`. A cover returns candidate books without recording a read; a shelf records owned inventory; an unrecognised image returns mode none. Confirm reads separately. Pass `reader_id` in the query string.","operationId":"photo_auto_api_photo_post","parameters":[{"name":"reader_id","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Reader Id"}}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/Body_photo_auto_api_photo_post"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/PhotoCoverScan"},{"$ref":"#/components/schemas/PhotoShelfScan"},{"$ref":"#/components/schemas/PhotoNoneScan"}],"discriminator":{"propertyName":"mode","mapping":{"cover":"#/components/schemas/PhotoCoverScan","shelf":"#/components/schemas/PhotoShelfScan","none":"#/components/schemas/PhotoNoneScan"}},"title":"Response Photo Auto Api Photo Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"Missing or unusable credential.","content":{"application/json":{"schema":{"type":"object","required":["detail"],"properties":{"detail":{"type":"string"}}}}}},"403":{"description":"Insufficient token scope or the reader is outside your account.","content":{"application/json":{"schema":{"type":"object","required":["detail"],"properties":{"detail":{"type":"string"}}}}}},"400":{"description":"Invalid input; see detail for the reason.","content":{"application/json":{"schema":{"type":"object","required":["detail"],"properties":{"detail":{"type":"string"}}}}}},"413":{"description":"The uploaded file exceeds this deployment's size limit.","content":{"application/json":{"schema":{"type":"object","required":["detail"],"properties":{"detail":{"type":"string"}}}}}},"429":{"description":"Request or usage limit reached. Wait before retrying.","content":{"application/json":{"schema":{"type":"object","required":["detail"],"properties":{"detail":{"type":"string"}}}}}},"502":{"description":"The image recognition provider could not read the photo.","content":{"application/json":{"schema":{"type":"object","required":["detail"],"properties":{"detail":{"type":"string"}}}}}},"503":{"description":"Capacity is unavailable or the service is restarting. Honour Retry-After when present.","content":{"application/json":{"schema":{"type":"object","required":["detail"],"properties":{"detail":{"type":"string"}}}}}}},"security":[{"PersonalAccessToken":[]}],"x-leafle-pat-scope":"write"}},"/api/photo/cover":{"post":{"tags":["photo"],"summary":"Identify a book cover","description":"**Access: write token.**\n\nUpload an image in multipart field `file` to identify candidate books. Nothing is marked read; confirm a chosen book with `/api/reads`. Pass `reader_id` in the query string.","operationId":"photo_cover_api_photo_cover_post","parameters":[{"name":"reader_id","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Reader Id"}}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/Body_photo_cover_api_photo_cover_post"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PhotoCoverResult"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"Missing or unusable credential.","content":{"application/json":{"schema":{"type":"object","required":["detail"],"properties":{"detail":{"type":"string"}}}}}},"403":{"description":"Insufficient token scope or the reader is outside your account.","content":{"application/json":{"schema":{"type":"object","required":["detail"],"properties":{"detail":{"type":"string"}}}}}},"400":{"description":"Invalid input; see detail for the reason.","content":{"application/json":{"schema":{"type":"object","required":["detail"],"properties":{"detail":{"type":"string"}}}}}},"413":{"description":"The uploaded file exceeds this deployment's size limit.","content":{"application/json":{"schema":{"type":"object","required":["detail"],"properties":{"detail":{"type":"string"}}}}}},"429":{"description":"Request or usage limit reached. Wait before retrying.","content":{"application/json":{"schema":{"type":"object","required":["detail"],"properties":{"detail":{"type":"string"}}}}}},"502":{"description":"The image recognition provider could not read the photo.","content":{"application/json":{"schema":{"type":"object","required":["detail"],"properties":{"detail":{"type":"string"}}}}}},"503":{"description":"Capacity is unavailable or the service is restarting. Honour Retry-After when present.","content":{"application/json":{"schema":{"type":"object","required":["detail"],"properties":{"detail":{"type":"string"}}}}}}},"security":[{"PersonalAccessToken":[]}],"x-leafle-pat-scope":"write"}},"/api/photo/shelf":{"post":{"tags":["photo"],"summary":"Record books from a shelf photo","description":"**Access: write token.**\n\nUpload an image in multipart field `file`. Identified books are recorded as owned inventory, not reading history. Pass `reader_id` in the query string.","operationId":"photo_shelf_api_photo_shelf_post","parameters":[{"name":"reader_id","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Reader Id"}}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/Body_photo_shelf_api_photo_shelf_post"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PhotoShelfResult"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"Missing or unusable credential.","content":{"application/json":{"schema":{"type":"object","required":["detail"],"properties":{"detail":{"type":"string"}}}}}},"403":{"description":"Insufficient token scope or the reader is outside your account.","content":{"application/json":{"schema":{"type":"object","required":["detail"],"properties":{"detail":{"type":"string"}}}}}},"400":{"description":"Invalid input; see detail for the reason.","content":{"application/json":{"schema":{"type":"object","required":["detail"],"properties":{"detail":{"type":"string"}}}}}},"413":{"description":"The uploaded file exceeds this deployment's size limit.","content":{"application/json":{"schema":{"type":"object","required":["detail"],"properties":{"detail":{"type":"string"}}}}}},"429":{"description":"Request or usage limit reached. Wait before retrying.","content":{"application/json":{"schema":{"type":"object","required":["detail"],"properties":{"detail":{"type":"string"}}}}}},"502":{"description":"The image recognition provider could not read the photo.","content":{"application/json":{"schema":{"type":"object","required":["detail"],"properties":{"detail":{"type":"string"}}}}}},"503":{"description":"Capacity is unavailable or the service is restarting. Honour Retry-After when present.","content":{"application/json":{"schema":{"type":"object","required":["detail"],"properties":{"detail":{"type":"string"}}}}}}},"security":[{"PersonalAccessToken":[]}],"x-leafle-pat-scope":"write"}},"/api/photo/shelf/reads":{"post":{"tags":["photo"],"summary":"Confirm reads from a shelf scan","description":"**Access: write token.**\n\nSend the scan's books and the reader's answer about which they have read. Matching owned books are upgraded to reading history; the response lists the upgraded titles.","operationId":"photo_shelf_reads_api_photo_shelf_reads_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ShelfReadsIn"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ShelfReadsResult"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"Missing or unusable credential.","content":{"application/json":{"schema":{"type":"object","required":["detail"],"properties":{"detail":{"type":"string"}}}}}},"403":{"description":"Insufficient token scope or the reader is outside your account.","content":{"application/json":{"schema":{"type":"object","required":["detail"],"properties":{"detail":{"type":"string"}}}}}},"429":{"description":"Request or usage limit reached. Wait before retrying.","content":{"application/json":{"schema":{"type":"object","required":["detail"],"properties":{"detail":{"type":"string"}}}}}},"503":{"description":"Capacity is unavailable or the service is restarting. Honour Retry-After when present.","content":{"application/json":{"schema":{"type":"object","required":["detail"],"properties":{"detail":{"type":"string"}}}}}}},"security":[{"PersonalAccessToken":[]}],"x-leafle-pat-scope":"write"}},"/api/hardcover":{"get":{"tags":["books"],"summary":"Get Hardcover connection status","description":"**Access: read token.**\n\nReturns this reader's connection state and preferences, never their credentials. Establish the connection interactively through Connected apps in Leafle.","operationId":"get_connection_api_hardcover_get","parameters":[{"name":"reader_id","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Reader Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HardcoverConnection"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"Missing or unusable credential.","content":{"application/json":{"schema":{"type":"object","required":["detail"],"properties":{"detail":{"type":"string"}}}}}},"403":{"description":"Insufficient token scope or the reader is outside your account.","content":{"application/json":{"schema":{"type":"object","required":["detail"],"properties":{"detail":{"type":"string"}}}}}}},"security":[{"PersonalAccessToken":[]}],"x-leafle-pat-scope":"read"},"patch":{"tags":["books"],"summary":"Update Hardcover sync preferences","description":"**Access: write token.**\n\nChange writing-back preferences and sync frequency. The connection must already exist (404 otherwise). Enabling writes requires suitable Hardcover permission; a missing permission is reported by the next sync. `reader_id` is a query parameter.","operationId":"update_preferences_api_hardcover_patch","parameters":[{"name":"reader_id","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Reader Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PreferencesRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HardcoverConnection"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"Missing or unusable credential.","content":{"application/json":{"schema":{"type":"object","required":["detail"],"properties":{"detail":{"type":"string"}}}}}},"403":{"description":"Insufficient token scope or the reader is outside your account.","content":{"application/json":{"schema":{"type":"object","required":["detail"],"properties":{"detail":{"type":"string"}}}}}},"404":{"description":"No such resource for this reader, or the requested daily find is unavailable.","content":{"application/json":{"schema":{"type":"object","required":["detail"],"properties":{"detail":{"type":"string"}}}}}}},"security":[{"PersonalAccessToken":[]}],"x-leafle-pat-scope":"write"},"delete":{"tags":["books"],"summary":"Disconnect Hardcover","description":"**Access: write token.**\n\nRemove the connection and sync mappings. Imported books stay in the reading ledger. Remote token revocation is best-effort if Hardcover cannot be reached. `reader_id` is a query parameter.","operationId":"disconnect_api_hardcover_delete","parameters":[{"name":"reader_id","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Reader Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HardcoverConnection"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"Missing or unusable credential.","content":{"application/json":{"schema":{"type":"object","required":["detail"],"properties":{"detail":{"type":"string"}}}}}},"403":{"description":"Insufficient token scope or the reader is outside your account.","content":{"application/json":{"schema":{"type":"object","required":["detail"],"properties":{"detail":{"type":"string"}}}}}}},"security":[{"PersonalAccessToken":[]}],"x-leafle-pat-scope":"write"}},"/api/hardcover/sync":{"post":{"tags":["books"],"summary":"Sync with Hardcover","description":"**Access: write token.**\n\nRun a sync for the existing connection and return the result. Inspect `ok`, `reason` and `error` even on HTTP 200. `reader_id` is a query parameter.","operationId":"sync_now_api_hardcover_sync_post","parameters":[{"name":"reader_id","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Reader Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HardcoverSyncResult"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"Missing or unusable credential.","content":{"application/json":{"schema":{"type":"object","required":["detail"],"properties":{"detail":{"type":"string"}}}}}},"403":{"description":"Insufficient token scope or the reader is outside your account.","content":{"application/json":{"schema":{"type":"object","required":["detail"],"properties":{"detail":{"type":"string"}}}}}}},"security":[{"PersonalAccessToken":[]}],"x-leafle-pat-scope":"write"}},"/api/portrait":{"get":{"tags":["portrait"],"summary":"Get your reading portrait","description":"**Access: read token.**\n\nReturns the reader's taste portrait. `ready: false` means there is not yet enough synthesised reading evidence to show one.","operationId":"api_portrait_api_portrait_get","parameters":[{"name":"reader_id","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Reader Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Portrait"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"Missing or unusable credential.","content":{"application/json":{"schema":{"type":"object","required":["detail"],"properties":{"detail":{"type":"string"}}}}}},"403":{"description":"Insufficient token scope or the reader is outside your account.","content":{"application/json":{"schema":{"type":"object","required":["detail"],"properties":{"detail":{"type":"string"}}}}}}},"security":[{"PersonalAccessToken":[]}],"x-leafle-pat-scope":"read"}},"/api/portrait/status":{"get":{"tags":["portrait"],"summary":"Check portrait readiness","description":"**Access: read token.**\n\nA lightweight readiness check. Compare the opaque `version` value with your last value to detect a changed portrait; do not parse it.","operationId":"api_portrait_status_api_portrait_status_get","parameters":[{"name":"reader_id","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Reader Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PortraitStatus"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"Missing or unusable credential.","content":{"application/json":{"schema":{"type":"object","required":["detail"],"properties":{"detail":{"type":"string"}}}}}},"403":{"description":"Insufficient token scope or the reader is outside your account.","content":{"application/json":{"schema":{"type":"object","required":["detail"],"properties":{"detail":{"type":"string"}}}}}}},"security":[{"PersonalAccessToken":[]}],"x-leafle-pat-scope":"read"}},"/api/portrait/shared/{token}":{"get":{"tags":["portrait"],"summary":"Open a shared reading portrait","description":"**Access: signed link.** The token in the path is the credential.\n\nReturns the frozen, reader-approved snapshot. Omitted sections were not shared. Images may be relative media URLs or inline data URIs. Older snapshots remain unchanged; the schema describes newly created shares. An invalid, expired or revoked link returns 410.","operationId":"api_portrait_shared_api_portrait_shared__token__get","parameters":[{"name":"token","in":"path","required":true,"schema":{"type":"string","title":"Token"},"description":"The signed token from the gift or portrait URL."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ShareSnapshot"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"410":{"description":"This link is no longer available.","content":{"application/json":{"schema":{"type":"object","required":["detail"],"properties":{"detail":{"type":"string"}}}}}}},"security":[]}},"/api/gift/{token}":{"get":{"tags":["gifts"],"summary":"Open a gift link","operationId":"api_gift_info_api_gift__token__get","parameters":[{"name":"token","in":"path","required":true,"schema":{"type":"string","title":"Token"},"description":"The signed token from the gift or portrait URL."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GiftPage"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"410":{"description":"This link is no longer available.","content":{"application/json":{"schema":{"type":"object","required":["detail"],"properties":{"detail":{"type":"string"}}}}}}},"security":[],"description":"**Access: signed link.** The token in the path is the credential.\n\nReturns only the reader-approved gift card, previously generated suggestions and claims. No account sign-in is needed. Reading and claiming remain possible after the link's recommendation allowance is spent, until expiry or revocation."}},"/api/gift/{token}/claim":{"post":{"tags":["gifts"],"summary":"Claim or release a gift idea","description":"**Access: signed link.** The token in the path is the credential.\n\nMark one of this link's suggested books as yours to buy. A new claim needs a valid email for the release link; `undo: true` releases it without an email. Claims coordinate givers; they are not purchases or reservations. Anyone holding the gift link can change them.","operationId":"api_gift_claim_api_gift__token__claim_post","parameters":[{"name":"token","in":"path","required":true,"schema":{"type":"string","title":"Token"},"description":"The signed token from the gift or portrait URL."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GiftClaimBody"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GiftClaimResult"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"410":{"description":"This link is no longer available.","content":{"application/json":{"schema":{"type":"object","required":["detail"],"properties":{"detail":{"type":"string"}}}}}},"400":{"description":"Invalid input; see detail for the reason.","content":{"application/json":{"schema":{"type":"object","required":["detail"],"properties":{"detail":{"type":"string"}}}}}},"429":{"description":"Request or usage limit reached. Wait before retrying.","content":{"application/json":{"schema":{"type":"object","required":["detail"],"properties":{"detail":{"type":"string"}}}}}}},"security":[]}},"/api/gift/{token}/recommend":{"post":{"tags":["gifts"],"summary":"Request gift ideas","operationId":"api_gift_recommend_api_gift__token__recommend_post","parameters":[{"name":"token","in":"path","required":true,"schema":{"type":"string","title":"Token"},"description":"The signed token from the gift or portrait URL."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GiftAskBody"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GiftRecommendation"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"410":{"description":"This link is no longer available.","content":{"application/json":{"schema":{"type":"object","required":["detail"],"properties":{"detail":{"type":"string"}}}}}},"429":{"description":"Request or usage limit reached. Wait before retrying.","content":{"application/json":{"schema":{"type":"object","required":["detail"],"properties":{"detail":{"type":"string"}}}}}},"503":{"description":"Capacity is unavailable or the service is restarting. Honour Retry-After when present.","content":{"application/json":{"schema":{"type":"object","required":["detail"],"properties":{"detail":{"type":"string"}}}}}}},"security":[],"description":"**Access: signed link.** The token in the path is the credential.\n\nGenerate gift ideas from the frozen gift card and giver's request. This consumes a use of the link. Reopening the link retrieves earlier ideas without spending another use. An exhausted, invalid, expired or revoked link returns 410."}},"/api/me":{"get":{"tags":["account"],"summary":"Get your account and active reader","operationId":"api_me_api_me_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"anyOf":[{"$ref":"#/components/schemas/Me"},{"$ref":"#/components/schemas/GuestMe"},{"type":"object","required":["user"],"properties":{"user":{"type":"null"},"waitlisted":{"type":"boolean"},"guest_chat":{"type":"boolean"}}}]}}}},"403":{"description":"Insufficient token scope or the reader is outside your account.","content":{"application/json":{"schema":{"type":"object","required":["detail"],"properties":{"detail":{"type":"string"}}}}}}},"parameters":[{"name":"reader_id","in":"query","required":false,"description":"Select an owned reader. Invalid or unavailable IDs fall back to self.","schema":{"type":"string"},"example":"42"}],"security":[{"PersonalAccessToken":[]},{}],"x-leafle-pat-scope":"read","description":"**Access: read token.**\n\nReturns your readers, reading queue, latest conversation and recovery state. Without a usable credential, returns `user: null` (200). An invalid or unavailable `reader_id` falls back to self."}},"/api/chat":{"post":{"tags":["chat"],"summary":"Talk to the librarian","operationId":"chat_api_chat_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChatRequest"}}},"required":true},"responses":{"200":{"description":"Server-sent events: each data value is one JSON frame matching a schema below, followed by a blank line. Lines starting with ':' are keepalives. The stream ends with a done or error frame (enrichment uses done: true).","content":{"text/event-stream":{"schema":{"anyOf":[{"$ref":"#/components/schemas/StatusFrame"},{"$ref":"#/components/schemas/ToolFrame"},{"$ref":"#/components/schemas/TokenFrame"},{"$ref":"#/components/schemas/DiscardTokensFrame"},{"$ref":"#/components/schemas/PickPreviewFrame"},{"$ref":"#/components/schemas/CandidateFrame"},{"$ref":"#/components/schemas/RecommendationFrame"},{"$ref":"#/components/schemas/SelectionProgressFrame"},{"$ref":"#/components/schemas/DoneFrame"},{"$ref":"#/components/schemas/ChatDoneFrame"},{"$ref":"#/components/schemas/HandoffDoneFrame"},{"$ref":"#/components/schemas/ErrorFrame"}]}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"Missing or unusable credential.","content":{"application/json":{"schema":{"type":"object","required":["detail"],"properties":{"detail":{"type":"string"}}}}}},"403":{"description":"Insufficient token scope or the reader is outside your account.","content":{"application/json":{"schema":{"type":"object","required":["detail"],"properties":{"detail":{"type":"string"}}}}}},"400":{"description":"Invalid input; see detail for the reason.","content":{"application/json":{"schema":{"type":"object","required":["detail"],"properties":{"detail":{"type":"string"}}}}}},"409":{"description":"Conflicting state: a turn is running, the book is already read, or the library offer has been settled.","content":{"application/json":{"schema":{"type":"object","required":["detail"],"properties":{"detail":{"type":"string"}}}}}},"429":{"description":"Request or usage limit reached. Wait before retrying.","content":{"application/json":{"schema":{"type":"object","required":["detail"],"properties":{"detail":{"type":"string"}}}}}},"503":{"description":"Capacity is unavailable or the service is restarting. Honour Retry-After when present.","content":{"application/json":{"schema":{"type":"object","required":["detail"],"properties":{"detail":{"type":"string"}}}}},"headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait during a restart."}}}},"security":[{"PersonalAccessToken":[]}],"x-leafle-pat-scope":"write","description":"**Access: write token.**\n\nPost a new message, or `message: null` to continue after discovery. Returns an SSE stream; see **Stream a conversation** above and the response frame schemas below. Only one turn per reader can run at a time (409). A disconnect may leave the turn running; recover through `/api/me` before retrying."}}},"components":{"schemas":{"AlsoBook":{"additionalProperties":true,"description":"A real, on-brief book the library doesn't hold — the buy/request suggestion.\nLegacy saved sets carry ``amazon_url`` instead of ``buy_url``; both are declared\nso neither is stripped from a stored set.","properties":{"title":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Title"},"author":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Author"},"why_short":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Why Short"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"buy_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Buy Url"},"amazon_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Amazon Url"},"isbn":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Isbn"},"pages":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Pages"},"year":{"anyOf":[{"type":"integer"},{"type":"string"},{"type":"null"}],"title":"Year"},"cover_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cover Url"},"review_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Review Url"}},"title":"AlsoBook","type":"object"},"Body_import_shelf_api_import_post":{"properties":{"file":{"type":"string","contentMediaType":"application/octet-stream","title":"File"}},"type":"object","required":["file"],"title":"Body_import_shelf_api_import_post"},"Body_photo_auto_api_photo_post":{"properties":{"file":{"type":"string","contentMediaType":"application/octet-stream","title":"File"}},"type":"object","required":["file"],"title":"Body_photo_auto_api_photo_post"},"Body_photo_cover_api_photo_cover_post":{"properties":{"file":{"type":"string","contentMediaType":"application/octet-stream","title":"File"}},"type":"object","required":["file"],"title":"Body_photo_cover_api_photo_cover_post"},"Body_photo_shelf_api_photo_shelf_post":{"properties":{"file":{"type":"string","contentMediaType":"application/octet-stream","title":"File"}},"type":"object","required":["file"],"title":"Body_photo_shelf_api_photo_shelf_post"},"Book":{"properties":{"id":{"type":"integer","title":"Id"},"title":{"type":"string","title":"Title"},"author":{"type":"string","title":"Author"},"status":{"type":"string","enum":["reading","read","owned","to_read","dnf"],"title":"Status"},"rating":{"anyOf":[{"type":"string","enum":["loved","liked"]},{"type":"null"}],"title":"Rating"},"source":{"type":"string","title":"Source"},"reaction":{"type":"string","title":"Reaction"},"cover_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cover Url"},"queue_position":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Queue Position"},"pick_why":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Pick Why"},"kind":{"type":"string","enum":["book","series"],"title":"Kind","default":"book"},"series":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Series"}},"type":"object","required":["id","title","author","status","rating","source","reaction","cover_url"],"title":"Book","description":"One reading-ledger record. Rating and cover_url are present even when null."},"BookNoteIn":{"properties":{"note":{"type":"string","maxLength":2000,"title":"Note","default":""},"reader_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Reader Id"}},"type":"object","title":"BookNoteIn"},"BookRatingIn":{"properties":{"rating":{"type":"string","enum":["loved","liked","none"],"title":"Rating"},"reader_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Reader Id"}},"type":"object","required":["rating"],"title":"BookRatingIn"},"BookRef":{"description":"A bare book reference — portrait keystones/reading-lately, taste-card loves,\nimport DNFs, gift claims. ``author`` may be ``\"\"`` but the key is always sent.","properties":{"title":{"title":"Title","type":"string"},"author":{"title":"Author","type":"string"}},"required":["title","author"],"title":"BookRef","type":"object"},"BookStatusIn":{"properties":{"status":{"type":"string","enum":["reading","read","owned","to_read","dnf"],"title":"Status"},"reader_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Reader Id"}},"type":"object","required":["status"],"title":"BookStatusIn"},"BooksPage":{"properties":{"books":{"items":{"$ref":"#/components/schemas/Book"},"type":"array","title":"Books"},"total":{"type":"integer","title":"Total"},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor"}},"type":"object","required":["books","total","next_cursor"],"title":"BooksPage"},"ChatRequest":{"properties":{"resume_handoff":{"type":"boolean","title":"Resume Handoff","default":false},"message":{"anyOf":[{"type":"string","maxLength":50000},{"type":"null"}],"title":"Message"},"lead_in":{"anyOf":[{"type":"string","maxLength":50000},{"type":"null"}],"title":"Lead In"},"reader_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Reader Id"}},"type":"object","title":"ChatRequest"},"Dislike":{"properties":{"title":{"type":"string","title":"Title"},"author":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Author"},"reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Reason"}},"type":"object","required":["title","author","reason"],"title":"Dislike"},"DislikeIn":{"properties":{"title":{"type":"string","title":"Title"},"author":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Author"},"reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Reason"},"reader_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Reader Id"}},"type":"object","required":["title"],"title":"DislikeIn"},"DislikesList":{"properties":{"dislikes":{"items":{"$ref":"#/components/schemas/Dislike"},"type":"array","title":"Dislikes"}},"type":"object","required":["dislikes"],"title":"DislikesList"},"DismissOfferIn":{"properties":{"reader_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Reader Id"},"offer_id":{"type":"string","title":"Offer Id"}},"type":"object","required":["offer_id"],"title":"DismissOfferIn"},"GiftAskBody":{"properties":{"occasion":{"type":"string","maxLength":60,"title":"Occasion","default":""},"budget":{"type":"string","maxLength":30,"title":"Budget","default":""},"format":{"type":"string","maxLength":20,"title":"Format","default":""},"vibe":{"type":"string","maxLength":200,"title":"Vibe","default":""}},"type":"object","title":"GiftAskBody"},"GiftCard":{"properties":{"summary":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Summary"},"themes_and_moods":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Themes And Moods"},"favourite_authors":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Favourite Authors"},"books_they_love":{"anyOf":[{"items":{"$ref":"#/components/schemas/BookRef"},"type":"array"},{"type":"null"}],"title":"Books They Love"},"format_preference":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Format Preference"},"reading_stage":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Reading Stage"},"language":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Language"}},"additionalProperties":true,"type":"object","title":"GiftCard","description":"The reader-approved taste card — exactly the `gifts.sanitise_card` whitelist.\nStored frozen per link, so extra/legacy keys pass through and absent ones stay\nabsent; a fresh card always carries all seven."},"GiftClaimBody":{"properties":{"title":{"type":"string","maxLength":200,"minLength":1,"title":"Title"},"author":{"type":"string","maxLength":120,"title":"Author","default":""},"email":{"type":"string","maxLength":200,"title":"Email","default":""},"undo":{"type":"boolean","title":"Undo","default":false}},"type":"object","required":["title"],"title":"GiftClaimBody"},"GiftClaimResult":{"properties":{"ok":{"type":"boolean","title":"Ok"},"claimed":{"items":{"$ref":"#/components/schemas/BookRef"},"type":"array","title":"Claimed"}},"type":"object","required":["ok","claimed"],"title":"GiftClaimResult","description":"POST /api/gift/{token}/claim — ack + the refreshed claim list."},"GiftPage":{"properties":{"label":{"type":"string","title":"Label"},"card":{"$ref":"#/components/schemas/GiftCard"},"claimed":{"items":{"$ref":"#/components/schemas/BookRef"},"type":"array","title":"Claimed"},"picks":{"items":{"$ref":"#/components/schemas/GiftPick"},"type":"array","title":"Picks"}},"type":"object","required":["label","card","claimed","picks"],"title":"GiftPage","description":"GET /api/gift/{token} — the giver's landing payload (public; token-credential)."},"GiftPick":{"properties":{"title":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Title"},"author":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Author"},"why_this_gift":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Why This Gift"},"buy_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Buy Url"},"isbn":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Isbn"},"year":{"anyOf":[{"type":"integer"},{"type":"string"},{"type":"null"}],"title":"Year"},"cover_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cover Url"},"review_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Review Url"}},"additionalProperties":true,"type":"object","title":"GiftPick","description":"One gift suggestion (`gifts.gift_turn` output, also read back from the stored\npool). Enrichment (isbn/year/cover) is attached only when grounding had it;\n``review_url`` (Goodreads) whenever the pick has a title."},"GiftRecommendation":{"properties":{"picks":{"items":{"$ref":"#/components/schemas/GiftPick"},"type":"array","title":"Picks"},"label":{"type":"string","title":"Label"},"detail":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Detail"}},"type":"object","required":["picks","label"],"title":"GiftRecommendation","description":"One generation of gift ideas. A detail field reports failure after the response started; inspect it even on HTTP 200. It is absent on success."},"HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"HardcoverConnection":{"properties":{"connected":{"type":"boolean","title":"Connected"},"username":{"type":"string","title":"Username","default":""},"push_enabled":{"type":"boolean","title":"Push Enabled","default":false},"needs_reconnect":{"type":"boolean","title":"Needs Reconnect","default":false},"connected_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Connected At"},"last_sync_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Sync At"},"last_sync_error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Sync Error"},"books_synced":{"type":"integer","title":"Books Synced","default":0},"sync_frequency":{"type":"string","title":"Sync Frequency","default":"daily"},"next_sync_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Sync At"},"account_url":{"type":"string","title":"Account Url"},"available":{"type":"boolean","title":"Available","default":false}},"type":"object","required":["connected","account_url"],"title":"HardcoverConnection","description":"GET/PATCH/DELETE /api/hardcover — the reader's Hardcover connection.\n\nNever carries a token. Connecting is not here at all: it is a browser redirect\nthrough Hardcover's approval screen (`/api/hardcover/authorize`), not something a\nJSON body can express."},"HardcoverSyncResult":{"properties":{"ok":{"type":"boolean","title":"Ok"},"reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Reason"},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"},"books_seen":{"type":"integer","title":"Books Seen","default":0},"new_books":{"type":"integer","title":"New Books","default":0},"added":{"type":"integer","title":"Added","default":0},"updated":{"type":"integer","title":"Updated","default":0},"removed":{"type":"integer","title":"Removed","default":0},"unmatched":{"type":"integer","title":"Unmatched","default":0}},"type":"object","required":["ok"],"title":"HardcoverSyncResult","description":"POST /api/hardcover/sync — what one sync run did.\n\n`unmatched` is reported rather than hidden: a queued book Hardcover has no record\nof is a real outcome the reader may want to know about, and silently dropping it\nwould look like the sync had worked."},"ImportResult":{"properties":{"source":{"type":"string","enum":["goodreads","storygraph"],"title":"Source"},"total_read":{"type":"integer","title":"Total Read"},"top_rated":{"items":{"$ref":"#/components/schemas/ImportTopRated"},"type":"array","title":"Top Rated"},"did_not_finish":{"items":{"$ref":"#/components/schemas/BookRef"},"type":"array","title":"Did Not Finish"},"shelves":{"items":{"type":"string"},"type":"array","title":"Shelves"},"greeting":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Greeting"}},"type":"object","required":["source","total_read","top_rated","did_not_finish","shelves","greeting"],"title":"ImportResult","description":"POST /api/import — the summary + greeting (the full book list stays server-side)."},"ImportTopRated":{"properties":{"title":{"type":"string","title":"Title"},"author":{"type":"string","title":"Author"},"rating":{"anyOf":[{"type":"integer"},{"type":"number"},{"type":"null"}],"title":"Rating"}},"type":"object","required":["title","author","rating"],"title":"ImportTopRated"},"LatestSession":{"description":"The latest conversation tail and recommendation set. from_your_shelf is omitted when the set has no suggestion from your own shelf. Optional set_id is the opaque saved-set identifier; explainable_books is for web-session deferred writing.","properties":{"set_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Set Id"},"explainable_books":{"anyOf":[{"items":{"$ref":"#/components/schemas/BookRef"},"type":"array"},{"type":"null"}],"title":"Explainable Books"},"messages":{"items":{"$ref":"#/components/schemas/TranscriptMessage"},"title":"Messages","type":"array"},"picks":{"items":{"$ref":"#/components/schemas/Pick"},"title":"Picks","type":"array"},"reserve":{"items":{"$ref":"#/components/schemas/Pick"},"title":"Reserve","type":"array"},"also_worth_a_look":{"items":{"$ref":"#/components/schemas/AlsoBook"},"title":"Also Worth A Look","type":"array"},"source":{"default":"library","enum":["library","buy","shelf"],"title":"Source","type":"string"},"from_your_shelf":{"anyOf":[{"$ref":"#/components/schemas/ShelfPick"},{"type":"null"}]}},"required":["messages","picks","reserve","also_worth_a_look"],"title":"LatestSession","type":"object"},"LibraryPlace":{"properties":{"id":{"title":"Id","type":"string"},"service_code":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Service Code"},"service":{"title":"Service","type":"string"},"name":{"title":"Name","type":"string"},"branch":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Branch"},"country":{"title":"Country","type":"string"},"url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Url"},"lat":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Lat"},"lng":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Lng"},"covered":{"title":"Covered","type":"boolean"},"kind":{"title":"Kind","type":"string"},"distance_km":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Distance Km"}},"required":["id","service_code","service","name","branch","country","url","lat","lng","covered","kind"],"title":"LibraryPlace","type":"object"},"LibrarySearch":{"properties":{"offer_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Offer Id"},"query":{"title":"Query","type":"string"},"country_hint":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Country Hint"},"results":{"items":{"$ref":"#/components/schemas/LibraryPlace"},"title":"Results","type":"array"},"countries":{"items":{"type":"string"},"title":"Countries","type":"array"},"message":{"default":"","title":"Message","type":"string"}},"required":["query","country_hint","results"],"title":"LibrarySearch","type":"object"},"Me":{"properties":{"user":{"$ref":"#/components/schemas/User"},"readers":{"items":{"$ref":"#/components/schemas/ReaderView"},"type":"array","title":"Readers"},"active_reader_id":{"type":"integer","title":"Active Reader Id"},"has_profile":{"type":"boolean","title":"Has Profile"},"chat_inflight":{"type":"boolean","title":"Chat Inflight","default":false},"discovery_handoff_pending":{"type":"boolean","title":"Discovery Handoff Pending","default":false},"portrait_ready":{"type":"boolean","title":"Portrait Ready","default":false},"portrait_version":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Portrait Version"},"latest_session":{"anyOf":[{"$ref":"#/components/schemas/LatestSession"},{"type":"null"}]},"planned_reads":{"items":{"$ref":"#/components/schemas/PlannedRead"},"type":"array","title":"Planned Reads"},"planned_greeting":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Planned Greeting"},"notices":{"additionalProperties":{"type":"string"},"type":"object","title":"Notices"},"queue":{"items":{"$ref":"#/components/schemas/QueuedBook"},"type":"array","title":"Queue","default":[]}},"type":"object","required":["user","readers","active_reader_id","has_profile","latest_session","planned_reads","planned_greeting"],"title":"Me","description":"Account and reader restore payload returned when an account is authenticated."},"NearbyIn":{"properties":{"lat":{"type":"number","maximum":90.0,"minimum":-90.0,"title":"Lat"},"lng":{"type":"number","maximum":180.0,"minimum":-180.0,"title":"Lng"},"country":{"anyOf":[{"type":"string","maxLength":2},{"type":"null"}],"title":"Country"}},"type":"object","required":["lat","lng"],"title":"NearbyIn"},"Ok":{"description":"The mutation acknowledgement: ``{\"ok\": true}``.","properties":{"ok":{"title":"Ok","type":"boolean"}},"required":["ok"],"title":"Ok","type":"object"},"PhotoBook":{"properties":{"title":{"type":"string","title":"Title","default":""},"author":{"type":"string","title":"Author","default":""},"isbn":{"type":"string","title":"Isbn","default":""},"cover_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cover Url"},"year":{"anyOf":[{"type":"integer"},{"type":"string"},{"type":"null"}],"title":"Year"},"confidence":{"type":"string","title":"Confidence","default":"low"}},"type":"object","title":"PhotoBook","description":"A grounded book read off a photo (`vision._ground_one`): every key is present\non the live path (``cover_url`` null when unresolved, ``year`` ``\"\"`` when\nunknown); defaults only cushion stubbed/legacy producers."},"PhotoCoverResult":{"properties":{"books":{"items":{"$ref":"#/components/schemas/PhotoBook"},"type":"array","title":"Books"}},"type":"object","required":["books"],"title":"PhotoCoverResult","description":"POST /api/photo/cover — grounded candidates for the read+react confirm."},"PhotoCoverScan":{"properties":{"mode":{"type":"string","const":"cover","title":"Mode"},"books":{"items":{"$ref":"#/components/schemas/PhotoBook"},"type":"array","title":"Books"}},"type":"object","required":["mode","books"],"title":"PhotoCoverScan"},"PhotoNoneScan":{"properties":{"mode":{"type":"string","const":"none","title":"Mode"},"books":{"items":{"$ref":"#/components/schemas/PhotoBook"},"type":"array","title":"Books"}},"type":"object","required":["mode","books"],"title":"PhotoNoneScan"},"PhotoShelfResult":{"properties":{"recorded":{"type":"integer","title":"Recorded"},"books":{"items":{"$ref":"#/components/schemas/PhotoBook"},"type":"array","title":"Books"},"author_runs":{"items":{"type":"string"},"type":"array","title":"Author Runs"}},"type":"object","required":["recorded","books","author_runs"],"title":"PhotoShelfResult","description":"POST /api/photo/shelf — what was recorded as owned inventory."},"PhotoShelfScan":{"properties":{"mode":{"type":"string","const":"shelf","title":"Mode"},"recorded":{"type":"integer","title":"Recorded"},"message":{"type":"string","title":"Message"},"books":{"items":{"$ref":"#/components/schemas/PhotoBook"},"type":"array","title":"Books"},"strip":{"items":{"$ref":"#/components/schemas/ShelfStripBook"},"type":"array","title":"Strip"},"author_runs":{"items":{"type":"string"},"type":"array","title":"Author Runs"}},"type":"object","required":["mode","recorded","message","books","strip","author_runs"],"title":"PhotoShelfScan"},"Pick":{"additionalProperties":true,"description":"A recommended book. Library picks may include branch/status/link; buying picks include buy_url; own-shelf picks include owned. Enrichment fields are omitted when unavailable. Historical sets may contain additional fields.","properties":{"title":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Title"},"author":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Author"},"why_for_this_reader":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Why For This Reader"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"isbn":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Isbn"},"pages":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Pages"},"year":{"anyOf":[{"type":"integer"},{"type":"string"},{"type":"null"}],"title":"Year"},"cover_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cover Url"},"review_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Review Url"},"branch":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Branch"},"status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status"},"was_borrowable":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Was Borrowable"},"was_borrowable_on":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Was Borrowable On"},"link":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Link"},"libby_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Libby Url"},"buy_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Buy Url"},"owned":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Owned"}},"title":"Pick","type":"object"},"PlanNextIn":{"properties":{"title":{"type":"string","title":"Title"},"author":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Author"},"reader_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Reader Id"}},"type":"object","required":["title"],"title":"PlanNextIn"},"PlannedRead":{"description":"The head of the to-read queue in the legacy next-read shape.","properties":{"title":{"title":"Title","type":"string"},"author":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Author"},"asked":{"title":"Asked","type":"boolean"}},"required":["title","author","asked"],"title":"PlannedRead","type":"object"},"Portrait":{"properties":{"ready":{"type":"boolean","title":"Ready"},"prose":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Prose"},"thesis":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Thesis"},"threads":{"anyOf":[{"items":{"$ref":"#/components/schemas/PortraitThread"},"type":"array"},{"type":"null"}],"title":"Threads"},"themes_and_moods":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Themes And Moods"},"favourite_authors":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Favourite Authors"},"keystones":{"anyOf":[{"items":{"$ref":"#/components/schemas/BookRef"},"type":"array"},{"type":"null"}],"title":"Keystones"},"reading_lately":{"anyOf":[{"items":{"$ref":"#/components/schemas/BookRef"},"type":"array"},{"type":"null"}],"title":"Reading Lately"},"total_books":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Total Books"},"illustration":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Illustration"},"refreshing":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Refreshing"}},"type":"object","required":["ready"],"title":"Portrait","description":"GET /api/portrait and POST /api/portrait/refine. ``{\"ready\": false}`` until the\nreader has a real taste model; when ready, every field below is present (the\nbuilder fills them all), so the omit-None base only trims the not-ready shape."},"PortraitStatus":{"properties":{"ready":{"type":"boolean","title":"Ready"},"version":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Version"}},"type":"object","required":["ready","version"],"title":"PortraitStatus","description":"Readiness and an opaque portrait version. Compare it; do not parse it."},"PortraitThread":{"description":"One weighted \"Themes & moods\" thread; ``weight`` is \"strong\" or a lighter grade.","properties":{"id":{"title":"Id","type":"string"},"label":{"title":"Label","type":"string"},"weight":{"title":"Weight","type":"string"}},"required":["id","label","weight"],"title":"PortraitThread","type":"object"},"PreferencesRequest":{"properties":{"push_enabled":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Push Enabled"},"sync_frequency":{"anyOf":[{"type":"string","enum":["manual","daily","weekly"]},{"type":"null"}],"title":"Sync Frequency"}},"type":"object","title":"PreferencesRequest","description":"Both settings are optional: sending one must not reset the other."},"QueueAvailability":{"properties":{"borrower":{"type":"boolean","title":"Borrower"},"digital":{"type":"boolean","title":"Digital"},"acquisition_preference":{"type":"string","enum":["borrow","buy"],"title":"Acquisition Preference"},"capped":{"type":"boolean","title":"Capped"},"books":{"items":{"$ref":"#/components/schemas/QueueBookAvailability"},"type":"array","title":"Books"}},"type":"object","required":["borrower","digital","acquisition_preference","capped","books"],"title":"QueueAvailability","description":"GET/POST /api/books/availability: routes for the to-read queue. ``borrower``\nis service connectivity (the profile's library resolves to a live connector),\nnot borrowing history; when false, ``books`` carry only search/buy links.\n``capped`` is true when the queue was longer than the per-request check budget\n— the missing tail is unknown, not unavailable."},"QueueBookAvailability":{"properties":{"book_id":{"type":"integer","title":"Book Id"},"status":{"type":"string","enum":["available","on_loan","held","unavailable","unknown"],"title":"Status"},"branch":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Branch"},"was_borrowable":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Was Borrowable"},"was_borrowable_on":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Was Borrowable On"},"link":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Link"},"libby_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Libby Url"},"buy_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Buy Url"},"review_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Review Url"},"checked_at":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Checked At"}},"type":"object","required":["book_id","status","branch","was_borrowable","was_borrowable_on","link","libby_url","buy_url","review_url","checked_at"],"title":"QueueBookAvailability","description":"One queued book's routes out of the queue. Only facts a connector actually\nreturned: ``status`` is `unknown` when the check failed or never ran (never an\nabsence claim), and ``checked_at`` (epoch seconds of the live read) is null\nthen too. ``link`` is the record/catalogue route; ``libby_url`` only when a\ncopy is borrowable right now; ``buy_url`` is the server-built affiliate search\nlink; ``review_url`` the book's Goodreads page (null only for a title-less row).\n``was_borrowable`` and its date ride beside a `held` status where the library\nreported one (see `Pick`)."},"QueueChanged":{"properties":{"ok":{"type":"boolean","title":"Ok"},"id":{"type":"integer","title":"Id"},"position":{"type":"integer","title":"Position"}},"type":"object","required":["ok","id","position"],"title":"QueueChanged","description":"POST /api/books/to-read and /api/books/{id}/queue-position: where the book\nnow sits in the to-read queue."},"QueuePositionIn":{"properties":{"position":{"type":"integer","minimum":1.0,"title":"Position"},"reader_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Reader Id"}},"type":"object","required":["position"],"title":"QueuePositionIn"},"QueueRecheckIn":{"properties":{"book_id":{"type":"integer","title":"Book Id"},"reader_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Reader Id"}},"type":"object","required":["book_id"],"title":"QueueRecheckIn"},"QueuedBook":{"description":"One to-read queue entry as /api/me summarises it (enough for the picks\ndrawer's \"Nth in your to-read list\" state; the full rows live on /api/books).","properties":{"id":{"title":"Id","type":"integer"},"title":{"title":"Title","type":"string"},"author":{"title":"Author","type":"string"},"position":{"title":"Position","type":"integer"}},"required":["id","title","author","position"],"title":"QueuedBook","type":"object"},"ReadIn":{"properties":{"title":{"type":"string","title":"Title"},"author":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Author"},"reaction":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Reaction"},"rating":{"anyOf":[{"type":"string","enum":["loved","liked"]},{"type":"null"}],"title":"Rating"},"reader_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Reader Id"}},"type":"object","required":["title"],"title":"ReadIn"},"ReadRef":{"properties":{"title":{"type":"string","title":"Title"},"author":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Author"}},"type":"object","required":["title","author"],"title":"ReadRef","description":"The title and author of a book recorded as read."},"ReaderView":{"description":"An account's self or child reader, with the ID to use for reader selection.","properties":{"id":{"title":"Id","type":"integer"},"kind":{"enum":["self","kid"],"title":"Kind","type":"string"},"name":{"title":"Name","type":"string"},"birth_year":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Birth Year"},"age":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Age"},"has_profile":{"title":"Has Profile","type":"boolean"}},"required":["id","kind","name","birth_year","age","has_profile"],"title":"ReaderView","type":"object"},"ReadsList":{"properties":{"reads":{"items":{"$ref":"#/components/schemas/ReadRef"},"type":"array","title":"Reads"}},"type":"object","required":["reads"],"title":"ReadsList"},"RecSet":{"properties":{"id":{"type":"integer","title":"Id"},"created_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Created At"},"library_authority":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Library Authority"},"source":{"type":"string","enum":["library","buy","shelf"],"title":"Source"},"note":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Note"},"picks":{"items":{"$ref":"#/components/schemas/Pick"},"type":"array","title":"Picks"},"reserve":{"items":{"$ref":"#/components/schemas/Pick"},"type":"array","title":"Reserve"},"also_worth_a_look":{"items":{"$ref":"#/components/schemas/AlsoBook"},"type":"array","title":"Also Worth A Look"},"from_your_shelf":{"anyOf":[{"$ref":"#/components/schemas/ShelfPick"},{"type":"null"}]}},"type":"object","required":["id","created_at","library_authority","source","note","picks","reserve","also_worth_a_look"],"title":"RecSet","description":"A saved recommendation set. Metadata is present, null when unknown. from_your_shelf is omitted when no own-shelf suggestion was saved."},"RecSetIndex":{"properties":{"id":{"type":"integer","title":"Id"},"created_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Created At"},"library_authority":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Library Authority"},"source":{"type":"string","enum":["library","buy","shelf"],"title":"Source"},"note":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Note"},"pick_count":{"type":"integer","title":"Pick Count"}},"type":"object","required":["id","created_at","library_authority","source","note","pick_count"],"title":"RecSetIndex","description":"Set metadata and pick count. Fetch /api/sets/{set_id} to retrieve the actual books."},"SaveLibrariesIn":{"properties":{"reader_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Reader Id"},"offer_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Offer Id"},"ids":{"anyOf":[{"items":{"type":"string"},"type":"array","maxItems":8},{"type":"null"}],"title":"Ids","description":"Ordered library IDs; [] declines library use; null clears the selection."}},"type":"object","required":["ids"],"title":"SaveLibrariesIn"},"SavedLibraries":{"properties":{"libraries":{"items":{"$ref":"#/components/schemas/LibraryPlace"},"type":"array","title":"Libraries"},"explicit":{"type":"boolean","title":"Explicit"},"pending_offer_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Pending Offer Id"}},"type":"object","required":["libraries","explicit"],"title":"SavedLibraries"},"SetsIndexPage":{"properties":{"sets":{"items":{"$ref":"#/components/schemas/RecSetIndex"},"type":"array","title":"Sets"},"offset":{"type":"integer","title":"Offset"},"limit":{"type":"integer","title":"Limit"}},"type":"object","required":["sets","offset","limit"],"title":"SetsIndexPage","description":"`GET /api/sets` — the lightweight listing (see `RecSetIndex`)."},"ShelfPick":{"additionalProperties":true,"description":"A suggestion from your own shelf, saved with the set. cover_url may be null.","properties":{"title":{"title":"Title","type":"string"},"author":{"title":"Author","type":"string"},"cover_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cover Url"},"why":{"title":"Why","type":"string"}},"required":["title","author","cover_url","why"],"title":"ShelfPick","type":"object"},"ShelfReadsIn":{"properties":{"answer":{"type":"string","maxLength":4000,"title":"Answer"},"books":{"items":{"$ref":"#/components/schemas/_ShelfBookRef"},"type":"array","maxItems":200,"title":"Books"},"reader_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Reader Id"}},"type":"object","required":["answer"],"title":"ShelfReadsIn"},"ShelfReadsResult":{"properties":{"upgraded":{"type":"integer","title":"Upgraded"},"titles":{"items":{"type":"string"},"type":"array","title":"Titles"}},"type":"object","required":["upgraded","titles"],"title":"ShelfReadsResult","description":"POST /api/photo/shelf/reads — which owned books were upgraded to reads."},"ShelfStripBook":{"properties":{"title":{"type":"string","title":"Title","default":""},"author":{"type":"string","title":"Author","default":""},"cover_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cover Url"}},"type":"object","title":"ShelfStripBook","description":"The slim cover-strip entry stored alongside the shelf-scan reply."},"ToReadIn":{"properties":{"title":{"type":"string","title":"Title"},"author":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Author"},"why":{"anyOf":[{"type":"string","maxLength":2000},{"type":"null"}],"title":"Why"},"source":{"type":"string","enum":["picks","daily-find"],"title":"Source","default":"picks"},"reader_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Reader Id"}},"type":"object","required":["title"],"title":"ToReadIn"},"TranscriptMessage":{"additionalProperties":true,"description":"A conversation row. Display user/assistant rows with nonempty string content. Other rows can carry structured tool context; older rows may have additional keys.","properties":{"library_search":{"anyOf":[{"$ref":"#/components/schemas/LibrarySearch"},{"type":"null"}]}},"title":"TranscriptMessage","type":"object"},"User":{"properties":{"id":{"type":"integer","title":"Id"},"email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Email"},"display_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Display Name"},"is_admin":{"type":"boolean","title":"Is Admin"},"can_invite":{"type":"boolean","title":"Can Invite"},"invites_remaining":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Invites Remaining"},"waitlist_count":{"type":"integer","title":"Waitlist Count"},"mcp_enabled":{"type":"boolean","title":"Mcp Enabled"},"features":{"items":{"type":"string"},"type":"array","title":"Features","default":[]}},"type":"object","required":["id","email","display_name","is_admin","can_invite","invites_remaining","waitlist_count","mcp_enabled"],"title":"User","description":"The signed-in account as /api/me presents it (presentation only — every\nallowance here is re-enforced server-side on the endpoints it gates)."},"ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"},"input":{"title":"Input"},"ctx":{"type":"object","title":"Context"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"},"_ShelfBookRef":{"properties":{"title":{"type":"string","title":"Title"},"author":{"type":"string","title":"Author","default":""}},"type":"object","required":["title"],"title":"_ShelfBookRef"},"CandidateFrame":{"additionalProperties":false,"description":"A candidate's current state, keyed by case-insensitive title and author. not_held means the library answered negatively; unchecked means no reliable answer. grounded means a verified book for a buying or own-shelf turn, with no stock claim.","properties":{"type":{"const":"candidate","default":"candidate","title":"Type","type":"string"},"title":{"title":"Title","type":"string"},"author":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Author"},"cover_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cover Url"},"state":{"enum":["grounded","checking","on_shelf","on_loan","held","not_held","unchecked"],"title":"State","type":"string"},"branch":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Branch"},"link":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Link"}},"required":["title","state"],"title":"CandidateFrame","type":"object"},"ChatDoneFrame":{"additionalProperties":false,"description":"A reply with routing support (chat, child-workflow discovery, or an eligible\nintroduction accompanying picks). ``action`` is present, null = no routing chip.","properties":{"type":{"const":"done","default":"done","title":"Type","type":"string"},"reply":{"title":"Reply","type":"string"},"messages":{"items":{"$ref":"#/components/schemas/TranscriptMessage"},"title":"Messages","type":"array"},"action":{"anyOf":[{"enum":["portrait","my_books","libraries","share","gift_link","add_child","import","photo","invite","sign_in"],"type":"string"},{"type":"null"}],"title":"Action"}},"required":["reply","messages","action"],"title":"ChatDoneFrame","type":"object"},"CoverageBranch":{"description":"A library point, or an unnamed uncovered grid cell with a count of libraries.","properties":{"name":{"title":"Name","type":"string"},"lat":{"title":"Lat","type":"number"},"lng":{"title":"Lng","type":"number"},"service_code":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Service Code"},"service":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Service"},"kind":{"title":"Kind","type":"string"},"country":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Country"},"count":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Count"}},"required":["name","lat","lng","service_code","service","kind","country"],"title":"CoverageBranch","type":"object"},"CoverageBranches":{"properties":{"branches":{"items":{"$ref":"#/components/schemas/CoverageBranch"},"title":"Branches","type":"array"}},"required":["branches"],"title":"CoverageBranches","type":"object"},"CoverageDirectory":{"description":"Counts distinguish covered services/branches from libraries whose location is known.","properties":{"totals":{"additionalProperties":{"type":"integer"},"title":"Totals","type":"object"},"countries":{"additionalProperties":{"additionalProperties":{"type":"integer"},"type":"object"},"title":"Countries","type":"object"},"services":{"items":{"$ref":"#/components/schemas/CoverageService"},"title":"Services","type":"array"},"mapbox_token":{"title":"Mapbox Token","type":"string"},"probed_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Probed At"}},"required":["totals","countries","services","mapbox_token","probed_at"],"title":"CoverageDirectory","type":"object"},"CoverageResolution":{"properties":{"query":{"title":"Query","type":"string"},"match":{"anyOf":[{"$ref":"#/components/schemas/CoverageService"},{"type":"null"}]},"did_you_mean":{"items":{"type":"string"},"title":"Did You Mean","type":"array"}},"required":["query","match","did_you_mean"],"title":"CoverageResolution","type":"object"},"CoverageService":{"properties":{"name":{"title":"Name","type":"string"},"country":{"title":"Country","type":"string"},"system":{"title":"System","type":"string"},"kind":{"title":"Kind","type":"string"},"url":{"title":"Url","type":"string"},"lat":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Lat"},"lng":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Lng"},"branch":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Branch"},"branch_lat":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Branch Lat"},"branch_lng":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Branch Lng"}},"required":["name","country","system","kind","url","lat","lng"],"title":"CoverageService","type":"object"},"DailyFind":{"description":"A published find with frozen editorial/evidence and request-local purchase links.","properties":{"date":{"title":"Date","type":"string"},"title":{"title":"Title","type":"string"},"author":{"title":"Author","type":"string"},"editorial_line":{"title":"Editorial Line","type":"string"},"page_blurb":{"title":"Page Blurb","type":"string"},"cover_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cover Url"},"panel_version":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Panel Version"},"checked_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Checked At"},"snapshot":{"additionalProperties":true,"title":"Snapshot","type":"object"},"review_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Review Url"},"buy_url":{"title":"Buy Url","type":"string"},"affiliate":{"title":"Affiliate","type":"boolean"}},"required":["date","title","author","editorial_line","page_blurb","cover_url","panel_version","checked_at","snapshot","review_url","buy_url","affiliate"],"title":"DailyFind","type":"object"},"DailyFindAvailability":{"description":"No library: library is null. Failed check: checked is false, counts are absent.\nA successful holdings-only check has a null copies_available_now, not zero.","properties":{"library":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Library"},"checked":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Checked"},"copies_available_now":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Copies Available Now"},"branch":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Branch"},"record_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Record Url"}},"required":["library"],"title":"DailyFindAvailability","type":"object"},"DiscardTokensFrame":{"additionalProperties":false,"description":"Drop any streamed tokens; a tool turn followed the preamble.","properties":{"type":{"const":"discard_tokens","default":"discard_tokens","title":"Type","type":"string"}},"title":"DiscardTokensFrame","type":"object"},"DoneFrame":{"additionalProperties":false,"description":"The terminal frame, bare variant: a recommendation's closing note, or a plain\ndiscovery reply. Carries neither ``action`` nor ``profile_ready`` — the variant\nsubclasses add exactly one each, so a frame can't mix them.","properties":{"type":{"const":"done","default":"done","title":"Type","type":"string"},"reply":{"title":"Reply","type":"string"},"messages":{"items":{"$ref":"#/components/schemas/TranscriptMessage"},"title":"Messages","type":"array"}},"required":["reply","messages"],"title":"DoneFrame","type":"object"},"ErrorFrame":{"additionalProperties":false,"description":"Terminal error.","properties":{"type":{"const":"error","default":"error","title":"Type","type":"string"},"detail":{"title":"Detail","type":"string"}},"required":["detail"],"title":"ErrorFrame","type":"object"},"Guest":{"description":"What a reader with no account has left: one recommendation set, then a capped\nconversation about it. Presentation only; the chat route enforces both.","properties":{"set_spent":{"title":"Set Spent","type":"boolean"},"followups_remaining":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Followups Remaining"},"expires_at":{"format":"date-time","title":"Expires At","type":"string"}},"required":["set_spent","followups_remaining","expires_at"],"title":"Guest","type":"object"},"GuestMe":{"description":"Browser guest restore payload, with no account and a limited chat allowance.","properties":{"user":{"title":"User","type":"null"},"readers":{"items":{"$ref":"#/components/schemas/ReaderView"},"title":"Readers","type":"array"},"active_reader_id":{"title":"Active Reader Id","type":"integer"},"has_profile":{"title":"Has Profile","type":"boolean"},"chat_inflight":{"default":false,"title":"Chat Inflight","type":"boolean"},"discovery_handoff_pending":{"default":false,"title":"Discovery Handoff Pending","type":"boolean"},"portrait_ready":{"default":false,"title":"Portrait Ready","type":"boolean"},"portrait_version":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Portrait Version"},"latest_session":{"anyOf":[{"$ref":"#/components/schemas/LatestSession"},{"type":"null"}]},"planned_reads":{"items":{"$ref":"#/components/schemas/PlannedRead"},"title":"Planned Reads","type":"array"},"planned_greeting":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Planned Greeting"},"notices":{"additionalProperties":{"type":"string"},"title":"Notices","type":"object"},"queue":{"default":[],"items":{"$ref":"#/components/schemas/QueuedBook"},"title":"Queue","type":"array"},"guest":{"$ref":"#/components/schemas/Guest"}},"required":["readers","active_reader_id","has_profile","latest_session","planned_reads","planned_greeting","guest"],"title":"GuestMe","type":"object"},"HandoffDoneFrame":{"additionalProperties":false,"description":"The discovery→pipeline handoff done: the client sees ``profile_ready`` and\nimmediately fires the continue request (`POST /api/chat {message: null}`).","properties":{"type":{"const":"done","default":"done","title":"Type","type":"string"},"reply":{"title":"Reply","type":"string"},"messages":{"items":{"$ref":"#/components/schemas/TranscriptMessage"},"title":"Messages","type":"array"},"profile_ready":{"const":true,"default":true,"title":"Profile Ready","type":"boolean"}},"required":["reply","messages"],"title":"HandoffDoneFrame","type":"object"},"PhaseCounts":{"additionalProperties":false,"description":"Progress counts known at this point in the turn. An absent count means unknown, not zero. unchecked includes checks that failed or could not finish in time.","properties":{"considered":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Considered"},"grounded":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Grounded"},"on_shelf":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"On Shelf"},"on_loan":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"On Loan"},"held":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Held"},"not_held":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Not Held"},"unchecked":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Unchecked"}},"title":"PhaseCounts","type":"object"},"PickPreviewFrame":{"additionalProperties":false,"description":"A confirmed-available book, streamed the moment its check lands mid-turn.\nCleared/replaced by the final `recommendation` frame.","properties":{"type":{"const":"pick_preview","default":"pick_preview","title":"Type","type":"string"},"pick":{"$ref":"#/components/schemas/PreviewPick"}},"required":["pick"],"title":"PickPreviewFrame","type":"object"},"PreviewPick":{"additionalProperties":false,"description":"The provisional card a `pick_preview` carries — display fields only (the why +\nfull enrichment arrive with the final `recommendation`); absent keys omitted.","properties":{"title":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Title"},"author":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Author"},"branch":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Branch"},"status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status"},"link":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Link"},"cover_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cover Url"}},"title":"PreviewPick","type":"object"},"RecommendationFrame":{"additionalProperties":false,"description":"The final set. from_your_shelf is present only when an own-shelf suggestion accompanies a library recommendation. Optional set_id is an opaque saved-set identifier. explainable_books lists identities eligible for web-session deferred writing.","properties":{"set_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Set Id"},"explainable_books":{"anyOf":[{"items":{"$ref":"#/components/schemas/BookRef"},"type":"array"},{"type":"null"}],"title":"Explainable Books"},"type":{"const":"recommendation","default":"recommendation","title":"Type","type":"string"},"picks":{"items":{"$ref":"#/components/schemas/Pick"},"title":"Picks","type":"array"},"reserve":{"items":{"$ref":"#/components/schemas/Pick"},"title":"Reserve","type":"array"},"also_worth_a_look":{"items":{"$ref":"#/components/schemas/AlsoBook"},"title":"Also Worth A Look","type":"array"},"source":{"enum":["library","buy","shelf"],"title":"Source","type":"string"},"from_your_shelf":{"anyOf":[{"$ref":"#/components/schemas/ShelfPick"},{"type":"null"}]}},"required":["picks","reserve","also_worth_a_look","source"],"title":"RecommendationFrame","type":"object"},"SelectionProgressFrame":{"additionalProperties":false,"description":"A transient replacement snapshot of settled picks, reserve, alternatives and disclosures while explanations finish. Only RecommendationFrame commits a set.","properties":{"type":{"const":"selection_progress","default":"selection_progress","title":"Type","type":"string"},"disclosures":{"items":{"type":"string"},"title":"Disclosures","type":"array"},"picks":{"items":{"$ref":"#/components/schemas/Pick"},"title":"Picks","type":"array"},"reserve":{"items":{"$ref":"#/components/schemas/Pick"},"title":"Reserve","type":"array"},"also_worth_a_look":{"items":{"$ref":"#/components/schemas/AlsoBook"},"title":"Also Worth A Look","type":"array"},"source":{"enum":["library","buy","shelf"],"title":"Source","type":"string"}},"required":["picks","reserve","also_worth_a_look","source"],"title":"SelectionProgressFrame","type":"object"},"ShareBook":{"additionalProperties":false,"description":"A book frozen into a shared portrait. cover_url may be null.","properties":{"title":{"title":"Title","type":"string"},"author":{"title":"Author","type":"string"},"cover_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cover Url"}},"required":["title","author","cover_url"],"title":"ShareBook","type":"object"},"ShareSnapshot":{"additionalProperties":false,"description":"A reader-approved portrait frozen when the link was created. Unshared sections are omitted. This describes newly created shares; older shares remain unchanged.","properties":{"name":{"title":"Name","type":"string"},"plate":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Plate"},"dingbats":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Dingbats"},"accent_hex":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Accent Hex"},"plate_w":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Plate W"},"plate_h":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Plate H"},"accent":{"title":"Accent","type":"string"},"thesis":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Thesis"},"prose":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Prose"},"threads":{"anyOf":[{"items":{"$ref":"#/components/schemas/PortraitThread"},"type":"array"},{"type":"null"}],"title":"Threads"},"favourite_authors":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Favourite Authors"},"keystones":{"anyOf":[{"items":{"$ref":"#/components/schemas/ShareBook"},"type":"array"},{"type":"null"}],"title":"Keystones"},"reading_lately":{"anyOf":[{"items":{"$ref":"#/components/schemas/ShareBook"},"type":"array"},{"type":"null"}],"title":"Reading Lately"}},"required":["name","accent"],"title":"ShareSnapshot","type":"object"},"StatusFrame":{"additionalProperties":false,"description":"Transient progress. label is sufficient for a basic client. Optional phase, counts, route and detail add context; ignore unknown values.","properties":{"type":{"const":"status","default":"status","title":"Type","type":"string"},"label":{"title":"Label","type":"string"},"notices":{"anyOf":[{"additionalProperties":{"type":"string"},"type":"object"},{"type":"null"}],"title":"Notices"},"phase":{"anyOf":[{"enum":["reading","choosing","verifying","screening","checking","widening","writing"],"type":"string"},{"type":"null"}],"title":"Phase"},"round":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Round"},"counts":{"anyOf":[{"$ref":"#/components/schemas/PhaseCounts"},{"type":"null"}]},"route":{"anyOf":[{"enum":["borrow","buy","shelf"],"type":"string"},{"type":"null"}],"title":"Route"},"detail":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Detail"}},"required":["label"],"title":"StatusFrame","type":"object"},"TokenFrame":{"additionalProperties":false,"description":"A streamed reply chunk (~80-char slices), text-only turns.","properties":{"type":{"const":"token","default":"token","title":"Type","type":"string"},"text":{"title":"Text","type":"string"}},"required":["text"],"title":"TokenFrame","type":"object"},"ToolFrame":{"additionalProperties":false,"description":"A tool call is running (discovery); rendered like `status`.","properties":{"type":{"const":"tool","default":"tool","title":"Type","type":"string"},"label":{"title":"Label","type":"string"}},"required":["label"],"title":"ToolFrame","type":"object"}},"securitySchemes":{"PersonalAccessToken":{"type":"http","scheme":"bearer","bearerFormat":"leafle_pat_…","description":"Create a token in Leafle's API access menu. Use read for reads or write for reads and mutations; each operation states its minimum scope. Enter the token alone; the client adds the Bearer prefix."}}},"tags":[{"name":"chat","description":"The discovery + recommendation conversation (SSE)."},{"name":"picks","description":"Saved recommendation sets, feedback and next-read choices."},{"name":"books","description":"The reading ledger (My Books), shelf import."},{"name":"portrait","description":"Reading portraits and frozen shared snapshots."},{"name":"gifts","description":"Gift ideas and coordination using a reader-approved signed link."},{"name":"account","description":"Account, readers and conversation recovery."},{"name":"coverage","description":"Library coverage and mapped branches."},{"name":"libraries","description":"Find libraries and save reader selections."},{"name":"finds","description":"Published daily finds and local availability."},{"name":"photo","description":"Book-cover and shelf-photo identification."}]}