# FullDatasets API and MCP

Canonical books data for product catalogs, search, and agents. Same Bearer key on REST and MCP.

- HTML docs: https://fulldatasets.com/docs
- This file (full spec): https://fulldatasets.com/docs.md
- Page index: https://fulldatasets.com/llms.txt
- OpenAPI: https://api.fulldatasets.com/openapi.json
- MCP: https://mcp.fulldatasets.com/mcp
- Request a key: https://fulldatasets.com/#signup

Release **2026-09.2** (`books.v1`). Source is Open Library.

## Pages

- [Introduction](https://fulldatasets.com/docs/index.md) — Canonical books data over REST and MCP.
- [Quickstart](https://fulldatasets.com/docs/quickstart.md) — One ISBN call and a title match.
- [Authentication](https://fulldatasets.com/docs/authentication.md) — Bearer keys, 401, and 429.
- [Choose an endpoint](https://fulldatasets.com/docs/choose.md) — ISBN vs match vs search vs ids.
- [Data model](https://fulldatasets.com/docs/data-model.md) — Works, editions, authors, and the book object.
- [Look up an ISBN](https://fulldatasets.com/docs/isbn.md) — Exact ISBN-10 / ISBN-13 lookup.
- [Get a book](https://fulldatasets.com/docs/books.md) — Edition by fds_ed_ id.
- [Get a work](https://fulldatasets.com/docs/works.md) — Work record and synopsis.
- [Get an author](https://fulldatasets.com/docs/authors.md) — Contributor by fds_au_ id.
- [Search books](https://fulldatasets.com/docs/search.md) — Full-text browse with filters.
- [Resolve a title](https://fulldatasets.com/docs/match.md) — Ranked candidates for a messy title.
- [Changelog](https://fulldatasets.com/docs/changes.md) — Incremental cursor. Empty on 2026-09.2.
- [Bulk download](https://fulldatasets.com/docs/bulk.md) — Release manifest and parquet URLs.
- [Connect MCP](https://fulldatasets.com/docs/mcp.md) — Cursor config and which tool to call.
- [MCP tools](https://fulldatasets.com/docs/mcp-tools.md) — Arguments and REST mappings for every tool.
- [Errors](https://fulldatasets.com/docs/errors.md) — Status codes, error.code, and redirects.
- [Rate limits and cache](https://fulldatasets.com/docs/limits.md) — 120 req/min and the 24h point-lookup cache.
- [Known gaps](https://fulldatasets.com/docs/gaps.md) — What this release does not cover.

---

# Introduction

FullDatasets is a canonical **books** dataset over REST and MCP. One Bearer key works on both.

| Item | Value |
|---|---|
| Release | `2026-09.2` (`books.v1`) |
| Source | Open Library. Library of Congress and BookBrainz are not in this release. |
| REST | `https://api.fulldatasets.com` |
| MCP | `https://mcp.fulldatasets.com/mcp` |
| OpenAPI | https://api.fulldatasets.com/openapi.json |
| Agent spec (this site, one file) | https://fulldatasets.com/docs.md |
| Page index | https://fulldatasets.com/llms.txt |

Keys are issued by hand in v1. [Request one](https://fulldatasets.com/#signup).

## Start here

1. [Quickstart](https://fulldatasets.com/docs/quickstart.md) — one ISBN call in under a minute
2. [Choose an endpoint](https://fulldatasets.com/docs/choose.md) — pick the right call
3. [Connect MCP](https://fulldatasets.com/docs/mcp.md) — Cursor / agent setup

Press **⌘K** (Ctrl+K on Windows/Linux) to search every page. Slash (`/`) also opens search.

## Check record

ISBN `9780593135204` — *Project Hail Mary* (Andy Weir).

You should get a synopsis, the author name, edition `fds_ed_37306635333664393039`, and work `fds_wk_61363037333564616531`.

## Agents

Do not scrape the HTML. Fetch markdown:

- Full spec: `https://fulldatasets.com/docs.md`
- This page: `https://fulldatasets.com/docs/index.md`
- Index of every page: `https://fulldatasets.com/llms.txt`

Prefer `lookup_isbn` when the user has an ISBN. Prefer `resolve_book` for a known title. Do not full-text search an ISBN.

---

# Quickstart

Get a book back in one request.

## 1. Get a key

Keys look like `fds_live_<kid>_<secret>`. They are issued manually in v1 — [request access](https://fulldatasets.com/#signup).

Export it:

```bash
export FDS_API_KEY='fds_live_…'
```

## 2. Look up an ISBN

```bash
curl -sS -H "Authorization: Bearer $FDS_API_KEY" \
  https://api.fulldatasets.com/v1/isbn/9780593135204
```

You should see `Project Hail Mary`, `authors: ["Andy Weir"]`, and a `description`.

Hyphens are fine. ISBN-10 `0593135202` resolves to the same edition.

## 3. Resolve a title (no ISBN)

```bash
curl -sS -H "Authorization: Bearer $FDS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"title":"Project Hail Mary","author":"Andy Weir"}' \
  https://api.fulldatasets.com/v1/match
```

Take `candidates[0]`. That is the canonical edition for the title.

## 4. Connect an agent

Same key, MCP endpoint `https://mcp.fulldatasets.com/mcp`. See [Connect MCP](https://fulldatasets.com/docs/mcp.md).

## Next

- [Authentication](https://fulldatasets.com/docs/authentication.md) — header, 401, 429
- [Choose an endpoint](https://fulldatasets.com/docs/choose.md) — ISBN vs match vs search
- [Book object](https://fulldatasets.com/docs/data-model.md) — fields you will keep seeing

---

# Authentication

Every `/v1` route requires a Bearer key.

```http
Authorization: Bearer fds_live_<kid>_<secret>
```

The value is the word `Bearer`, one space, then the key. Do not send the key twice. Do not put `Bearer ` inside the key.

Public (no auth):

- `GET https://api.fulldatasets.com/health`
- `GET https://api.fulldatasets.com/openapi.json`

MCP uses the same header on `https://mcp.fulldatasets.com/mcp`.

## Key format

```
fds_live_<kid>_<secret>
```

| Part | Meaning |
|---|---|
| `fds_live` | Environment prefix (v1 is live only) |
| `kid` | Key id. Used for rate limits and revocation lookup |
| `secret` | Secret. Hashed at rest; treat it like a password |

Keys are issued by hand. [Request one](https://fulldatasets.com/#signup). There is no self-serve dashboard in v1.

## Failures

| Status | When |
|---|---|
| `401` | Missing header, malformed key, unknown `kid`, or revoked |
| `429` | More than **120 requests / 60 seconds** for this `kid` |

Error body:

```json
{ "error": { "code": "rate_limited", "message": "…" } }
```

See [Errors](https://fulldatasets.com/docs/errors.md).

## Usage

Each authenticated `/v1` call is metered asynchronously (API → queue → Postgres). Do not treat usage rows as a synchronous response field. `/health` and `/openapi.json` are not billed.

## MCP

Cursor (and any streamable-HTTP client):

```json
{
  "mcpServers": {
    "fulldatasets": {
      "url": "https://mcp.fulldatasets.com/mcp",
      "headers": {
        "Authorization": "Bearer fds_live_<kid>_<secret>"
      }
    }
  }
}
```

If initialize fails with 401, the header is wrong — not the tool names.

---

# Choose an endpoint

Pick **one** call. Do not search when you already have an ISBN or an id.

| You have | REST | MCP | Why |
|---|---|---|---|
| ISBN-10 or ISBN-13 | [`GET /v1/isbn/:isbn`](https://fulldatasets.com/docs/isbn.md) | `lookup_isbn` | Exact, cached, cheapest |
| Edition id `fds_ed_…` | [`GET /v1/books/:id`](https://fulldatasets.com/docs/books.md) | `get_book` | Point lookup |
| Work id `fds_wk_…` | [`GET /v1/works/:id`](https://fulldatasets.com/docs/works.md) | `get_work` | Synopsis lives on the work |
| Author id `fds_au_…` | [`GET /v1/authors/:id`](https://fulldatasets.com/docs/authors.md) | — | Contributor name |
| A title to identify *the* book | [`POST /v1/match`](https://fulldatasets.com/docs/match.md) | `resolve_book` | Ranked candidates; take the first |
| Exploratory / filtered browse | [`GET /v1/books/search`](https://fulldatasets.com/docs/search.md) | `search_books` | Not the first choice for a known title |
| Sync since a cursor | [`GET /v1/changes`](https://fulldatasets.com/docs/changes.md) | `get_changes` | Empty on this first release |
| Parquet dump | [`GET /v1/datasets/books/releases/latest`](https://fulldatasets.com/docs/bulk.md) | — | Same Bearer key |

## Decision rules

1. **ISBN present** → `lookup_isbn` / `GET /v1/isbn/:isbn`. Never pass an ISBN as `q` on search.
2. **Stable id present** → the matching `GET /v1/{books|works|authors}/:id`.
3. **User named a book** (“the Andy Weir Hail Mary”) → `resolve_book` / `POST /v1/match`. Read `candidates[0]`.
4. **User is browsing** (“sci-fi from 2021”) → `search_books` with filters.
5. **You are syncing a cache** → `get_changes`. On release `2026-09.2` the list is empty.

## Ranking note

Search and match drop “Summary of…” / study-guide titles when a real title exists, then collapse to **one canonical edition** for that title. A box set with a different title can still appear as a later hit.

## Check record

`9780593135204` / `Project Hail Mary` / Andy Weir. Expect a description and a non-empty `authors` array.

---

# Data model

```
work (fds_wk_…)  →  edition (fds_ed_…)  →  ISBN / product
contributor (fds_au_…)
```

| Entity | Prefix | What it is |
|---|---|---|
| Work | `fds_wk_` | Abstract title. Synopsis lives here. |
| Edition | `fds_ed_` | A specific publication. What product UIs want. |
| Contributor | `fds_au_` | Author / editor / etc. |

Ids are stable across releases. If two records merge, the oldest id survives and the retired id **301**s. Follow redirects.

About **8.4M** of **78.2M** works have a `description` in this release.

## Book object

Returned by ISBN, book id, search hits, and match candidates.

```json
{
  "id": "fds_ed_37306635333664393039",
  "work_id": "fds_wk_61363037333564616531",
  "title": "Project Hail Mary",
  "subtitle": null,
  "authors": ["Andy Weir"],
  "isbn13": "9780593135204",
  "isbn10": "0593135202",
  "publication_date": null,
  "publisher": null,
  "language": "eng",
  "subjects": [],
  "page_count": null,
  "format": null,
  "identifiers": [],
  "description": "Ryland Grace is the sole survivor…",
  "description_source": "open_library.description",
  "release": "2026-09.2",
  "schema_version": "books.v1"
}
```

| Field | Notes |
|---|---|
| `id` | Edition id |
| `work_id` | Parent work |
| `title`, `subtitle` | Edition title. `subtitle` may be null |
| `authors` | Display names. Open Library `/authors/OL…` keys are resolved on read. Some editions are still `[]` |
| `isbn13`, `isbn10` | May be null |
| `publication_date`, `publisher`, `language`, `subjects`, `page_count`, `format` | Often sparse |
| `identifiers` | `{ namespace, value }` pairs |
| `description`, `description_source` | From the parent work (`open_library.description` when present) |
| `release` | e.g. `2026-09.2` |
| `schema_version` | Always `books.v1` |

## Work object

```json
{
  "id": "fds_wk_61363037333564616531",
  "title": "Project Hail Mary",
  "subtitle": null,
  "language": "eng",
  "subjects": [],
  "series": null,
  "description": "Ryland Grace is the sole survivor…",
  "description_source": "open_library.description",
  "release": "2026-09.2"
}
```

## Contributor object

```json
{
  "id": "fds_au_37316331633930383534",
  "name": "Andy Weir",
  "sort_name": "Weir, Andy",
  "release": "2026-09.2"
}
```

---

# Look up an ISBN

Exact edition lookup. Use this whenever you have an ISBN-10 or ISBN-13.

MCP: `lookup_isbn` `{ "isbn": "9780593135204" }`

## When to use

- The user pasted an ISBN
- You are joining a catalog row that already has an ISBN

Do **not** pass an ISBN as `q` on [search](https://fulldatasets.com/docs/search.md).

## Request

```http
GET /v1/isbn/9780593135204
Authorization: Bearer fds_live_<kid>_<secret>
```

```bash
curl -sS -H "Authorization: Bearer $FDS_API_KEY" \
  https://api.fulldatasets.com/v1/isbn/9780593135204
```

| Path | Required | Notes |
|---|---|---|
| `isbn` | yes | ISBN-10 or ISBN-13. Hyphens and spaces are stripped. Canonicalized to ISBN-13 for lookup. |

## Response `200`

A [book object](https://fulldatasets.com/docs/data-model.md). Check record:

| Field | Value |
|---|---|
| `id` | `fds_ed_37306635333664393039` |
| `work_id` | `fds_wk_61363037333564616531` |
| `title` | `Project Hail Mary` |
| `authors` | `["Andy Weir"]` |
| `isbn13` | `9780593135204` |
| `description` | present |

`X-Cache: HIT` or `MISS`. Point lookups cache 24h after auth.

## Errors

| Status | `code` | When |
|---|---|---|
| `400` | `bad_request` | String is not a valid ISBN |
| `401` | — | Bad or missing key |
| `404` | `not_found` | Valid ISBN, no edition |
| `429` | `rate_limited` | Over 120 req / 60s |

## See also

- [Match](https://fulldatasets.com/docs/match.md) — title, no ISBN
- [Get a book](https://fulldatasets.com/docs/books.md) — you already have `fds_ed_…`

---

# Get a book

Edition by FullDatasets id.

MCP: `get_book` `{ "id": "fds_ed_37306635333664393039" }`

## When to use

You already stored an `fds_ed_…` from ISBN, match, or search. For a raw ISBN, use [ISBN lookup](https://fulldatasets.com/docs/isbn.md) instead.

## Request

```bash
curl -sS -H "Authorization: Bearer $FDS_API_KEY" \
  https://api.fulldatasets.com/v1/books/fds_ed_37306635333664393039
```

| Path | Required | Notes |
|---|---|---|
| `id` | yes | Edition id (`fds_ed_…`) |

## Response

- `200` — [book object](https://fulldatasets.com/docs/data-model.md)
- `301` — id was merged; `Location` is `/v1/books/{surviving_id}`. Follow it.
- `404` — unknown id

Cached 24h after auth (`X-Cache`).

## See also

- [Get a work](https://fulldatasets.com/docs/works.md) — synopsis on `work_id`
- [Data model](https://fulldatasets.com/docs/data-model.md) — redirects and prefixes

---

# Get a work

Abstract title. This is where `description` / `description_source` live. About **8.4M** of **78.2M** works have a synopsis in this release.

MCP: `get_work` `{ "id": "fds_wk_61363037333564616531" }`

## When to use

You have a `work_id` from a book object and want the work record (or to re-fetch the synopsis). Book / ISBN / search / match already join the parent description onto the edition — you usually do not need this call for product UI.

## Request

```bash
curl -sS -H "Authorization: Bearer $FDS_API_KEY" \
  https://api.fulldatasets.com/v1/works/fds_wk_61363037333564616531
```

| Path | Required | Notes |
|---|---|---|
| `id` | yes | Work id (`fds_wk_…`) |

## Response `200`

```json
{
  "id": "fds_wk_61363037333564616531",
  "title": "Project Hail Mary",
  "subtitle": null,
  "language": "eng",
  "subjects": [],
  "series": null,
  "description": "Ryland Grace is the sole survivor…",
  "description_source": "open_library.description",
  "release": "2026-09.2"
}
```

- `301` — merged work; follow `Location`
- `404` — unknown id

Cached 24h after auth.

## See also

- [Get a book](https://fulldatasets.com/docs/books.md)
- [Data model](https://fulldatasets.com/docs/data-model.md)

---

# Get an author

Contributor by FullDatasets id. There is no MCP tool for this yet — call REST.

## When to use

You have an `fds_au_…` id. Book objects return **display names** in `authors`, not ids. This route is for the contributor record itself.

## Request

```bash
curl -sS -H "Authorization: Bearer $FDS_API_KEY" \
  https://api.fulldatasets.com/v1/authors/fds_au_37316331633930383534
```

| Path | Required | Notes |
|---|---|---|
| `id` | yes | Contributor id (`fds_au_…`) |

## Response `200`

```json
{
  "id": "fds_au_37316331633930383534",
  "name": "Andy Weir",
  "sort_name": "Weir, Andy",
  "release": "2026-09.2"
}
```

- `301` — merged contributor; follow `Location`
- `404` — unknown id

Cached 24h after auth.

## See also

- [Data model](https://fulldatasets.com/docs/data-model.md)
- [Search](https://fulldatasets.com/docs/search.md) — `author` query filter (name, not id)

---

# Search books

Full-text search over editions. Use this to **browse**. To identify a known title, use [match](https://fulldatasets.com/docs/match.md) instead.

MCP: `search_books` `{ "q": "Project Hail Mary", "limit": 5 }`

## When to use

- Filtered exploration (`year`, `language`, `subject`)
- “Show me books like…”

Do **not** search an ISBN. Do **not** use search as the first call when the user named one book — [match](https://fulldatasets.com/docs/match.md) ranks and collapses better for that.

## Request

```bash
curl -sS -H "Authorization: Bearer $FDS_API_KEY" \
  "https://api.fulldatasets.com/v1/books/search?q=Project%20Hail%20Mary&limit=5"
```

| Query | Required | Notes |
|---|---|---|
| `q` | yes | Title-ish query |
| `author` | no | Folded into the FTS query |
| `year` | no | `publication_year` |
| `language` | no | e.g. `eng` |
| `subject` | no | Exact subject tag |
| `limit` | no | Default `20`, max `100` |
| `cursor` | no | Pagination from `next_cursor` |

## Response `200`

```json
{
  "results": [{ "id": "fds_ed_…", "title": "Project Hail Mary" }],
  "next_cursor": "fds_ed_…"
}
```

Each item is a [book object](https://fulldatasets.com/docs/data-model.md). `next_cursor` is `null` when there is no next page.

## Ranking

1. Exact title first
2. “Summary of…” / study guides dropped when a real title exists
3. One canonical edition for that title

A box set with a different title can still appear as a later hit.

Search is short-TTL and hits Postgres (not the 24h point-lookup cache).

## Errors

| Status | `code` | When |
|---|---|---|
| `400` | `bad_request` | Missing `q` |

## See also

- [Match](https://fulldatasets.com/docs/match.md) — resolve *the* book
- [ISBN](https://fulldatasets.com/docs/isbn.md) — exact product

---

# Resolve a title

Turn a messy title (and optional author or ISBN) into ranked edition candidates.

MCP: `resolve_book` `{ "title": "Project Hail Mary", "author": "Andy Weir" }`

## When to use

- The user named a book
- You have a title string from another system and need a stable `fds_ed_…`

If you already have an ISBN, call [ISBN lookup](https://fulldatasets.com/docs/isbn.md) (or pass `isbn` here — it short-circuits). Do not use [search](https://fulldatasets.com/docs/search.md) as the first choice for a single known title.

## Request

```bash
curl -sS -H "Authorization: Bearer $FDS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"title":"Project Hail Mary","author":"Andy Weir","limit":5}' \
  https://api.fulldatasets.com/v1/match
```

| Body | Required | Notes |
|---|---|---|
| `isbn` | one of `isbn` or `title` | If valid and found, returns one `exact_isbn` candidate (score `1`) |
| `title` | one of `isbn` or `title` | Required when `isbn` is absent |
| `author` | no | Folded into FTS |
| `limit` | no | Default `10`, max `25` |

## Response `200`

```json
{
  "candidates": [
    {
      "entity_id": "fds_ed_37306635333664393039",
      "entity_type": "edition",
      "score": 1,
      "match_method": "exact_isbn",
      "book": {
        "id": "fds_ed_37306635333664393039",
        "title": "Project Hail Mary",
        "authors": ["Andy Weir"]
      }
    }
  ]
}
```

`match_method` is `exact_isbn` or `fuzzy_title_author`. **Prefer `candidates[0]`.**

Same ranking collapse as search: exact title first, summaries dropped, one canonical edition per title. A box set with a different title can still appear later.

Match hits Postgres (not the 24h point-lookup cache).

## Errors

| Status | `code` | When |
|---|---|---|
| `400` | `bad_request` | Invalid JSON, or neither `title` nor `isbn` |

## See also

- [Search](https://fulldatasets.com/docs/search.md) — browse / filters
- [Choose an endpoint](https://fulldatasets.com/docs/choose.md)

---

# Changelog

Incremental cursor for caches that want to follow the release stream.

MCP: `get_changes` `{ "since": "<cursor>" }`

## When to use

You already ingested a release and want creates / updates / deletes / merges since a cursor.

**Release `2026-09.2` is the first load.** This list is **empty** until the next release. Do not poll it expecting Hail Mary to appear.

## Request

```bash
curl -sS -H "Authorization: Bearer $FDS_API_KEY" \
  "https://api.fulldatasets.com/v1/changes?since=0&limit=100"
```

| Query | Required | Notes |
|---|---|---|
| `since` | yes | Opaque cursor from the previous `next_cursor`, or a starting token you persist |
| `limit` | no | Default `100`, max `1000` |

## Response `200`

```json
{
  "changes": [
    {
      "entity_id": "fds_ed_…",
      "entity_type": "edition",
      "op": "update",
      "content_hash_before": "…",
      "content_hash_after": "…",
      "merged_into": null,
      "release": "2026-09.3",
      "observed_at": "2026-09-01T00:00:00.000Z",
      "cursor": "…"
    }
  ],
  "next_cursor": null
}
```

| `entity_type` | `work` · `edition` · `contributor` |
| `op` | `create` · `update` · `delete` · `merge` |

On `merge`, `merged_into` is the surviving id. Follow [redirects](https://fulldatasets.com/docs/data-model.md).

Short-TTL cache (30s / 60s CDN).

## Errors

| Status | `code` | When |
|---|---|---|
| `400` | `bad_request` | Missing `since` |

## See also

- [Bulk download](https://fulldatasets.com/docs/bulk.md) — full snapshot instead of a cursor

---

# Bulk download

Manifest plus time-limited download URLs for parquet files. Same Bearer key as the rest of the API. No MCP tool.

Resale of the dataset is prohibited. Dumps are for your own systems.

## Request

```bash
curl -sS -H "Authorization: Bearer $FDS_API_KEY" \
  https://api.fulldatasets.com/v1/datasets/books/releases/latest
```

## Response `200`

```json
{
  "release": "2026-09.2",
  "schema_version": "books.v1",
  "created_at": "…",
  "manifest": {},
  "downloads": {
    "manifest.json": { "url": "https://api.fulldatasets.com/v1/datasets/books/releases/2026-09.2/files/manifest.json", "expires_in": 3600 },
    "books.parquet": { "url": "…", "expires_in": 3600 },
    "changes.parquet": { "url": "…", "expires_in": 3600 },
    "redirects.parquet": { "url": "…", "expires_in": 3600 }
  }
}
```

Fetch each `downloads.*.url` with the **same** `Authorization` header. URLs expire in 3600 seconds. File route:

`GET /v1/datasets/books/releases/{release}/files/{file}`

| File | Contents |
|---|---|
| `manifest.json` | Release metadata |
| `books.parquet` | Denormalized bulk table |
| `changes.parquet` | Changelog snapshot |
| `redirects.parquet` | Retired id → surviving id |

`404` if no release is loaded or the file name is unknown.

## See also

- [Changelog](https://fulldatasets.com/docs/changes.md) — incremental after you have a snapshot
- [Known gaps](https://fulldatasets.com/docs/gaps.md)

---

# Connect MCP

Endpoint: `https://mcp.fulldatasets.com/mcp`

Transport: streamable HTTP (JSON-RPC). **Same Bearer key** as REST.

Read the tool list in [MCP tools](https://fulldatasets.com/docs/mcp-tools.md). Full agent spec: https://fulldatasets.com/docs.md

## Cursor

```json
{
  "mcpServers": {
    "fulldatasets": {
      "url": "https://mcp.fulldatasets.com/mcp",
      "headers": {
        "Authorization": "Bearer fds_live_<kid>_<secret>"
      }
    }
  }
}
```

The header value is `Bearer` plus one space plus the key. Do not nest a second `Bearer`.

After connect, `initialize` returns `instructions` that point at this docs set. `tools/list` is the live tool catalog.

## Which tool

| Situation | Tool |
|---|---|
| User gave an ISBN | `lookup_isbn` |
| User named a book | `resolve_book` — take the first candidate |
| You have `fds_ed_…` | `get_book` |
| You have `fds_wk_…` and want the work / synopsis | `get_work` |
| Browse or filter | `search_books` |
| Sync a cursor | `get_changes` (empty on `2026-09.2`) |

There is no author tool. Use REST [`GET /v1/authors/:id`](https://fulldatasets.com/docs/authors.md).

## Check

`lookup_isbn` `9780593135204` should return *Project Hail Mary*, Andy Weir, and a description.

## Auth failures

`401` on `POST /mcp` means the header is wrong or the key is revoked. Tool names are not the problem.

Rate limits are enforced on the API behind the service binding (120 / 60s per key), same as REST.

## See also

- [MCP tools](https://fulldatasets.com/docs/mcp-tools.md) — arguments and return shapes
- [Authentication](https://fulldatasets.com/docs/authentication.md)
- [Choose an endpoint](https://fulldatasets.com/docs/choose.md)

---

# MCP tools

Live endpoint: `https://mcp.fulldatasets.com/mcp`. Setup: [Connect MCP](https://fulldatasets.com/docs/mcp.md).

Each tool is a thin pass-through to REST. Errors come back as the API JSON (`error.code`) inside the tool text.

Call **one** tool. Do not scrape HTML docs.

## `lookup_isbn`

Maps to `GET /v1/isbn/{isbn}`.

Use when the user (or a row) has an ISBN-10 or ISBN-13. Do not use `search_books` for an ISBN.

| Argument | Required | Notes |
|---|---|---|
| `isbn` | yes | Hyphens ok |

Returns a [book object](https://fulldatasets.com/docs/data-model.md).

## `resolve_book`

Maps to `POST /v1/match`.

Use when the user named a book and you need *the* edition. Take `candidates[0]`.

| Argument | Required | Notes |
|---|---|---|
| `title` | one of `title` or `isbn` | |
| `isbn` | one of `title` or `isbn` | Short-circuits to `exact_isbn` |
| `author` | no | Improves ranking |
| `limit` | no | Default `10`, max `25` |

## `get_book`

Maps to `GET /v1/books/{id}`.

| Argument | Required | Notes |
|---|---|---|
| `id` | yes | `fds_ed_…` |

## `get_work`

Maps to `GET /v1/works/{id}`.

| Argument | Required | Notes |
|---|---|---|
| `id` | yes | `fds_wk_…` |

Use when you already have a work id and want the work record / synopsis. Book tools already include the parent `description`.

## `search_books`

Maps to `GET /v1/books/search`.

Browse or filter. Not the first choice for a known title.

| Argument | Required | Notes |
|---|---|---|
| `q` | yes | Title-ish query |
| `author` | no | |
| `year` | no | Number |
| `language` | no | e.g. `eng` |
| `subject` | no | Exact tag |
| `limit` | no | Default `20`, max `100` |

## `get_changes`

Maps to `GET /v1/changes`.

| Argument | Required | Notes |
|---|---|---|
| `since` | yes | Cursor |
| `limit` | no | Default `100`, max `1000` |

Empty on release `2026-09.2`.

## Not exposed

| REST | Why |
|---|---|
| `GET /v1/authors/:id` | Call REST if you have `fds_au_…` |
| Bulk parquet | Call REST [`/v1/datasets/books/releases/latest`](https://fulldatasets.com/docs/bulk.md) |

---

# Errors

Every error is JSON:

```json
{ "error": { "code": "not_found", "message": "ISBN 9780000000000 not found" } }
```

`message` is safe to log. Do not parse it. Branch on `code` and status.

| Status | `code` | Meaning |
|---|---|---|
| `400` | `bad_request` | Invalid ISBN, missing `q` / `since` / `title|isbn`, bad JSON |
| `401` | — | Missing, malformed, unknown, or revoked key |
| `404` | `not_found` | Valid request, no row (or no release / file) |
| `429` | `rate_limited` | More than 120 requests in 60 seconds for this key |
| `500` | `internal` | Server fault — retry with backoff |

MCP tools return the same JSON as text in the tool result. A 401 on `POST /mcp` never reaches a tool.

## Redirects

Point lookups on a **merged** id return `301` with `Location` pointing at the surviving entity (`/v1/books/…`, `/v1/works/…`, or `/v1/authors/…`). Follow them. Ids stay stable; the old id remains as a redirect.

## Validation examples

| Call | Typical `400` |
|---|---|
| `GET /v1/isbn/not-an-isbn` | Invalid ISBN |
| `GET /v1/books/search` (no `q`) | `q is required` |
| `POST /v1/match` `{}` | `title or isbn required` |
| `GET /v1/changes` (no `since`) | `since cursor is required` |

## See also

- [Rate limits and cache](https://fulldatasets.com/docs/limits.md)
- [Authentication](https://fulldatasets.com/docs/authentication.md)

---

# Rate limits and cache

## Rate limit

**120 requests / 60 seconds / key** (`kid`). Over the limit:

```json
{ "error": { "code": "rate_limited", "message": "…" } }
```

Status `429`. MCP calls count against the same budget (they hit the API over a service binding).

`/health` and `/openapi.json` are not rate-limited.

## Point-lookup cache

These GETs are cached at the Worker **after** auth. The cache key is the URL only (24h).

- `GET /v1/isbn/:isbn`
- `GET /v1/books/:id`
- `GET /v1/works/:id`
- `GET /v1/authors/:id`

Response header: `X-Cache: HIT` or `MISS`.

Hot ISBN lookups should land well under 200ms on a hit. Misses pay Hyperdrive + join.

Search, match, and changes are short-TTL and hit Postgres.

## Metering

Authenticated `/v1` calls are written to `usage_events` asynchronously. Do not expect a usage field on the response.

## See also

- [Errors](https://fulldatasets.com/docs/errors.md)
- [Known gaps](https://fulldatasets.com/docs/gaps.md)

---

# Known gaps

Honest limits of release `2026-09.2`.

- **Changelog is empty.** First load. [`GET /v1/changes`](https://fulldatasets.com/docs/changes.md) will start filling on the next release.
- **Authors are sparse.** Some editions still return `authors: []`. Open Library keys are resolved to names on read when we can.
- **Subjects and edition metadata are sparse.** `publisher`, `publication_date`, `page_count`, `format` are often null.
- **Synopses cover a slice.** About **8.4M** of **78.2M** works have `description`.
- **Split works.** Open Library split some popular titles into several work ids. Match and search collapse those when the title is an exact hit.
- **One source.** Open Library only. Library of Congress and BookBrainz are not in this release.
- **No author MCP tool.** Use REST [`GET /v1/authors/:id`](https://fulldatasets.com/docs/authors.md).
- **Resale is prohibited.** Bulk dumps are for your own systems.

## What is solid

- ISBN-10 / ISBN-13 lookup
- Stable ids + 301 redirects
- Title resolution with summary/study-guide demotion
- MCP and REST sharing one key
- Check record: `9780593135204` *Project Hail Mary*

---
