Skip to main content
The books router provides endpoints for searching, retrieving, and managing books in your library.

Get book with metadata

Retrieve a single book with its complete metadata.
const book = await client.books.getBookWithMetadata({
  uuid: "book-uuid-here",
});

Input

uuid
string
required
Book UUID

Response

id
number
Internal book ID
uuid
string
Book UUID
filename
string
Original filename
filesizeKb
number | null
File size in kilobytes
title
string | null
Book title
titleRomaji
string | null
Romanized title (for Japanese books)
subtitle
string | null
Book subtitle
description
string | null
Book description
publishedDate
string | null
Publication date
languageCode
string | null
ISO language code (e.g., “en”, “ja”)
pageCount
number | null
Number of pages
amountChars
number | null
Character count (for text-based formats)
isbn10
string | null
ISBN-10 identifier
isbn13
string | null
ISBN-13 identifier
asin
string | null
Amazon ASIN
cover
string | null
Cover image URL
color
string | null
Dominant cover color (hex format)
Signed download URL
authors
array
Array of author objects
name
string
Author name
role
string | null
Author role (e.g., “author”, “translator”)
publisher
object | undefined
Publisher information
name
string
Publisher name
createdAt
string
ISO timestamp when book was added
lastModified
string | null
Last modification timestamp

List recent books

Get recently added books, scoped to the user’s active organization.
const books = await client.books.listRecent({
  limit: 20,
});

Input

limit
number
default:20
Maximum number of books to return (1-50)

Response

Array of book objects with the same structure as getBookWithMetadata.

List random books

Get random books from the library.
const books = await client.books.listRandom({
  limit: 15,
});

Input

limit
number
default:15
Maximum number of books to return (1-50)

Response

Array of book objects with the same structure as getBookWithMetadata.

Search books

Search books using Elasticsearch with support for filters, sorting, and pagination.
const results = await client.books.search({
  query: "太宰治",
  filters: {
    languageCode: ["ja"],
    publishedDateRange: {
      from: "1930",
      to: "1950",
    },
    pageCountRange: {
      min: 100,
      max: 500,
    },
    authors: ["Osamu Dazai"],
    publishers: ["Shinchosha"],
  },
  sort: "relevance",
  limit: 20,
  cursor: "optional-cursor-for-pagination",
});

Input

query
string
Search query. Supports Japanese text with Sudachi tokenizer.
exactMatch
boolean
Whether to perform exact phrase matching
filters
object
Search filters
languageCode
string[]
Filter by language codes
publishedDateRange
object
from
string
Start date (YYYY format)
to
string
End date (YYYY format)
pageCountRange
object
min
number
Minimum page count
max
number
Maximum page count
authors
string[]
Filter by author names
series
string[]
Filter by series names
publishers
string[]
Filter by publisher names
sort
enum
default:"relevance"
Sort order: relevance, newest, oldest, title_asc, title_desc
limit
number
default:20
Results per page (1-50)
cursor
string
Pagination cursor from previous response

Response

items
array
Array of book objects matching the search criteria
cursor
string | null
Cursor for fetching the next page, or null if no more results
total
number
Total number of matching books

Reindex books

Trigger a full reindex of all books into Elasticsearch. This is useful after Elasticsearch schema changes or data corruption.
const result = await client.books.reindex();
console.log(result.jobId); // BullMQ job ID

Response

jobId
string
BullMQ job ID for tracking the reindex operation

Book metadata schema

Book metadata is extracted from ebook files using various providers (local extraction from EPUB/PDF metadata). The metadata schema is defined in packages/api/src/routers/books/metadata/book.metadata.model.ts:
export const MetadataInfoSchema = z.object({
  title: z.string().nullable().optional(),
  titleRomaji: z.string().nullable().optional(),
  subtitle: z.string().nullable().optional(),
  description: z.string().nullable().optional(),
  publishedDate: z.string().nullable().optional(),
  languageCode: z.string().nullable().optional(),
  pageCount: z.number().int().nullable().optional(),
  isbn10: z.string().nullable().optional(),
  isbn13: z.string().nullable().optional(),
  asin: z.string().nullable().optional(),
  cover: z.string().nullable(),
  amountChars: z.number().nullable().optional(),
  authors: z.array(AuthorSchema).nullable().optional(),
  publisher: PublisherSchema.optional(),
});
import { useState } from "react";
import { orpc } from "@/utils/orpc";

function BookSearch() {
  const [query, setQuery] = useState("");
  
  const { data, isLoading, fetchNextPage } = orpc.books.search.useInfiniteQuery(
    {
      query,
      limit: 20,
    },
    {
      getNextPageParam: (lastPage) => lastPage.cursor,
    },
  );

  const books = data?.pages.flatMap((page) => page.items) ?? [];

  return (
    <div>
      <input
        type="text"
        value={query}
        onChange={(e) => setQuery(e.target.value)}
        placeholder="Search books..."
      />
      
      {isLoading && <p>Loading...</p>}
      
      <div>
        {books.map((book) => (
          <div key={book.uuid}>
            <img src={book.cover} alt={book.title} />
            <h3>{book.title}</h3>
            <p>{book.authors?.map((a) => a.name).join(", ")}</p>
          </div>
        ))}
      </div>
      
      <button onClick={() => fetchNextPage()}>Load more</button>
    </div>
  );
}

Build docs developers (and LLMs) love