# open-library-api-rs
[](https://crates.io/crates/open-library-api-rs)
[](https://docs.rs/open-library-api-rs)
[](LICENSE)
[](https://www.rust-lang.org)
A full-featured async Rust client library and CLI for the [Open Library API](https://openlibrary.org/developers/api).
Covers every documented read endpoint across all twelve API domains: works, editions, books (bibkey), authors, full-text search, subjects, covers, user lists, reading logs, the partner/volumes API, recent changes, and the generic query and history endpoints.
---
## Contents
- [Features](#features)
- [Library](#library)
- [Installation](#installation)
- [Quick Start](#quick-start)
- [Client Configuration](#client-configuration)
- [API Reference](#api-reference)
- [Works](#works)
- [Editions](#editions)
- [Books (Bibkey Lookup)](#books-bibkey-lookup)
- [Authors](#authors)
- [Search](#search)
- [Subjects](#subjects)
- [Covers and Author Photos](#covers-and-author-photos)
- [User Lists](#user-lists)
- [Reading Log](#reading-log)
- [Partner / Volumes API](#partner--volumes-api)
- [Recent Changes](#recent-changes)
- [Generic Query](#generic-query)
- [Resource History](#resource-history)
- [Error Handling](#error-handling)
- [Input Validation](#input-validation)
- [Feature Flags](#feature-flags)
- [Blocking (Sync) API](#blocking-sync-api)
- [CLI — `olib`](#cli--olib)
- [Installing the CLI](#installing-the-cli)
- [Global Options](#global-options)
- [work](#work)
- [edition](#edition)
- [author](#author)
- [search](#search-1)
- [subject](#subject)
- [cover](#cover)
- [list](#list)
- [reading](#reading)
- [changes](#changes)
- [volume](#volume)
- [books](#books)
- [query](#query)
- [history](#history)
- [Security](#security)
- [Rate Limits](#rate-limits)
- [Building from Source](#building-from-source)
- [License](#license)
---
## Features
- **Complete API coverage** — every documented public read endpoint
- **Strongly typed** — dedicated structs for all response shapes; `serde` with `#[serde(default)]` and no `deny_unknown_fields` so new API fields never break deserialization
- **Async-first** — built on `tokio` and `reqwest`; blocking wrappers available behind a feature flag
- **Built-in rate limiting** — token-bucket limiter via `governor`; 1 req/s default, 3 req/s with an identifying `User-Agent`
- **Input validation** — OLIDs, ISBN-10/13, subject slugs, usernames, bibkeys, dates all validated before any HTTP call is made
- **Security hardened** — `rustls` TLS only by default, 10 MB response body cap, `#![forbid(unsafe_code)]`, URL-safe parameter construction (no raw string concatenation)
- **CLI included** — `olib` binary prints human-readable output to stdout, errors to stderr; `--json` flag for machine-readable output
---
## Library
### Installation
Add to `Cargo.toml`:
```toml
[dependencies]
open-library-api-rs = "0.1"
tokio = { version = "1", features = ["full"] }
```
The default feature set uses `rustls-tls`. If you need the system TLS stack:
```toml
open-library-api-rs = { version = "0.1", default-features = false, features = ["native-tls"] }
```
### Quick Start
```rust
use open_library_api_rs::{OpenLibraryClient, Result};
use open_library_api_rs::models::search::SearchParams;
#[tokio::main]
async fn main() -> Result<()> {
// Build a client (1 req/s, rustls TLS, 10s connect / 30s request timeout)
let client = OpenLibraryClient::builder().build()?;
// Fetch a work
let work = client.get_work("OL45804W").await?;
println!("Title: {:?}", work.title);
println!("Subjects: {:?}", work.subjects);
// Search
let results = client.search(SearchParams {
q: Some("rust programming language".into()),
limit: Some(5),
..Default::default()
}).await?;
println!("Found {} results", results.num_found);
for doc in &results.docs {
println!(" {} — {:?}", doc.key, doc.title);
}
Ok(())
}
```
### Client Configuration
```rust
use std::time::Duration;
use open_library_api_rs::OpenLibraryClient;
let client = OpenLibraryClient::builder()
// Provide a contact email to unlock the 3 req/s identified tier
.contact_email("me@example.com")?
// Always pair contact_email with rate_limit(3)
.rate_limit(3)
// TCP connect timeout (default: 10 s)
.connect_timeout(Duration::from_secs(10))
// Total request timeout (default: 30 s)
.timeout(Duration::from_secs(30))
// Override base URL (useful for tests or a local mirror)
.base_url("https://openlibrary.org")
// Override covers base URL
.covers_url("https://covers.openlibrary.org")
.build()?;
```
`OpenLibraryClient` is `Clone + Send + Sync`; clone it cheaply to share across tasks or threads.
---
### API Reference
All methods are `async` and return `Result<T>` where `T` is a strongly-typed struct. Every method validates its inputs before making any HTTP request.
#### Works
A *work* is the canonical creative entity (the book as an idea). Editions are concrete printings of a work.
```rust
// Get a work by its Work OLID
let work: Work = client.get_work("OL45804W").await?;
// work.key, work.title, work.description, work.authors, work.subjects,
// work.covers, work.first_publish_date, ...
// List editions of a work (paginated)
let editions: WorkEditions = client.get_work_editions("OL45804W", 20, 0).await?;
// editions.entries — Vec<WorkEditionEntry>
// editions.size — total edition count
// Community star ratings
let ratings: WorkRatings = client.get_work_ratings("OL45804W").await?;
// ratings.summary.average, ratings.summary.count
// ratings.counts.one / .two / .three / .four / .five
// Bookshelf counts (want-to-read, currently reading, already read)
let shelves: WorkBookshelves = client.get_work_bookshelves("OL45804W").await?;
// shelves.counts.want_to_read, .currently_reading, .already_read
```
**OLID format:** `OL<digits>W` — e.g. `OL45804W`.
#### Editions
An *edition* is a specific physical or digital printing of a work.
```rust
// Get an edition by its Edition OLID
let edition: Edition = client.get_edition("OL7353617M").await?;
// edition.key, edition.title, edition.publishers, edition.publish_date,
// edition.isbn_13, edition.isbn_10, edition.languages, edition.number_of_pages,
// edition.physical_format, edition.covers, ...
// Get an edition by ISBN (10 or 13 digits; hyphens and spaces are stripped)
let edition: Edition = client.get_edition_by_isbn("978-0-14-032-8721-4").await?;
let edition: Edition = client.get_edition_by_isbn("0140328726").await?;
```
**OLID format:** `OL<digits>M` — e.g. `OL7353617M`.
#### Books (Bibkey Lookup)
The `/api/books` endpoint returns structured data for one or more books identified by bibliographic keys.
```rust
use open_library_api_rs::models::common::BooksJsCmd;
use std::collections::HashMap;
let bibkeys = vec![
"ISBN:0451450523".to_string(),
"OCLC:45883427".to_string(),
"LCCN:2004046975".to_string(),
];
// BooksJsCmd::Data (default) — full data bundle per book
// BooksJsCmd::Details — structured details including subjects, excerpts
// BooksJsCmd::ViewApi — preview / read links
let books: HashMap<String, BooksApiEntry> =
client.get_books(&bibkeys, BooksJsCmd::Data).await?;
for (key, entry) in &books {
println!("{key}: {:?}", entry.info_url);
}
```
**Valid bibkey prefixes:** `ISBN:`, `OCLC:`, `LCCN:`, `OLID:`, `ID:`
#### Authors
```rust
// Get an author by their Author OLID
let author: Author = client.get_author("OL23919A").await?;
// author.key, author.name, author.birth_date, author.death_date,
// author.bio, author.photos, author.wikipedia, author.alternate_names, ...
// Get the works attributed to an author (paginated)
let works: AuthorWorks = client.get_author_works("OL23919A", 50, 0).await?;
// works.entries — Vec<AuthorWorkEntry> with key, title, covers, first_publish_date
```
**OLID format:** `OL<digits>A` — e.g. `OL23919A`.
#### Search
Five distinct search endpoints, all returning `SearchResponse<T>` where `num_found` is the total match count and `docs` is the current page.
```rust
use open_library_api_rs::models::search::{
SearchParams, AuthorSearchParams,
BookDoc, AuthorDoc, SubjectDoc, ListDoc, InsideDoc,
};
// ── Book / work search ────────────────────────────────────────────────────────
let results: SearchResponse<BookDoc> = client.search(SearchParams {
q: Some("lord of the rings".into()),
author: Some("tolkien".into()),
language: Some("eng".into()),
limit: Some(10),
offset: Some(0),
sort: Some("new".into()), // "new" | "old" | "relevance"
..Default::default()
}).await?;
// SearchParams also accepts: title, isbn, subject, place, person, publisher, page, fields, lang
// ── Author search ─────────────────────────────────────────────────────────────
let authors: SearchResponse<AuthorDoc> = client.search_authors(AuthorSearchParams {
q: Some("tolkien".into()),
limit: Some(5),
offset: Some(0),
}).await?;
// ── Subject search ────────────────────────────────────────────────────────────
let subjects: SearchResponse<SubjectDoc> = client.search_subjects("fantasy").await?;
// ── List search ───────────────────────────────────────────────────────────────
let lists: SearchResponse<ListDoc> = client.search_lists("tolkien", Some(10)).await?;
// ── Full-text inside-book search ──────────────────────────────────────────────
let inside: SearchResponse<InsideDoc> = client.search_inside("lembas bread", Some(5)).await?;
```
#### Subjects
Subjects group works by topic, place, person, or time period. The slug must be lowercase ASCII with underscores.
```rust
use open_library_api_rs::models::search::SubjectParams;
let subject: Subject = client.get_subject("science_fiction", SubjectParams {
details: Some(true), // include related subjects, authors, publishers
ebooks: Some(false), // filter to works with e-book editions
published_in: Some("1950-1999".into()), // year range
limit: Some(20),
offset: Some(0),
}).await?;
// subject.name, subject.work_count, subject.works
// subject.authors, subject.publishers (when details=true)
// subject.related_subjects (when details=true)
```
#### Covers and Author Photos
Cover images live on a separate CDN (`covers.openlibrary.org`). The `cover_url` and `author_photo_url` methods return a `url::Url` without making any HTTP request.
```rust
use open_library_api_rs::models::common::{CoverKey, ImageSize};
// Construct a cover URL (no network call)
let url: url::Url = client.cover_url(CoverKey::Id, "5428012", ImageSize::Large);
let url: url::Url = client.cover_url(CoverKey::Isbn, "9780451450524", ImageSize::Medium);
let url: url::Url = client.cover_url(CoverKey::Olid, "OL7353617M", ImageSize::Small);
let url: url::Url = client.cover_url(CoverKey::Oclc, "45883427", ImageSize::Large);
let url: url::Url = client.cover_url(CoverKey::Lccn, "2004046975", ImageSize::Large);
// Construct an author photo URL (validates the OLID, no network call)
let url: url::Url = client.author_photo_url("OL23919A", ImageSize::Medium)?;
// Fetch cover metadata (makes a network call)
let metas: Vec<CoverMeta> = client.cover_meta(CoverKey::Id, "5428012").await?;
// meta.id, meta.url, meta.size, meta.width, meta.height
```
#### User Lists
Open Library users can create public reading lists. All list endpoints are read-only in v0.1.
```rust
// All public lists for a user
let lists: UserLists = client.get_user_lists("alice", 20, 0).await?;
// lists.lists — Vec<ListSummary> with key, name, seed_count
// lists.size — total list count
// A single list
let list: List = client.get_list("alice", "OL123L").await?;
// list.name, list.description, list.seed_count, list.edition_count, list.last_update
// Editions contained in a list (paginated)
let editions: ListEditions = client.get_list_editions("alice", "OL123L", 20, 0).await?;
// Subjects covered by the works in a list
let subjects: ListSubjects = client.get_list_subjects("alice", "OL123L").await?;
// subjects.subjects, subjects.places, subjects.people, subjects.times
// Raw seeds (works, editions, subjects) in the list
let seeds: ListSeeds = client.get_list_seeds("alice", "OL123L").await?;
```
#### Reading Log
Users' public reading shelves — want to read, currently reading, and already read.
```rust
let log: ReadingLog = client.get_want_to_read("alice").await?;
let log: ReadingLog = client.get_currently_reading("alice").await?;
let log: ReadingLog = client.get_already_read("alice").await?;
for entry in &log.reading_log_entries {
if let Some(work) = &entry.work {
println!("{}: {:?}", work.key, work.title);
}
println!(" logged: {:?}", entry.logged_date);
}
```
#### Partner / Volumes API
The partner API resolves book identifiers to availability and borrowing information.
```rust
use open_library_api_rs::models::common::VolumeIdType;
// Single volume lookup
let v: VolumesResponse = client.read_volume(VolumeIdType::Isbn, "0451450523").await?;
let v: VolumesResponse = client.read_volume(VolumeIdType::Lccn, "2004046975").await?;
let v: VolumesResponse = client.read_volume(VolumeIdType::Oclc, "45883427").await?;
let v: VolumesResponse = client.read_volume(VolumeIdType::Olid, "OL7408846M").await?;
// Batch lookup — each request is "type/value"
let requests = vec![
"isbn/0451450523".to_string(),
"oclc/45883427".to_string(),
];
let v: VolumesResponse = client.read_volumes_batch(&requests).await?;
// v.records — HashMap<String, VolumeRecord> keyed by "/books/OL…"
// v.items — Vec<VolumeItem> with status (Borrowable/Readable/Limited/Unavailable)
```
#### Recent Changes
The recent-changes feed lists edits made to Open Library records.
```rust
use open_library_api_rs::models::changes::{ChangesParams, ChangeKind};
let params = ChangesParams {
limit: Some(50),
offset: Some(0),
bot: Some(false), // exclude bot edits
};
// All recent changes
let changes: Vec<RecentChange> = client.get_recent_changes(params).await?;
// Changes on a specific date (YYYY-MM-DD)
let changes: Vec<RecentChange> = client.get_changes_by_date("2024-06-15", params).await?;
// Changes of a specific kind
let changes: Vec<RecentChange> = client.get_changes_by_kind(&ChangeKind::EditBook, params).await?;
// Combined date + kind filter
let changes: Vec<RecentChange> =
client.get_changes_by_date_and_kind("2024-06-15", &ChangeKind::AddCover, params).await?;
// Available ChangeKind values:
#### Generic Query
The `/query.json` endpoint lets you filter any Open Library object type by field values.
```rust
use std::collections::HashMap;
let mut fields = HashMap::new();
fields.insert("isbn".to_string(), "0451450523".to_string());
let results: QueryResponse = client.query(
"/type/edition",
&fields,
20, // limit
0, // offset
).await?;
// results.result — Vec<serde_json::Value>
```
#### Resource History
Fetch the full edit history (revision log) for any Open Library resource.
```rust
let history: Vec<HistoryEntry> = client.get_resource_history("/works/OL45804W").await?;
for entry in &history {
println!("r{:?} {:?} {:?}",
entry.revision, entry.timestamp, entry.comment);
}
```
---
### Error Handling
All fallible operations return `open_library_api_rs::Result<T>`, which is an alias for `std::result::Result<T, open_library_api_rs::Error>`.
```rust
use open_library_api_rs::Error;
match client.get_work("OL45804W").await {
Ok(work) => { /* ... */ }
Err(Error::InvalidInput(msg)) => {
// Input failed validation before any network call
eprintln!("bad input: {msg}");
}
Err(Error::NotFound(url)) => {
// HTTP 404
eprintln!("not found: {url}");
}
Err(Error::RateLimited) => {
// HTTP 429 — slow down
}
Err(Error::Status { code, url }) => {
// Unexpected non-2xx response
eprintln!("HTTP {code} from {url}");
}
Err(Error::Deserialize { source, body }) => {
// Response arrived but could not be parsed
eprintln!("parse error: {source}\nBody: {body}");
}
Err(Error::ResponseTooLarge) => {
// Response body exceeded the 10 MB cap
}
Err(Error::Timeout) => {
// Connect or read timeout
}
Err(Error::Http(e)) => {
// Lower-level reqwest / network error
eprintln!("network error: {e}");
}
Err(Error::Url(e)) => {
// URL parsing error (internal)
eprintln!("url error: {e}");
}
}
```
---
### Input Validation
Inputs are validated before any network call is made, returning `Error::InvalidInput` immediately on failure.
| Work OLID | `OL<digits>W` — e.g. `OL45804W` |
| Edition OLID | `OL<digits>M` — e.g. `OL7353617M` |
| Author OLID | `OL<digits>A` — e.g. `OL23919A` |
| ISBN-10 | 10 alphanumeric chars; valid Luhn-mod-11 check digit; trailing `X` allowed |
| ISBN-13 | 13 digits; prefix `978` or `979`; valid EAN-13 check digit |
| Subject slug | Non-empty; `[a-z0-9_]+`; ≤ 200 chars |
| Search query | Non-empty; ≤ 1000 chars |
| Username | Non-empty; `[a-zA-Z0-9_-]+`; ≤ 64 chars |
| List ID | Non-empty; `[a-zA-Z0-9_-]+` |
| `limit` | 1–1000 |
| Bibkey | Non-empty; prefix must be `ISBN:`, `OCLC:`, `LCCN:`, `OLID:`, or `ID:` |
| Date | `YYYY-MM-DD` format; month 1–12, day 1–31 |
| Contact email | Must contain `@`, not at start or end |
ISBN hyphens and spaces are stripped automatically for `get_edition_by_isbn`.
---
### Feature Flags
| `rustls-tls` | **on** | TLS via `rustls` (no system certs needed) |
| `native-tls` | off | TLS via the OS / OpenSSL |
| `blocking` | off | Synchronous wrappers for all async methods |
Only one TLS flag should be active at a time.
### Blocking (Sync) API
Enable the `blocking` feature and use `OpenLibraryClient::into_blocking()`:
```toml
open-library-api-rs = { version = "0.1", features = ["blocking"] }
tokio = { version = "1", features = ["rt"] }
```
```rust
use open_library_api_rs::OpenLibraryClient;
let client = OpenLibraryClient::builder().build()?.into_blocking()?;
// Every async method is available as a synchronous equivalent:
let work = client.get_work("OL45804W")?;
let results = client.search(params)?;
let edition = client.get_edition_by_isbn("9780451450524")?;
```
`BlockingClient` owns a `tokio::runtime::Runtime` internally. It is not `Clone` or `Send`; create one per thread if you need concurrency.
---
## CLI — `olib`
The `olib` binary gives command-line access to every library method. Results go to **stdout**; errors go to **stderr**; exit code is `0` on success, `1` on failure.
Use `--json` to get machine-readable JSON output that can be piped directly to `jq`.
### Installing the CLI
```bash
# From crates.io
cargo install open-library-api-rs --bin olib
# From source
cargo install --path . --bin olib
# Or build without installing
cargo build --release --bin olib
./target/release/olib --help
```
### Global Options
These flags apply to every subcommand:
| `--json` | off | Print raw pretty-printed JSON instead of formatted text |
| `--rate-limit <N>` | `1` | Requests per second sent to the API |
| `--email <EMAIL>` | none | Contact email appended to `User-Agent` header; also set `--rate-limit 3` |
```bash
# Identify your application to get the 3 req/s tier
olib --email me@example.com --rate-limit 3 work get OL45804W
# JSON output for scripting
---
### work
```
olib work <COMMAND>
Commands:
get Fetch a work by OLID
editions Fetch the editions of a work (paginated)
ratings Fetch community star ratings
bookshelves Fetch bookshelf shelf counts
```
```bash
# Get a work
olib work get OL45804W
# Get raw JSON
# List editions (first 5)
olib work editions OL45804W --limit 5
# Paginate: second page of 20
olib work editions OL45804W --limit 20 --offset 20
# Star ratings
olib work ratings OL45804W
# Bookshelf counts (want-to-read / currently-reading / already-read)
olib work bookshelves OL45804W
```
---
### edition
```
olib edition <COMMAND>
Commands:
get Fetch an edition by OLID
isbn Fetch an edition by ISBN-10 or ISBN-13
```
```bash
# By OLID
olib edition get OL7353617M
# By ISBN-10 (hyphens ignored)
olib edition isbn 0-14-032-872-6
# By ISBN-13
olib edition isbn 9780140328724
# JSON output
---
### author
```
olib author <COMMAND>
Commands:
get Fetch an author by OLID
works Fetch the works attributed to an author (paginated)
```
```bash
# Get an author
olib author get OL23919A
# Get their works
olib author works OL23919A --limit 20
# Page 2
olib author works OL23919A --limit 20 --offset 20
# JSON
---
### search
```
olib search <COMMAND>
Commands:
books Search books and works
authors Search authors
subjects Search subjects
lists Search user-created lists
inside Full-text search inside book content
```
```bash
# Book search — free-text
olib search books --q "lord of the rings"
# With filters
olib search books --author tolkien --language eng --limit 5
# Sort options: relevance (default) | new | old
olib search books --q "dune" --sort new --limit 10
# Filter by title, isbn, subject, place, person
olib search books --title "Foundation" --author "Asimov"
olib search books --q "python" --subject "programming"
# Author search
olib search authors --q "ursula le guin" --limit 5
# Subject search
olib search subjects "artificial intelligence"
# List search
olib search lists "classic literature" --limit 5
# Inside search (scans book text)
olib search inside "call me ishmael" --limit 3
# JSON output
---
### subject
```
olib subject <SLUG> [OPTIONS]
Options:
--details Include related subjects, authors, publishers
--ebooks Only return works with e-book editions
--published-in Year range, e.g. 1950-1999
--limit Results per page (default: 20)
--offset Pagination offset (default: 0)
```
Subject slugs are lowercase with underscores (e.g. `science_fiction`, `world_war_2`).
```bash
# Basic subject page
olib subject love
# With related subjects, authors, publishers
olib subject science_fiction --details
# Only e-books published in the 1980s
olib subject cyberpunk --ebooks --published-in 1980-1989
# Paginate
olib subject mystery --limit 10 --offset 30
# JSON
---
### cover
```
olib cover <COMMAND>
Commands:
url Print a cover image URL (no network call)
meta Fetch cover image metadata (size, dimensions)
photo Print an author photo URL (no network call)
```
```bash
# Cover URL by internal cover ID
olib cover url id 5428012 large
# Cover URL by ISBN
olib cover url isbn 9780451450524 medium
# Cover URL by OLID
olib cover url olid OL7353617M large
# Cover metadata (width, height, URL)
olib cover meta id 5428012
# Author photo URL (validates OLID)
olib cover photo OL23919A large
# Pipe the URL straight to curl
curl -L "$(olib cover url id 5428012 large)" -o cover.jpg
```
---
### list
```
olib list <COMMAND>
Commands:
user List a user's public lists
show Fetch a single list
editions Fetch editions in a list (paginated)
subjects Fetch subjects covered by a list
seeds Fetch raw seeds (items) in a list
```
```bash
# List all public lists for a user
olib list user alice
# Paginate
olib list user alice --limit 10 --offset 10
# Show a specific list
olib list show alice OL123L
# Editions contained in the list
olib list editions alice OL123L --limit 20
# Subjects derived from the list
olib list subjects alice OL123L
# Raw seeds (work keys, edition keys, subject URLs)
olib list seeds alice OL123L
# JSON
---
### reading
```
olib reading <COMMAND>
Commands:
want-to-read Books on the want-to-read shelf
currently-reading Books currently being read
already-read Books already finished
```
```bash
olib reading want-to-read alice
olib reading currently-reading alice
olib reading already-read alice
# JSON
---
### changes
```
olib changes [OPTIONS]
Options:
--date <DATE> Filter to a specific date (YYYY-MM-DD)
--kind <KIND> Filter by change type
--limit <N> Results per page (default: 20)
--offset <N> Pagination offset (default: 0)
```bash
# All recent changes (last 20)
olib changes
# Changes on a specific date
olib changes --date 2024-06-15
# Changes of a specific type
olib changes --kind edit-book --limit 50
# Date + kind
olib changes --date 2024-06-15 --kind add-cover
# Exclude bot edits
olib changes --bot false --limit 100
# JSON
---
### volume
```
olib volume <COMMAND>
Commands:
isbn Look up a volume by ISBN
lccn Look up a volume by Library of Congress Control Number
oclc Look up a volume by OCLC number
olid Look up a volume by Open Library edition OLID
batch Look up multiple volumes at once
```
```bash
# Single lookups
olib volume isbn 0451450523
olib volume lccn 2004046975
olib volume oclc 45883427
olib volume olid OL7408846M
# Batch: each argument is "type/value"
olib volume batch isbn/0451450523 oclc/45883427
# JSON (includes borrowability status)
---
### books
Bibkey-based lookup using the `/api/books` endpoint. Supports multiple keys in one request.
```
olib books <BIBKEY>... [--jscmd data|details|viewapi]
```
| `data` (default) | Full bibliographic data bundle |
| `details` | Structured details including subjects and excerpts |
| `viewapi` | Preview and read-online links |
```bash
# Single key
olib books ISBN:0451450523
# Multiple keys
olib books ISBN:0451450523 OCLC:45883427 LCCN:2004046975
# With details jscmd
olib books ISBN:0451450523 --jscmd details
# Valid prefixes: ISBN: OCLC: LCCN: OLID: ID:
olib books OLID:OL7408846M ID:5428012
# JSON
---
### query
Generic query against any Open Library object type via `/query.json`.
```
olib query --type <TYPE> [--field KEY=VALUE]... [--limit N] [--offset N]
```
```bash
# Find editions by ISBN
olib query --type /type/edition --field isbn=0451450523
# Find works by title
olib query --type /type/work --field title="Dune"
# Multiple fields
olib query --type /type/edition --field publishers=Penguin --field publish_date=1988
# JSON
---
### history
Fetch the full revision history of any Open Library resource.
```
olib history <KEY>
```
The key is an Open Library path, e.g. `/works/OL45804W` or `/books/OL7353617M`.
```bash
olib history /works/OL45804W
olib history /books/OL7353617M
olib history /authors/OL23919A
# JSON
---
## Security
| TLS | `rustls` with full certificate verification; system CA bundle not required; `native-tls` available as an opt-in feature |
| Timeouts | Connect: 10 s; total request: 30 s — both configurable via builder |
| Body cap | Responses truncated at 10 MB before deserialization; `Error::ResponseTooLarge` returned |
| Unsafe | `#![forbid(unsafe_code)]` — no unsafe Rust anywhere in the crate |
| URL construction | All path and query values routed through the `url` crate — no `format!("{base}/{user_input}")` |
| User-Agent | Statically set to `open-library-api-rs/0.1.0`; optional contact email appended when provided |
| Input validation | All user-supplied strings validated before making any network call |
---
## Rate Limits
| Anonymous (no User-Agent) | 1 req/s |
| Identified (name + email in User-Agent) | 3 req/s |
| Covers API via non-OLID/ID keys | 100 req per 5 min per IP |
The client ships with a built-in token-bucket rate limiter (`governor`). Set `rate_limit(3)` and `contact_email(...)` together to use the identified tier. The limiter is enforced locally; if the server still returns HTTP 429, the library returns `Error::RateLimited`.
---
## Building from Source
```bash
git clone https://github.com/aVOIDSTARch/open-library-api-rs.git
cd open-library-api-rs
# Build the library
cargo build
# Build the CLI binary
cargo build --bin olib --release
# Run the full test suite (unit + integration + CLI + doctest)
cargo test
# Run only integration tests
cargo test --test integration
# Check for warnings
cargo clippy -- -D warnings
# Build with the blocking feature
cargo build --features blocking
# Build with native-tls instead of rustls
cargo build --no-default-features --features native-tls
# Generate docs
cargo doc --no-deps --open
```
---
## License
MIT. See [LICENSE](LICENSE).
Open Library data is provided by the [Internet Archive](https://archive.org) and is available under the [Open Library API Terms](https://openlibrary.org/developers/api). Please attribute the Internet Archive / Open Library when using this data in your application, and provide a contact email via `contact_email()` / `--email` so they can reach you if needed.