Appearance
API Reference
Interactive API documentation generated from the OpenAPI spec.
API Version: 0.1.0
Whether this install still needs setting up
GET
/api/setup
Lets the sign-in page decide between offering sign-in and offering first-run setup. Public, because there is no account to authenticate with when the answer is yes. Returns a bare boolean on purpose — anything richer would be an unauthenticated window into who exists on this server. True both for a fresh install and for an upgraded one whose users were created by the auth cutover migration and therefore have no password yet.
Responses
Setup status
application/json
JSON "required": true
{
}
GET
/api/setup
Samples
Create or recover the first admin
POST
/api/setup
First-run bootstrap. Available only while nobody on this install can sign in with a password — every later account is created by an admin, and self-registration is disabled. On a fresh install it creates the first user with role admin. On an install upgraded from pre-Better-Auth Libris, where the cutover migration created users with no credential, it attaches the submitted email and password to an EXISTING user (the one already holding that email, else the oldest admin, else the oldest user promoted to admin) rather than creating a duplicate person. Returns 409 once any credential exists, so this is safe to leave mounted.
Request Body
application/json
JSON "email": "string", "password": "string", "name": "string"
{
}
Responses
The first admin credential was created
application/json
JSON "id": "string", "email": "string", "name": "string", "role": "string", "adopted": true
{
}
POST
/api/setup
Samples
Liveness probe
GET
/api/health/live
Answers 200 as soon as the process is serving HTTP. Performs no database, Redis or event-bus I/O, so it stays cheap at the frequency an orchestrator probes at and never fails because a dependency is down. Use this for container liveness probes; use GET /api/health for readiness and dependency status.
Responses
The process is up and serving requests
application/json
JSON "status": "string", "service": "string"
{
}
GET
/api/health/live
Samples
Health check (readiness)
GET
/api/health
Deep readiness check: verifies the database, Redis and the event bus, and answers 503 when any of them is degraded. Returns minimal status for unauthenticated requests; provide a valid API key for per-dependency detail. Costs one database round-trip and one Redis PING per call — probe GET /api/health/live instead for container liveness.
Responses
All systems healthy
application/json
JSON "status": "string", "service": "string", "checks": { "database": { "status": "string", "latencyMs": 0, "error": "string" }, "redis": { "status": "string", "latencyMs": 0, "error": "string" }, "eventBus": { "status": "string", "latencyMs": 0, "error": "string" } }
{
}
GET
/api/health
Samples
Get full settings page status
GET
/api/settings/status
Aggregate endpoint that returns health checks, job queue status, failed jobs, app settings, and all credential statuses in a single request. Non-admin users receive only their credential connection status; admin users receive the full diagnostics payload.
Responses
Aggregated settings status
application/json
JSON "health": { "status": "string", "checks": { "database": { "status": "string", "latencyMs": 0, "error": "string" }, "redis": { "status": "string", "latencyMs": 0, "error": "string" }, "eventBus": { "status": "string", "latencyMs": 0, "error": "string" } } }, "queues": { "additionalProperties": { "waiting": 0, "active": 0, "completed": 0, "failed": 0, "delayed": 0, "isPaused": true } }, "failedJobs": { "jobs": [ { "id": "string", "queueName": "string", "name": "string", "data": { "additionalProperties": "string" }, "error": "string", "failedAt": 0, "attemptsMade": 0, "maxAttempts": 0 } ], "total": 0 }, "settings": { "libraryPath": "string", "inboxPath": "string", "hardcoverMetadataEnabled": true, "hardcoverSyncEnabled": true }, "credentials": { "opds": { "configured": true, "service": "string", "username": "string", "createdAt": "string", "updatedAt": "string" }, "kosync": { "configured": true, "service": "string", "username": "string", "createdAt": "string", "updatedAt": "string" }, "hardcover": { "configured": true, "service": "string", "username": "string", "createdAt": "string", "updatedAt": "string" } }
{
}
GET
/api/settings/status
Samples
Get settings
GET
/api/settings
Return application settings. The response varies by the caller's role: libraryPath and inboxPath are host filesystem paths and are present only for administrators, absent for everyone else. The remaining fields are returned to every authenticated caller.
Responses
Current settings. Admin callers additionally receive libraryPath and inboxPath.
application/json
JSON "kosyncConfigured": true, "hardcoverMetadataEnabled": true, "hardcoverSyncEnabled": true, "libraryPath": "string", "inboxPath": "string"
{
}
GET
/api/settings
Samples
Update settings
PATCH
/api/settings
Update persistent application settings (Hardcover integration toggles). Library and inbox paths are configured via the LIBRIS_LIBRARY_PATH and LIBRIS_INBOX_PATH environment variables and cannot be changed at runtime.
Request Body
application/json
JSON "hardcoverMetadataEnabled": true, "hardcoverSyncEnabled": true
{
}
Responses
Settings updated
application/json
JSON "updated": [ "string" ]
{
}
PATCH
/api/settings
Samples
library
Operations
GET/api/libraryGET/api/library/syncGET/api/library/facetsGET/api/library/{id}PATCH/api/library/{id}GET/api/library/{id}/progressPOST/api/library/{id}/refetchPOST/api/library/{id}/reorganizePOST/api/library/{id}/apply-metadataDELETE/api/library/{id}/reading-statusPATCH/api/library/{id}/reading-statusGET/api/library/{id}/coverGET/api/library/{id}/download/{fileId}
List library books
GET
/api/library
Paginated list of organized books with optional search and filtering. The organized library is shared, so every caller sees every book together with its uploader's display label. uploader.id is an opaque per-install reference, never the uploader's user id; pass a value from GET /api/library/facets as uploaderId to filter. An unrecognised uploaderId returns an empty page.
Parameters
Query Parameters
page
Page number
Type
integer
Minimum
1Default
1limit
Items per page
Type
integer
Minimum
1Maximum
100Default
20author
Filter by author (partial match)
Type
string
Max Length
500Default
""genre
Filter by genre (exact, case-insensitive)
Type
string
Max Length
500Default
""language
Filter by language code (exact, case-insensitive)
Type
string
Max Length
500Default
""series
Filter by series name (exact match)
Type
string
Max Length
500Default
""uploaderId
Filter by uploader. Takes the opaque uploaders[].id reference from GET /api/library/facets, not a user id; an unrecognised value returns an empty page.
Type
string
Default
""q
Full-text search across title, author, and description with typo tolerance
Type
string
Max Length
500Default
""sort
Sort order for results
Type
string
Default
"title_asc"Responses
Paginated list of books with files
application/json
JSON "data": [ { "id": "string", "status": "string", "title": "string", "author": "string", "isbn10": "string", "isbn13": "string", "publisher": "string", "publishedYear": 0, "language": "string", "description": "string", "coverUrl": "string", "coverPath": "string", "pageCount": 0, "series": "string", "seriesIndex": 0, "genres": [ "string" ], "tags": [ "string" ], "hardcoverBookId": 0, "hardcoverEditionId": 0, "createdBy": "string", "possibleDuplicateOf": "string", "approvedAt": "string", "createdAt": "string", "updatedAt": "string", "files": [ { "id": "string", "format": "string", "originalName": "string", "fileSize": "string" } ], "uploader": { "id": "string", "label": "string" } } ], "pagination": { "page": 0, "limit": 0, "total": 0, "totalPages": 0 }
{
}
GET
/api/library
Samples
Bulk library sync feed
GET
/api/library/sync
Single paginated endpoint optimised for full-vault mirror clients and CLIs. Returns BookSyncRecord[] bundling each organised book's metadata + a per-book progress aggregate (max % across devices + derived reading status). Optional ?since=uploader.id is an opaque per-install reference, never the uploader's user id.
Parameters
Query Parameters
page
Page number
Type
integer
Minimum
1Default
1limit
Items per page
Type
integer
Minimum
1Maximum
100Default
20since
ISO 8601 timestamp. When set, only return books whose metadata or progress changed after this time. Pass serverTime from the previous successful response.
Type
string
Format
"date-time"Responses
Paginated sync records
application/json
JSON "data": [ { "id": "string", "status": "string", "title": "string", "author": "string", "isbn10": "string", "isbn13": "string", "publisher": "string", "publishedYear": 0, "language": "string", "description": "string", "coverUrl": "string", "coverPath": "string", "pageCount": 0, "series": "string", "seriesIndex": 0, "genres": [ "string" ], "tags": [ "string" ], "hardcoverBookId": 0, "hardcoverEditionId": 0, "createdBy": "string", "possibleDuplicateOf": "string", "approvedAt": "string", "createdAt": "string", "updatedAt": "string", "files": [ { "id": "string", "format": "string", "originalName": "string", "fileSize": "string" } ], "uploader": { "id": "string", "label": "string" }, "progress": { "percentage": 0, "status": "string", "lastDevice": "string", "lastTimestamp": 0, "startedAt": "string", "finishedAt": "string", "pausedAt": "string", "manuallySet": true, "externallySet": true } } ], "pagination": { "page": 0, "limit": 0, "total": 0, "totalPages": 0 }, "serverTime": "string"
{
}
GET
/api/library/sync
Samples
Get library filter facets
GET
/api/library/facets
Returns library filter values. The organized library is shared, so every caller receives every uploader who owns an organized book. Each uploader is identified by an opaque per-install reference plus a display label — never by user id. Pass the reference back as uploaderId on GET /api/library.
Responses
Distinct authors, genres, languages, series, and uploader values
application/json
JSON "authors": [ "string" ], "genres": [ "string" ], "languages": [ "string" ], "series": [ "string" ], "uploaders": [ { "id": "string", "label": "string" } ]
{
}
GET
/api/library/facets
Samples
Get library book
GET
/api/library/{id}
Retrieve a single organized book with its files. uploader.id is an opaque per-install reference, never the uploader's user id.
Parameters
Path Parameters
id*
Book UUID
Type
Requiredstring
Format
"uuid"Responses
Book with files
application/json
JSON "id": "string", "status": "string", "title": "string", "author": "string", "isbn10": "string", "isbn13": "string", "publisher": "string", "publishedYear": 0, "language": "string", "description": "string", "coverUrl": "string", "coverPath": "string", "pageCount": 0, "series": "string", "seriesIndex": 0, "genres": [ "string" ], "tags": [ "string" ], "hardcoverBookId": 0, "hardcoverEditionId": 0, "createdBy": "string", "possibleDuplicateOf": "string", "approvedAt": "string", "createdAt": "string", "updatedAt": "string", "files": [ { "id": "string", "format": "string", "originalName": "string", "fileSize": "string", "storagePath": "string", "checksum": "string" } ], "uploader": { "id": "string", "label": "string" }, "progress": { "percentage": 0, "status": "string", "lastDevice": "string", "lastTimestamp": 0, "startedAt": "string", "finishedAt": "string", "pausedAt": "string", "manuallySet": true, "externallySet": true }
{
}
GET
/api/library/{id}
Samples
Update library book
PATCH
/api/library/{id}
Edit metadata fields on an organized book
Parameters
Path Parameters
id*
Book UUID
Type
Requiredstring
Format
"uuid"Request Body
application/json
JSON "title": "string", "author": "string", "isbn10": "string", "isbn13": "string", "publisher": "string", "publishedYear": 0, "language": "string", "description": "string", "pageCount": 0, "series": "string", "seriesIndex": 0, "genres": [ "string" ], "tags": [ "string" ], "coverUrl": "string"
{
}
Responses
Updated book
application/json
JSON "id": "string", "status": "string", "title": "string", "author": "string", "isbn10": "string", "isbn13": "string", "publisher": "string", "publishedYear": 0, "language": "string", "description": "string", "coverUrl": "string", "coverPath": "string", "pageCount": 0, "series": "string", "seriesIndex": 0, "genres": [ "string" ], "tags": [ "string" ], "hardcoverBookId": 0, "hardcoverEditionId": 0, "createdBy": "string", "possibleDuplicateOf": "string", "approvedAt": "string", "createdAt": "string", "updatedAt": "string"
{
}
PATCH
/api/library/{id}
Samples
Get reading progress for a book
GET
/api/library/{id}/progress
Returns reading progress across all devices by matching book file content hashes to KoReader document identifiers
Parameters
Path Parameters
id*
Book UUID
Type
Requiredstring
Format
"uuid"Responses
Reading progress entries for the book
application/json
JSON "bookId": "string", "progress": [ { "document": "string", "device": "string", "deviceId": "string", "progress": "string", "percentage": 0, "timestamp": 0 } ]
{
}
GET
/api/library/{id}/progress
Samples
Refetch metadata from external sources
POST
/api/library/{id}/refetch
Delete existing non-file metadata candidates and re-fetch from Hardcover for an organized book. The book stays organized throughout.
Parameters
Path Parameters
id*
Book UUID
Type
Requiredstring
Format
"uuid"Responses
Refetch enqueued
application/json
JSON "status": "string", "bookId": "string", "searchQuery": "string"
{
}
POST
/api/library/{id}/refetch
Samples
Re-organize a library book
POST
/api/library/{id}/reorganize
Enqueue a BOOK_ORGANIZE job for an already-organized book so its files are moved to match updated metadata (author/title)
Parameters
Path Parameters
id*
Book UUID
Type
Requiredstring
Format
"uuid"Responses
Reorganize job enqueued
application/json
JSON "message": "string", "bookId": "string"
{
}
POST
/api/library/{id}/reorganize
Samples
Apply refetched metadata to a library book
POST
/api/library/{id}/apply-metadata
Select metadata fields from refetched candidates and apply them to an organized book. Automatically enqueues a re-organize job to update file paths and re-embed EPUB metadata.
Parameters
Path Parameters
id*
Book UUID
Type
Requiredstring
Format
"uuid"Request Body
application/json
JSON "fields": { "additionalProperties": { "source": "string", "value": "string" } }
{
}
Responses
Metadata applied and re-organize job enqueued
application/json
JSON "id": "string", "status": "string", "title": "string", "author": "string", "isbn10": "string", "isbn13": "string", "publisher": "string", "publishedYear": 0, "language": "string", "description": "string", "coverUrl": "string", "coverPath": "string", "pageCount": 0, "series": "string", "seriesIndex": 0, "genres": [ "string" ], "tags": [ "string" ], "hardcoverBookId": 0, "hardcoverEditionId": 0, "createdBy": "string", "possibleDuplicateOf": "string", "approvedAt": "string", "createdAt": "string", "updatedAt": "string"
{
}
POST
/api/library/{id}/apply-metadata
Samples
Clear the manual reading status override
DELETE
/api/library/{id}/reading-status
Remove any manual override and revert to the computed reading status from KoReader sync data.
Parameters
Path Parameters
id*
Book UUID
Type
Requiredstring
Format
"uuid"Responses
Override cleared; returns the updated effective progress aggregate
application/json
JSON "percentage": 0, "status": "string", "lastDevice": "string", "lastTimestamp": 0, "startedAt": "string", "finishedAt": "string", "pausedAt": "string", "manuallySet": true, "externallySet": true
{
}
DELETE
/api/library/{id}/reading-status
Samples
Manually set reading status for a book
PATCH
/api/library/{id}/reading-status
Override the computed reading status with user-supplied values. Sticky against KoReader sync until the user clears the override via DELETE.
Parameters
Path Parameters
id*
Book UUID
Type
Requiredstring
Format
"uuid"Request Body
application/json
JSON "status": "string", "startedAt": "string", "finishedAt": "string", "pausedAt": "string"
{
}
Responses
Override applied; returns the updated effective progress aggregate
application/json
JSON "percentage": 0, "status": "string", "lastDevice": "string", "lastTimestamp": 0, "startedAt": "string", "finishedAt": "string", "pausedAt": "string", "manuallySet": true, "externallySet": true
{
}
PATCH
/api/library/{id}/reading-status
Samples
Get library book cover
GET
/api/library/{id}/cover
Returns the cover image for an organized book, served from the library storage path. Supports ETag-based cache revalidation.
Parameters
Path Parameters
id*
Book UUID
Type
Requiredstring
Format
"uuid"Responses
Cover image (JPEG, PNG, WebP, or GIF)
GET
/api/library/{id}/cover
Samples
Download a book file
GET
/api/library/{id}/download/{fileId}
Streams the ebook file for download, identified by book ID and file ID. Sets Content-Disposition for browser download with the original filename.
Parameters
Path Parameters
id*
Book UUID
Type
Requiredstring
Format
"uuid"fileId*
File UUID
Type
Requiredstring
Format
"uuid"Responses
Ebook file binary stream
GET
/api/library/{id}/download/{fileId}
Samples
List inbox books
GET
/api/inbox
Paginated list of books in inbox or review status. Non-admin users see only books they own.
Parameters
Query Parameters
page
Page number
Type
integer
Minimum
1Default
1limit
Items per page
Type
integer
Minimum
1Maximum
100Default
20q
Full-text search query
Type
string
Max Length
500Default
""sort
Sort order for results
Type
string
Default
"detected_newest"Responses
Paginated list of inbox books with files
application/json
JSON "data": [ { "id": "string", "status": "string", "title": "string", "author": "string", "isbn10": "string", "isbn13": "string", "publisher": "string", "publishedYear": 0, "language": "string", "description": "string", "coverUrl": "string", "coverPath": "string", "pageCount": 0, "series": "string", "seriesIndex": 0, "genres": [ "string" ], "tags": [ "string" ], "hardcoverBookId": 0, "hardcoverEditionId": 0, "createdBy": "string", "possibleDuplicateOf": "string", "approvedAt": "string", "createdAt": "string", "updatedAt": "string", "files": [ { "id": "string", "format": "string", "originalName": "string", "fileSize": "string" } ], "uploader": { "id": "string", "label": "string" } } ], "pagination": { "page": 0, "limit": 0, "total": 0, "totalPages": 0 }
{
}
GET
/api/inbox
Samples
Get inbox count
Inbox processing status
GET
/api/inbox/processing
Returns the current pipeline stage for visible books being processed. Non-admin results are owner-scoped.
Responses
Map of bookId to processing stage
application/json
JSON "processing": { "additionalProperties": { "stage": "string", "label": "string" } }
{
}
GET
/api/inbox/processing
Samples
Get inbox book
GET
/api/inbox/{id}
Retrieve an owned inbox/review book with its files and metadata candidates. Admins may retrieve any book.
Parameters
Path Parameters
id*
Book UUID
Type
Requiredstring
Format
"uuid"Responses
Book with files and candidates
application/json
JSON "id": "string", "status": "string", "title": "string", "author": "string", "isbn10": "string", "isbn13": "string", "publisher": "string", "publishedYear": 0, "language": "string", "description": "string", "coverUrl": "string", "coverPath": "string", "pageCount": 0, "series": "string", "seriesIndex": 0, "genres": [ "string" ], "tags": [ "string" ], "hardcoverBookId": 0, "hardcoverEditionId": 0, "createdBy": "string", "possibleDuplicateOf": "string", "approvedAt": "string", "createdAt": "string", "updatedAt": "string", "possibleDuplicate": { "id": "string", "title": "string", "author": "string", "status": "string" }, "files": [ { "id": "string", "format": "string", "originalName": "string", "fileSize": "string", "checksum": "string" } ], "candidates": [ { "id": "string", "source": "string", "normalized": "string", "confidence": "string", "selectedFields": [ "string" ] } ], "uploader": { "id": "string", "label": "string" }
{
}
GET
/api/inbox/{id}
Samples
Rescan inbox book metadata
PATCH
/api/inbox/{id}/rescan
Delete existing metadata candidates and re-fetch from external sources
Parameters
Path Parameters
id*
Book UUID
Type
Requiredstring
Format
"uuid"Responses
Rescan enqueued
application/json
JSON "status": "string", "bookId": "string", "searchQuery": "string"
{
}
PATCH
/api/inbox/{id}/rescan
Samples
Get inbox book cover
GET
/api/inbox/{id}/cover
Returns the cover image for an owned inbox/review book. Admins may retrieve any cover. Tries EPUB extraction first, then falls back to proxying the coverUrl from metadata sources.
Parameters
Path Parameters
id*
Book UUID
Type
Requiredstring
Format
"uuid"Responses
Cover image (JPEG, PNG, WebP, or GIF)
GET
/api/inbox/{id}/cover
Samples
Upload ebook files
POST
/api/inbox/upload
Upload one or more ebook files (EPUB) to the inbox directory. Files are saved to disk; the file watcher picks them up for processing.
The response splits the batch three ways. uploaded[] is what was written. skipped[] is files whose contents are already on the server — already ingested, or uploaded by anyone and still awaiting the watcher; ingestion deduplicates by checksum, so writing them would drop them silently, and they are not written. A skip is not a failure: the library already holds that book, which is what the caller wanted. errors[] is genuine rejections — unsupported format, over the size limit, not a readable EPUB, or an unsafe filename.
The status is 400 only when every file landed in errors[]. A batch that was entirely skipped is 200: nothing was wrong with the request, there was simply nothing left to do.
Request Body
multipart/form-data
One or more ebook files
Responses
Per-file outcome for the batch: uploaded[], skipped[] (already in the library), errors[]. Returned whenever at least one file was written or skipped, even if others errored.
application/json
JSON "uploaded": [ { "filename": "string", "size": 0 } ], "skipped": [ { "filename": "string", "reason": "string" } ], "errors": [ { "filename": "string", "error": "string" } ]
{
}
POST
/api/inbox/upload
Samples
Delete book
Approve book metadata
POST
/api/books/{id}/approve
Select metadata fields from candidates, mark book as organized, and enqueue file organization
Parameters
Path Parameters
id*
Book UUID
Type
Requiredstring
Format
"uuid"Request Body
application/json
JSON "fields": { "additionalProperties": { "source": "string", "value": "string" } }
{
}
Responses
Book approved and organize job enqueued
application/json
JSON "id": "string", "status": "string", "title": "string", "author": "string", "isbn10": "string", "isbn13": "string", "publisher": "string", "publishedYear": 0, "language": "string", "description": "string", "coverUrl": "string", "coverPath": "string", "pageCount": 0, "series": "string", "seriesIndex": 0, "genres": [ "string" ], "tags": [ "string" ], "hardcoverBookId": 0, "hardcoverEditionId": 0, "createdBy": "string", "possibleDuplicateOf": "string", "approvedAt": "string", "createdAt": "string", "updatedAt": "string"
{
}
POST
/api/books/{id}/approve
Samples
Get metadata candidates
GET
/api/books/{id}/candidates
List metadata candidates fetched from external sources for a book
Parameters
Path Parameters
id*
Book UUID
Type
Requiredstring
Format
"uuid"Responses
Book info with metadata candidates
application/json
JSON "book": { "id": "string", "status": "string", "title": "string", "author": "string" }, "candidates": [ { "id": "string", "source": "string", "normalized": "string", "confidence": "string", "selectedFields": [ "string" ] } ]
{
}
GET
/api/books/{id}/candidates
Samples
List all series
GET
/api/series
Returns distinct series names with book counts and cover art. Supports search filtering.
Parameters
Query Parameters
q
Filter series names (partial match)
Type
string
Max Length
500Default
""Responses
Series list
application/json
JSON "data": [ { "name": "string", "bookCount": 0, "coverUrl": "string", "coverPath": "string", "coverBookId": "string" } ], "total": 0
{
}
GET
/api/series
Samples
Get books in a series
GET
/api/series/{name}
Returns all books in the given series ordered by series_index (nulls last).
Parameters
Path Parameters
name*
Series name
Type
Requiredstring
Responses
Series detail with books
application/json
JSON "series": "string", "books": [ { "id": "string", "title": "string", "author": "string", "series": "string", "seriesIndex": 0, "coverUrl": "string", "coverPath": "string", "genres": [ "string" ], "tags": [ "string" ], "pageCount": 0, "publishedYear": 0, "files": [ { "id": "string", "format": "string", "originalName": "string", "fileSize": "string" } ] } ], "total": 0
{
}
GET
/api/series/{name}
Samples
Search suggestions for command palette
GET
/api/search/suggest
Lightweight prefix search returning up to 8 results for autocomplete. Matches every organized book (the library is shared) plus the caller's own review books, so you can find an upload of yours that is still awaiting metadata approval. Other users' review books are never returned; admins see all of them.
Parameters
Query Parameters
q*
Search prefix
Type
Requiredstring
Min Length
1Max Length
500Responses
Search suggestions
application/json
JSON "data": [ { "id": "string", "title": "string", "author": "string", "status": "string", "coverUrl": "string" } ]
{
}
GET
/api/search/suggest
Samples
Get dashboard data
GET
/api/dashboard
Returns currently reading books, recently added, inbox count, library stats, and pipeline status. currentlyReading, inboxCount and stats.processingCount are per-user (reading progress is private, and inbox/review books are pre-approval uploads); recentlyAdded and the rest of stats describe the shared organized library, so stats.totalFileSize counts only organized books' files. pipeline holds install-wide queue counts and is therefore admin-only: it is an empty object for everyone else. Admins receive the install-wide inbox count and processing count.
Responses
Dashboard data
application/json
JSON "currentlyReading": [ { "id": "string", "title": "string", "author": "string", "coverPath": "string", "percentage": 0, "device": "string", "lastRead": 0 } ], "recentlyAdded": [ { "id": "string", "title": "string", "author": "string", "coverPath": "string", "createdAt": "string" } ], "inboxCount": 0, "stats": { "totalBooks": 0, "totalAuthors": 0, "topGenre": "string", "totalFileSize": 0, "processingCount": 0 }, "pipeline": { "additionalProperties": { "waiting": 0, "active": 0, "completed": 0, "failed": 0, "delayed": 0 } }
{
}
GET
/api/dashboard
Samples
Get reading statistics
GET
/api/stats
Returns reading analytics for the stats page: finished-book counts, genre distribution, reading streak, average finish time, yearly pages-read heatmap, books finished per month, reading velocity (7-day moving avg), top authors, days-to-finish histogram, and library growth.
Parameters
Query Parameters
year
Calendar year for the daily-pages heatmap (YYYY). Defaults to the current year.
Type
integer
Example
2026Minimum
1970Maximum
2100Responses
Reading statistics
application/json
JSON "booksFinished": { "allTime": 0, "thisYear": 0, "thisMonth": 0 }, "genreDistribution": [ { "genre": "string", "count": 0 } ], "streak": { "current": 0, "longest": 0 }, "avgDaysToFinish": 0, "pagesHeatmap": { "year": 0, "days": [ { "day": "string", "pages": 0 } ] }, "finishedPerMonth": [ { "month": "string", "count": 0 } ], "readingVelocity": [ { "day": "string", "avgPages": 0 } ], "topAuthors": [ { "author": "string", "count": 0 } ], "daysToFinishBuckets": [ { "bucket": "string", "count": 0 } ], "libraryGrowth": [ { "month": "string", "cumulative": 0 } ]
{
}
GET
/api/stats
Samples
Get reading status counts
List books by reading status
GET
/api/reading-status/{status}
Returns a paginated list of organized books filtered by the given reading status, with optional search and sorting.
Parameters
Path Parameters
status*
Reading status to filter by
Type
Requiredstring
Valid values
"unread""reading""finished""paused"Query Parameters
page
Page number (1-based)
Type
integer
Minimum
1Default
1limit
Number of items per page
Type
integer
Minimum
1Maximum
100Default
20sort
Sort field
Type
string
Default
"title"order
Sort direction
Type
string
Default
"asc"search
Full-text search query
Type
string
Max Length
500Default
""Responses
Paginated list of books with reading progress
application/json
JSON "data": [ { "id": "string", "title": "string", "author": "string", "coverPath": "string", "isbn13": "string", "isbn10": "string", "genres": [ "string" ], "pageCount": 0, "percentage": 0, "device": "string", "lastReadAt": "string", "readingStatus": "string" } ], "pagination": { "page": 0, "limit": 0, "total": 0, "totalPages": 0 }
{
}
GET
/api/reading-status/{status}
Samples
Check service credentials
GET
/api/credentials/{service}
Check whether credentials are configured for a service (opds, kosync, or hardcover). Returns the username and timestamps if configured.
Parameters
Path Parameters
service*
Service name
Type
Requiredstring
Valid values
"kosync""hardcover"Responses
Credential status
application/json
JSON "configured": true, "service": "string", "username": "string", "createdAt": "string", "updatedAt": "string"
{
}
GET
/api/credentials/{service}
Samples
Set service credentials
PUT
/api/credentials/{service}
Set or update credentials for a service. Passwords for opds/kosync are bcrypt-hashed. Hardcover tokens are sealed with reversible encryption.
Parameters
Path Parameters
service*
Service name
Type
Requiredstring
Valid values
"kosync""hardcover"Request Body
application/json
JSON "username": "string", "password": "string"
{
}
Responses
Credentials updated
application/json
JSON "service": "string", "username": "string", "updated": true
{
}
PUT
/api/credentials/{service}
Samples
Delete service credentials
DELETE
/api/credentials/{service}
Remove stored credentials for a service (opds, kosync, or hardcover).
Parameters
Path Parameters
service*
Service name
Type
Requiredstring
Valid values
"kosync""hardcover"Responses
Credentials deleted
application/json
JSON "service": "string", "deleted": true
{
}
DELETE
/api/credentials/{service}
Samples
List your app passwords
GET
/api/app-passwords
Every credential issued to the signed-in user, for the Connected Devices list. The credentials themselves are hashed and never returned — only their labels, prefixes and last-used timestamps.
Responses
The caller's app passwords
application/json
JSON "keys": [ { "id": "string", "name": "string", "start": "string", "enabled": true, "createdAt": "string", "lastRequest": "string" } ]
{
}
GET
/api/app-passwords
Samples
Issue an app password
POST
/api/app-passwords
Create a credential for an e-reader, OPDS client or script. The plaintext is in the response and is never retrievable again — show it to the user immediately.
Request Body
application/json
JSON "name": "string"
{
}
Responses
The new app password, including its plaintext
application/json
JSON "id": "string", "name": "string", "start": "string", "enabled": true, "createdAt": "string", "lastRequest": "string", "key": "string"
{
}
POST
/api/app-passwords
Samples
Revoke an app password
DELETE
/api/app-passwords/{id}
Revoke a credential immediately — there is no session cache, so the next request using it fails. Revoking someone else's credential returns 404 rather than 403, so ids cannot be probed for existence.
Parameters
Path Parameters
id*
Credential id
Type
Requiredstring
Responses
Revoked
DELETE
/api/app-passwords/{id}
Samples
Job queue status
GET
/api/jobs/status
Return job counts per queue (waiting, active, completed, failed, delayed) plus whether the queue itself is paused. A paused queue keeps its jobs in waiting — isPaused is a queue-level flag, not a job count.
Responses
Queue status counts
application/json
JSON "queues": { "additionalProperties": { "waiting": 0, "active": 0, "completed": 0, "failed": 0, "delayed": 0, "isPaused": true } }
{
}
GET
/api/jobs/status
Samples
List failed jobs
GET
/api/jobs/failed
Return failed jobs across all queues with job ID, queue name, error message, timestamps, and attempt count
Responses
List of failed jobs
application/json
JSON "jobs": [ { "id": "string", "queueName": "string", "name": "string", "data": { "additionalProperties": "string" }, "error": "string", "failedAt": 0, "attemptsMade": 0, "maxAttempts": 0 } ], "total": 0
{
}
GET
/api/jobs/failed
Samples
List jobs across all queues
GET
/api/jobs
Browse recent jobs across all queues with filtering by queue name and status. Supports pagination via page/pageSize query params.
Parameters
Query Parameters
queue
Filter by queue name (e.g. book-detected)
Type
string
status
Filter by job status
Type
string
Valid values
"completed""active""waiting""failed""delayed"page
Page number
Type
integer
Minimum
1Default
1pageSize
Items per page (max 100)
Type
integer
Minimum
1Maximum
100Default
20Responses
Paginated list of jobs
application/json
JSON "jobs": [ { "id": "string", "queueName": "string", "name": "string", "data": { "additionalProperties": "string" }, "status": "string", "progress": 0, "returnValue": "string", "failedReason": "string", "stacktrace": [ "string" ], "attemptsMade": 0, "maxAttempts": 0, "timestamp": 0, "processedOn": 0, "finishedOn": 0, "duration": 0 } ], "total": 0, "page": 0, "pageSize": 0, "totalPages": 0
{
}
GET
/api/jobs
Samples
Get job details
GET
/api/jobs/{id}
Return full details for a specific job including stack trace, duration, progress, timestamps, return value, and payload data. Requires queueName since BullMQ job IDs are auto-incremented per queue and not unique across queues.
Parameters
Path Parameters
id*
Job ID
Type
Requiredstring
Min Length
1Query Parameters
queueName*
Queue this job belongs to (e.g. book-fetch-metadata)
Type
Requiredstring
Min Length
1Responses
Job details
application/json
JSON "id": "string", "queueName": "string", "name": "string", "data": { "additionalProperties": "string" }, "status": "string", "progress": 0, "returnValue": "string", "failedReason": "string", "stacktrace": [ "string" ], "attemptsMade": 0, "maxAttempts": 0, "timestamp": 0, "processedOn": 0, "finishedOn": 0, "duration": 0
{
}
GET
/api/jobs/{id}
Samples
Get job logs
GET
/api/jobs/{id}/logs
Return log lines stored via BullMQ job.log() for a specific job. Logs are stored in Redis per job and provide a timeline of job execution. Requires queueName since BullMQ job IDs are auto-incremented per queue and not unique across queues.
Parameters
Path Parameters
id*
Job ID
Type
Requiredstring
Min Length
1Query Parameters
queueName*
Queue this job belongs to (e.g. book-fetch-metadata)
Type
Requiredstring
Min Length
1Responses
Job logs
application/json
JSON "jobId": "string", "logs": [ "string" ], "count": 0
{
}
GET
/api/jobs/{id}/logs
Samples
Retry a failed job
POST
/api/jobs/{id}/retry
Retry a specific failed job by ID. Requires queueName since BullMQ job IDs are auto-incremented per queue and not unique across queues.
Parameters
Path Parameters
id*
Job ID to retry
Type
Requiredstring
Min Length
1Query Parameters
queueName*
Queue this job belongs to (e.g. book-fetch-metadata)
Type
Requiredstring
Min Length
1Responses
Job retried successfully
application/json
JSON "success": true, "jobId": "string", "queueName": "string"
{
}
POST
/api/jobs/{id}/retry
Samples
Pause a queue
POST
/api/jobs/queues/{name}/pause
Pause a specific queue by name. New jobs will not be processed until resumed.
Parameters
Path Parameters
name*
Queue name (e.g. book-detected)
Type
Requiredstring
Min Length
1Responses
Queue paused
application/json
JSON "success": true, "queue": "string", "paused": true
{
}
POST
/api/jobs/queues/{name}/pause
Samples
Resume a queue
POST
/api/jobs/queues/{name}/resume
Resume a paused queue. Jobs will begin processing again.
Parameters
Path Parameters
name*
Queue name
Type
Requiredstring
Min Length
1Responses
Queue resumed
application/json
JSON "success": true, "queue": "string", "paused": true
{
}
POST
/api/jobs/queues/{name}/resume
Samples
Clean failed jobs from a queue
Drain a queue
POST
/api/jobs/queues/{name}/drain
Remove all waiting and delayed jobs from a queue. Active jobs are not affected.
Parameters
Path Parameters
name*
Queue name
Type
Requiredstring
Min Length
1Responses
Queue drained
application/json
JSON "success": true, "queue": "string"
{
}
POST
/api/jobs/queues/{name}/drain
Samples
Get Hardcover connection status
GET
/api/hardcover/status
Check whether a Hardcover credential is configured, verify the token with the Hardcover API, and return the connected username and last sync timestamp.
Responses
Connection status
application/json
JSON "connected": true, "username": "string", "lastSyncAt": "string", "error": "string"
{
}
GET
/api/hardcover/status
Samples
Trigger Hardcover sync
POST
/api/hardcover/sync
Enqueue a user-scoped job to synchronize reading progress and ratings with the Hardcover service. Global metadata maintenance runs only on scheduled jobs. Requires a configured Hardcover credential.
Responses
Sync job enqueued
application/json
JSON "message": "string"
{
}
POST
/api/hardcover/sync
Samples
List Hardcover sync log entries
GET
/api/hardcover/sync/log
Return recent Hardcover sync log entries joined with book titles, sorted by last synced timestamp descending.
Parameters
Query Parameters
limit
Maximum entries to return
Type
integer
Minimum
1Maximum
100Default
20Responses
Array of sync log entries
application/json
JSON { "bookId": "string", "bookTitle": "string", "status": "string", "progress": "string", "rating": "string", "syncedAt": "string" }
[
]
GET
/api/hardcover/sync/log
Samples
Search Hardcover for metadata
GET
/api/hardcover/search
Run a free-text search against Hardcover and return up to 5 normalized metadata candidates. Used by the UI when auto-fetched metadata is wrong or missing — the user picks a result to autofill the edit form. The search always spends the caller's own Hardcover token; a caller with no Hardcover credential of their own gets 503 and no request is made to Hardcover, matching what GET /api/hardcover/status reports for them.
Parameters
Query Parameters
q*
Search query — title, author, ISBN, or any combination
Type
Requiredstring
Min Length
2Max Length
200Responses
Search results (may be empty)
application/json
JSON "results": [ { "source": "string", "normalized": "string", "confidence": 0 } ]
{
}
GET
/api/hardcover/search
Samples
Realtime event stream (WebSocket)
GET
/api/events
Upgrades to a WebSocket carrying job, pipeline and Hardcover sync events for the caller. Events are scoped to the authenticated principal; an admin additionally receives install-wide job events. The server closes the socket with application code 4401 when the session behind it is revoked (ban, sign-out, removal) and 4409 when the caller's identity or role changed and the client should re-dial to be re-scoped. Connections are capped per principal and process-wide.
Parameters
Query Parameters
bookId
Only deliver events concerning this book.
Type
string
Responses
Switching Protocols - the WebSocket is established.
GET
/api/events
Samples
Authenticate via KOReader headers
GET
/kosync/users/auth
KOReader sends credentials via x-auth-user (username) and x-auth-key (md5-hashed password) headers. Returns the userkey for subsequent sync requests. These non-standard headers are validated in the handler.
Responses
Authentication successful
application/json
JSON "authorized": "string", "userkey": "string"
{
}
GET
/kosync/users/auth
Samples
Authenticate via JSON body
POST
/kosync/users/auth
Validate KoSync credentials provided as a JSON body. Returns the md5-hashed password as the userkey for subsequent sync requests. The rate limiter buckets attempts by the username in this body, so a body over 8 KB — which no KOReader login sends — is refused with 413 rather than let through unbucketed.
Request Body
application/json
JSON "username": "string", "password": "string"
{
}
Responses
Authentication successful
application/json
JSON "authorized": "string", "userkey": "string"
{
}
POST
/kosync/users/auth
Samples
Register a KoSync user (disabled)
POST
/kosync/users/create
KOReader calls this endpoint to register a new user. Registration is disabled in Libris — credentials must be set via the Libris dashboard. Always returns 409.
Request Body
application/json
JSON "username": "string", "password": "string"
{
}
Responses
Registration is disabled; set credentials in the Libris dashboard
POST
/kosync/users/create
Samples
Get reading progress
GET
/kosync/syncs/progress/{document}
Retrieve the most recent reading progress entry for a document identified by its hash. Returns 404 if no progress has been recorded for this document.
Parameters
Path Parameters
document*
Document hash (content or original)
Type
Requiredstring
Min Length
1Responses
Reading progress
application/json
JSON "document": "string", "progress": "string", "percentage": 0, "device": "string", "device_id": "string", "timestamp": 0
{
}
GET
/kosync/syncs/progress/{document}
Samples
Upsert reading progress
PUT
/kosync/syncs/progress
Create or update reading progress for a document/device pair. Also appends to the progress history table (fire-and-forget). Returns the persisted progress entry.
Request Body
application/json
JSON "document": "string", "progress": "string", "device": "string", "percentage": 0, "device_id": "string"
{
}
Responses
Persisted reading progress
application/json
JSON "document": "string", "progress": "string", "percentage": 0, "device": "string", "device_id": "string", "timestamp": 0
{
}
PUT
/kosync/syncs/progress
Samples
OPDS root catalog
List all books (OPDS)
GET
/opds/books
Returns a paginated OPDS acquisition feed of all organized books, sorted alphabetically by title. Supports page-based pagination via query parameter.
Parameters
Query Parameters
page
Page number (1-based)
Type
integer
Example
1Minimum
1Responses
OPDS acquisition feed with book entries (Atom XML)
application/atom+xml;profile=opds-catalog;kind=acquisition
XML
GET
/opds/books
Samples
Get a single book entry (OPDS)
GET
/opds/books/{id}
Returns an OPDS entry document for a single organized book, including metadata and acquisition links for available file formats.
Parameters
Path Parameters
id*
Book UUID
Type
Requiredstring
Example
"550e8400-e29b-41d4-a716-446655440000"Format
"uuid"Responses
OPDS entry document for the book (Atom XML)
application/atom+xml;type=entry;profile=opds-catalog
XML
GET
/opds/books/{id}
Samples
Books by author (OPDS)
GET
/opds/authors/{slug}
Returns a paginated OPDS acquisition feed of books by a specific author, identified by a URL-friendly slug derived from the author name.
Parameters
Path Parameters
slug*
URL-friendly author slug
Type
Requiredstring
Min Length
1Max Length
500Query Parameters
page
Page number (1-based)
Type
integer
Example
1Minimum
1Responses
OPDS acquisition feed of books by the author (Atom XML)
application/atom+xml;profile=opds-catalog;kind=acquisition
XML
GET
/opds/authors/{slug}
Samples
List genres (OPDS)
Books by genre (OPDS)
GET
/opds/genres/{slug}
Returns a paginated OPDS acquisition feed of books in a specific genre, identified by a URL-friendly slug.
Parameters
Path Parameters
slug*
URL-friendly genre slug
Type
Requiredstring
Min Length
1Max Length
500Query Parameters
page
Page number (1-based)
Type
integer
Example
1Minimum
1Responses
OPDS acquisition feed of books in the genre (Atom XML)
application/atom+xml;profile=opds-catalog;kind=acquisition
XML
GET
/opds/genres/{slug}
Samples
List series (OPDS)
Books in a series (OPDS)
GET
/opds/series/{name}
Returns a paginated OPDS acquisition feed of books in a specific series, ordered by series index.
Parameters
Path Parameters
name*
Series name
Type
Requiredstring
Min Length
1Max Length
500Query Parameters
page
Page number (1-based)
Type
integer
Example
1Minimum
1Responses
OPDS acquisition feed of books in the series (Atom XML)
application/atom+xml;profile=opds-catalog;kind=acquisition
XML
GET
/opds/series/{name}
Samples
List languages (OPDS)
GET
/opds/languages
Returns an OPDS navigation feed listing only the languages present in organized books (canonical ISO 639-1 codes shown as full names), each linking to a language-specific acquisition feed.
Responses
OPDS navigation feed of languages (Atom XML)
application/atom+xml;profile=opds-catalog;kind=navigation
XML
GET
/opds/languages
Samples
Books by language (OPDS)
GET
/opds/languages/{code}
Returns a paginated OPDS acquisition feed of books in a specific language, identified by its ISO 639-1 code (case-insensitive).
Parameters
Path Parameters
code*
Language code, e.g. 'en'
Type
Requiredstring
Min Length
1Max Length
35Query Parameters
page
Page number (1-based)
Type
integer
Example
1Minimum
1Responses
OPDS acquisition feed of books in the language (Atom XML)
application/atom+xml;profile=opds-catalog;kind=acquisition
XML
GET
/opds/languages/{code}
Samples
New arrivals (OPDS)
GET
/opds/new
Returns a paginated OPDS acquisition feed of recently added books, sorted by creation date (newest first).
Parameters
Query Parameters
page
Page number (1-based)
Type
integer
Example
1Minimum
1Responses
OPDS acquisition feed of new arrivals (Atom XML)
application/atom+xml;profile=opds-catalog;kind=acquisition
XML
GET
/opds/new
Samples
Search books or get OpenSearch descriptor (OPDS)
GET
/opds/search
When called with a q query parameter, returns a paginated OPDS acquisition feed of matching books using full-text search. Without q, returns an OpenSearch description document that OPDS clients use for search discovery.
Parameters
Query Parameters
q
Search query. Omit to get the OpenSearch description document.
Type
string
Max Length
500page
Page number (1-based, only used with q)
Type
integer
Example
1Minimum
1Responses
OPDS acquisition feed of search results (Atom XML) or OpenSearch description (XML)
XML
GET
/opds/search
Samples
Get book cover image (OPDS)
GET
/opds/covers/{id}
Returns the cover image for an organized book. Used by OPDS clients to display book thumbnails. Supports ETag-based cache revalidation.
Parameters
Path Parameters
id*
Book UUID
Type
Requiredstring
Example
"550e8400-e29b-41d4-a716-446655440000"Format
"uuid"Responses
Cover image (JPEG, PNG, WebP, or GIF)
GET
/opds/covers/{id}
Samples
Download a book file (OPDS)
GET
/opds/download/{fileId}
Streams an ebook file for download, identified by its file ID. The file must belong to an organized book. Sets Content-Disposition for browser/e-reader download with the original filename.
Parameters
Path Parameters
fileId*
Book file UUID
Type
Requiredstring
Example
"550e8400-e29b-41d4-a716-446655440000"Format
"uuid"Responses
Ebook file binary stream
GET
/opds/download/{fileId}