farfield · docs

library

library.farfield.systems

A self-hosted OPDS e-book catalog. Upload EPUBs through the admin UI and browse them from any OPDS reader — KOReader, Thorium, Foliate, Cantook. Title, author, language, and cover are read from each EPUB's OPF on upload. Book and cover bytes are content-addressed (CIDv1 sha-256) and stored on Cloudflare R2; metadata lives in SQLite. Books are organised into folders (collections), surfaced as a navigation feed the reader browses.

Connecting a reader

Point an OPDS reader at the catalog root and authenticate with HTTP Basic Auth — any username, the catalog key as the password:

URL:      https://library.farfield.systems/opds
Username: (anything)
Password: (the catalog key)

The root is an OPDS navigation feed of folders; each folder links to an acquisition feed of books. Every catalog endpoint requires the Basic credential (or a logged-in admin session).

OPDS catalog

GET /opds
Navigation feed — a folder per collection, plus Uncategorized and All books
GET /opds/all
Acquisition feed of every book
GET /opds/collection?c={name}
Acquisition feed of one folder (empty c is the uncategorised books)
GET /opds/download/{cid}
The EPUB bytes — application/epub+zip, immutable ETag
GET /opds/cover/{cid}
The cover image — immutable ETag

Upload API

One credential covers the whole API surface: the catalog Basic password and the upload X-API-Key are the same key.

POST /api/books?filename={name}&collection={folder}
Upload one raw EPUB body → BookX-API-Key. filename and collection are optional.
DELETE /api/books/{cid}
Delete a book — X-API-Key
GET /status
{ "service": "library", "ok": true, "books": N } — public

Record Shape

Book

{
  "cid": "bafk…",            // CID of the EPUB bytes
  "title": "Catch-22",
  "author": "Joseph Heller",
  "language": "en",
  "identifier": "urn:isbn:…",
  "description": "…",
  "collection": "Sci-Fi",    // folder, or "" for uncategorised
  "filename": "catch-22.epub",
  "size": 468000,
  "coverCid": "bafk…",       // CID of the cover image, or ""
  "coverMime": "image/jpeg",
  "createdAt": "2026-06-04T00:00:00Z"
}