See Library & Reading for what these operations do
in the UI. A book’s id is a slug derived from its title (deduplicated
with a hash suffix on collision), not a UUID.
GET /api/books
GETAny session
Lists every book in the library — this endpoint is not scoped to the
active profile; ratings, read, and progress on each record reflect
whichever profile last had them applied server-side. Use
PATCH /api/books/:id to read/write the active profile’s own state,
via its returned book object.
Response: { "books": BookRecord[] }
POST /api/books
POSTAny session
Uploads an EPUB. multipart/form-data, one field:
| Field | Type | Required | Notes |
|---|---|---|---|
file |
file | Yes | Must end in .epub |
Deduplicated by SHA-256 content hash — re-uploading identical bytes returns the existing book instead of creating a new one.
Response: 201 with { "book": BookRecord, "duplicate": false }, or
200 with { "book": BookRecord, "duplicate": true } if the file already
exists. 400 if the file isn’t .epub or its metadata can’t be parsed.
PATCH /api/books/:id
PATCHAny session
Updates the active profile’s reading state for one book — rating, read status, and reading position all live per-profile, not on the shared book record.
Request body — all fields optional:
| Field | Type | Notes |
|---|---|---|
rating |
integer 0–5 | 0 clears the rating |
read |
boolean | |
cfi |
string | EPUB CFI (reading position); also stamps lastReadAt |
progress |
number | Clamped to 0–100, rounded |
Response: { "book": Book } — the book merged with the active
profile’s state.
DELETE /api/books/:id
DELETEAdmin only
Deletes the book’s files from storage, removes it from the index, and clears every profile’s reading state for it.
Response: { "ok": true }
GET /api/books/:id/readers
GETAny session
Lists other profiles (excluding the active one) that have read, rated, or made progress on this book — powers the “Readers” panel on a shared install.
Response: { "readers": [{ "profileId": string, "name": string, "color": string, "read": boolean, "progress"?: number, "rating"?: number }] }
POST /api/books/:id/send-to-ereader
POSTAny session
Emails the book’s .epub to the active profile’s e-reader address. See
Send to E-Reader for the SMTP and
destination-address setup this depends on.
Response: { "ok": true, "sentTo": string }, or 400 if SMTP or the
profile’s e-reader email isn’t configured yet, 502 if sending fails.
GET /api/books/:id/metadata/candidates
GETAny session
Looks up metadata candidates on Open Library by the book’s ISBN, title, and author. How many candidates come back is set admin-side — see Settings.
Response: { "candidates": [...] }, or 404 if Open Library has no
match.
POST /api/books/:id/metadata
POSTAny session
Applies edited or looked-up metadata. Accepts either JSON or
multipart/form-data (use form data to upload a cover file directly).
Request body (JSON)
| Field | Type | Notes |
|---|---|---|
title |
string | |
author |
string | |
description |
string | |
coverUrl |
string | Fetched server-side and stored as the new cover |
Request body (form data) — same title/author/description
fields, plus a cover file field (must be an image) instead of
coverUrl.
Response: { "book": BookRecord }