# BOOK-OFF Online — Japan's second-hand books, comics, CD, DVD and games

> Search BOOK-OFF Online's 6,152,410-item catalogue — Japan's largest second-hand chain — across its six genres (books, comics/manga, magazines, CD, DVD & Blu-ray, games) or its 61,208 multi-volume sets. ONE ROW IS ONE CATALOGUE PRODUCT, not a copy: BOOK-OFF is a single retailer, so each product has one second-hand price and, where it is still published, one new price (measured: searching an exact ISBN-13/JAN returns exactly one row, 3 of 3 tried). Each row carries which condition surface it is (`surface` = used/new/set) together with the price, the publisher's list price, BOOK-OFF's own discount figure and its own pre-markdown price with the date it applied. 🔴 Send at least one of keyword, author, publisher, genre, genre_code or sets_only. Page size is the source's own 30, 60, 120 and is exact. The hard ceiling is page 999 (page 1000 → HTTP 404), so at most 119,880 items are reachable per query. Past the last real page the source returns an EMPTY grid, not filler rows, and `stop_reason` says `past_last_page`.
> ReefAPI engine `bookoff` · 3 endpoints · clean JSON, no scraping or browsers to manage.

## How to call
- **Endpoint:** `POST https://api.reefapi.com/bookoff/v1/<action>` with a JSON body.
- **Auth:** header `x-api-key: <YOUR_REEFAPI_KEY>` — create one free (1,000 credits, no card): https://reefapi.com/signup
- **Response (every call):** `{ ok: boolean, data: ..., meta: { record_count, credits, ... }, error: { code, message } }` — branch on `ok`. Failed calls are free except verified SHEIN NOT_FOUND on product/detail and price (4 credits).
- **One key + one shared credit pool** across every ReefAPI API. Per-call credits are listed on each endpoint below.
- **Use it from an AI agent (MCP):** connect `https://api.reefapi.com/mcp` (remote streamable-http). Send the key as `Authorization: Bearer <key>`, or put it in the URL (`?key=<key>`) when the client has no header field, as ChatGPT does.

## Endpoints

### POST https://api.reefapi.com/bookoff/v1/search — 3 credits
Search BOOK-OFF Online's 6,152,410-item catalogue — Japan's largest second-hand chain — across its six genres (books, comics/manga, magazines, CD, DVD & Blu-ray, games) or its 61,208 multi-volume sets. ONE ROW IS ONE CATALOGUE PRODUCT, not a copy: BOOK-OFF is a single retailer, so each product has one second-hand price and, where it is still published, one new price (measured: searching an exact ISBN-13/JAN returns exactly one row, 3 of 3 tried). Each row carries which condition surface it is (`surface` = used/new/set) together with the price, the publisher's list price, BOOK-OFF's own discount figure and its own pre-markdown price with the date it applied. 🔴 Send at least one of keyword, author, publisher, genre, genre_code or sets_only. Page size is the source's own 30, 60, 120 and is exact. The hard ceiling is page 999 (page 1000 → HTTP 404), so at most 119,880 items are reachable per query. Past the last real page the source returns an EMPTY grid, not filler rows, and `stop_reason` says `past_last_page`.

**Parameters:**
- `keyword` (string, optional) — Free-text search over title, author/artist and product number. Upstream this is a PATH segment, not a query string — six query spellings were tried and every one returned the full catalogue with HTTP 200.
- `exclude_keyword` (string, optional) — Exclude rows matching this text. Measured: keyword=ワンピース 2,181 → excluding カード 2,168.
- `author` (string, optional) — Author, artist or creator, as BOOK-OFF spells it. Measured on its own: 尾田栄一郎 → 1,062 items.
- `publisher` (string, optional) — Publisher, label or maker. Measured: keyword=ワンピース 2,181 → publisher=集英社 436.
- `genre` (enum, optional) — One of BOOK-OFF's six public genres. Measured on keyword=ワンピース: book 649, comic 394, magazine 0, cd 272, dvd 775, game 91 — which sum to exactly the 2,181 unfiltered total. BOOK-OFF has NO trading-card genre: card products appear inside these six and are reachable by keyword only. [one of: book, comic, magazine, cd, dvd, game]
- `genre_code` (string, optional) — BOOK-OFF's own genre code instead of the `genre` enum: a 2-digit top-level code (12, 11, 13, 31, 71, 51) or a 4-digit sub-genre whose first two digits are one of those — e.g. 5127 = Nintendo Switch (3,111 items, measured). An unrecognised code is refused here because the source answers one with the FULL catalogue and HTTP 200. Note: this is NOT the dotted code a product page prints as its `genre_code` — that is a different, internal taxonomy (31xx vs 1601-01-01).
- `in_stock_only` (boolean, optional, default false) — Only items BOOK-OFF has in stock now. Measured: keyword=ワンピース 2,181 → 1,383.
- `used_only` (boolean, optional, default false) — Switch every row to its SECOND-HAND offer. 🔴 On its own this is NOT a count filter: measured 2,181 → 2,181, the same 30 ids in the same order, only each row's offer rewritten (a row went new JPY 484 → used JPY 440). Together with in_stock_only it does filter: 1,383 → 1,326. A warning says so in `meta`.
- `store_pickup_only` (boolean, optional, default false) — Only items collectable at a BOOK-OFF shop. Measured: 2,181 → 1,095.
- `price_min` (integer, optional) — Minimum price in JPY (whole yen — JPY has no minor unit). Measured: price 5,000 and up → 51 of 2,181.
- `price_max` (integer, optional) — Maximum price in JPY. Measured: 0-300 → 680 of 2,181.
- `released_from` (string, optional) — Earliest release year-month, YYYY-MM or YYYYMM. Measured: from 2025-01 → 105 of 2,181. The source's own note: sets are excluded from this filter.
- `released_to` (string, optional) — Latest release year-month. Measured: 2025-01 to 2025-12 → 60 of 2,181.
- `sort` (enum, optional, default "popularity") — Sort order. Omitted = BOOK-OFF's own popularity order, which is REPRODUCIBLE: three identical calls returned the same 30 ids in the same order. Measured: used_price_asc gave 110, 110, 110 … and used_price_desc gave 49,500, 46,200, 26,400 … [one of: popularity, used_price_asc, used_price_desc, new_price_asc, new_price_desc, newest, oldest]
- `per_page` (integer, optional, default 30) — Rows per page. The source offers 30, 60 and 120 only and silently falls back to 30 for anything else (measured: 77 → 30 rows), so other values are refused. [one of: 30, 60, 120]
- `page` (integer, optional, default 1) — 1-based page. The source refuses page 1000 with HTTP 404, so 999 is the hard ceiling.
- `sets_only` (boolean, optional, default false) — Search the 61,208 multi-volume SETS instead of single products. A set is its own SKU bundling N products; resolve one with the `set` action. The genre, stock, store-pickup and release-date filters are refused with this because they were NOT measured to apply on the set surface.

**Returns:** {page, per_page, returned, total, pages_estimate, page_ceiling, order_is_stable, filters_applied, facet_counts, items:[{product_id, surface, url, title, author, category, genre_label, condition, price_jpy, price_display, list_price_jpy, discount_jpy, discount_pct, previous_price_jpy, previous_price_date, points, in_stock, stock_text, store_pickup, release_date, image_url, tags, set_id, items_total, items_available}]}

**Example request body:**
```json
{
  "keyword": "ワンピース"
}
```

### POST https://api.reefapi.com/bookoff/v1/product — 2 credits
One BOOK-OFF product in full: title, author, category and sub-genre, the 13-digit JAN (republished as `isbn13` when it really is a 978/979 Bookland code — ISBN-10 is never derived from it), publisher, release date, description, and the category-specific rows the source prints — catalogue number on CD, DVD and games, disc count and the full track list on CD and DVD, cast and the director/writer/composer credit row on DVD (enumerated over 18 live pages, three per genre; artist and platform are NOT published as rows and are therefore absent rather than permanently null). FOUR money figures are kept apart: the price you pay, the publisher's list price, BOOK-OFF's own discount, and BOOK-OFF's own pre-markdown price with the date it applied — each cross-checked against the page's own schema.org offer. Plus STORE STOCK: how many BOOK-OFF shops have received the item and the per-prefecture breakdown. 🔴 An id alone does NOT identify an offer: `/used/<id>` and `/new/<id>` are the same product at different prices (measured 1,760 vs 2,200). Pass `condition` to choose; with it omitted the second-hand surface is tried first and then the new one, and the answer always reports which one replied. A dead id returns NOT_FOUND.

**Parameters:**
- `product_id` (string, required) — The 10-digit BOOK-OFF item number (https://shopping.bookoff.co.jp/used/0020673438 → 0020673438), or the whole product URL — in which case the URL's own /used/ or /new/ segment sets `condition`. 🔴 Anything looser is refused: BOOK-OFF drops trailing characters and serves a DIFFERENT product with HTTP 200 (measured: /used/0020673438x returned the page of 0020673438).
- `condition` (enum, optional) — Which condition surface to read. Omitted = try second-hand, then new; the reply's `condition` and `url` always say which one answered, and `offer_surfaces` lists the surfaces the product page's own toggle links to. [one of: used, new]

**Returns:** {product_id, surface, condition, url, title, author, category, genre_label, genre_code, price_jpy, price_display, price_ld_json, price_witnesses_agree, price_unit_verified, price_source, page_shape, currency, list_price_jpy, discount_jpy, discount_pct, discount_arithmetic_ok, previous_price_jpy, previous_price_date, points, in_stock, stock_text, availability, shipping_text, store_pickup, store_arrival_estimate, jan, isbn13, isbn10, publisher, release_date, description, catalog_number, disc_count_text, tracklist, cast, credits, rating, review_count, image_url, images, offer_surfaces, other_condition_url, tags, store_stock:{stores_with_stock, by_prefecture:[{prefecture_code, prefecture, items}], by_prefecture_sum, breakdown_matches_headline, store_names, note}}

**Example request body:**
```json
{
  "product_id": "0020673438",
  "condition": "used"
}
```

### POST https://api.reefapi.com/bookoff/v1/set — 2 credits
One of BOOK-OFF's 61,208 multi-volume SETS: its own price and discount, how many of its component items are available right now split into second-hand and new (the source's own "全2点中 1点（中古1点 / 新品0点）" = 1 of 2 items, 1 used and 0 new), and the component products themselves as ids and URLs you can resolve with `product`. Set pages publish NO schema.org data, so every field here is parsed from the printed page. A dead set id returns NOT_FOUND.

**Parameters:**
- `set_id` (string, required) — The 10-digit set number (https://shopping.bookoff.co.jp/s/8800050251 → 8800050251), or the whole set URL. `search` returns it as `product_id` on rows whose `surface` is `set`, and as `set_id` on single rows that also belong to a set.

**Returns:** {set_id, url, title, author, category, genre_label, price_jpy, price_display, list_price_in_stock_jpy, discount_jpy, discount_pct, items_total, items_available, items_available_used, items_available_new, stock_text, shipping_text, image_url, components:[{product_id, surface, url}], components_returned, store_stock}

**Example request body:**
```json
{
  "set_id": "8800050251"
}
```

## At scale
- **Volume:** 5M+ requests a day, measured at 60 requests a second across the fleet with no
  central bottleneck. Per-key limits are raised for high-volume accounts; volume pricing on request.
- **Missing a source:** tell us a site we do not cover and it becomes an engine. A customer asked
  for bestprice.gr on 21 Sep 2026 and it was in the catalog on 22 Sep.
- **Support:** 2 minute median time from a question in the live chat to the first answer. Setup
  help included, no support tier to buy.
- **One key, one credit pool** across every API. No per-site plans, no separate subscriptions.

## More
- Try it live, no code: https://reefapi.com/playground?engine=bookoff
- Human docs page: https://reefapi.com/docs/bookoff
- Overview page: https://reefapi.com/bookoff-api
- Every ReefAPI API in one file (for your AI): https://reefapi.com/llms-full.txt
