API · REST · JSON

The BiblioScan API

Scan, price and export your books from your own tools. A personal API key opens almost every authenticated BiblioScan route — shelf scans, barcodes, metadata, buy lists, inventory, Amazon Seller data — behind a single HTTP header.

Base URL
https://biblioscan.ai
Auth
Authorization header
Format
JSON (UTF-8)
Keys per account
20 max
01

Quickstart

Three steps, no library to install.

  1. 01

    Create a key in your settings

    Go to Settings → API keys, name the key and pick how long it stays valid. The full key is displayed once, right there — copy it immediately.

  2. 02

    Put it in the Authorization header

    The raw value, with no "Bearer" prefix. It sits exactly where the session token sits, so existing BiblioScan code needs no other change.

  3. 03

    Call any allowed route

    Same parameters, same responses, same credits as a session. Start with /api/user/me to confirm the key works.

First call
curl https://biblioscan.ai/api/user/me \
  -H "Authorization: bsk_fce06ed9fabf22cb46a22a3f24d575b594f7506ca8e96902"
A 200 with your profile means the key works. A 401 means the key is unknown, revoked, or expired.
02

Authentication

BiblioScan uses neither OAuth nor request signing. One header is enough, and the server recognizes a key by its prefix.

Key format

A key starts with bsk_ and is 52 characters long: the prefix plus 48 hexadecimal characters. The server routes on that prefix and authenticates through the key; anything else is treated as a regular session token.

The header

Send the raw value. No "Bearer", no "Token", no quotes — the same convention the app's session token uses.

HTTP header
Authorization: bsk_fce06ed9fabf22cb46a22a3f24d575b594f7506ca8e96902

The key is shown once

The full value appears only in the creation response. It is stored hashed (SHA-256) and can never be read back — not by you, not by support. Lose it and you revoke it and create another.

API key or session token?

The session token belongs to the browser and the mobile app: it expires quickly and renews on login. The API key is built for scripts and integrations: you pick its lifetime, you revoke it at will, and it cannot touch the account itself.

03

Managing keys

Three routes handle the key lifecycle. They live under /api/apikeys and return JSON like the rest of the API.

All three require a session token. An API key cannot manage API keys — it gets a 403. That is deliberate: a leaked key cannot make itself permanent or mint more keys.

Create a key

POST/api/apikeys

Returns 201 with the full key. This is the only response that ever carries the key field — display or store it right away.

FieldTypeRequiredDescription
namestring (1–50)YesLabel shown in settings. Used only so you can tell your keys apart.
expiresInDaysinteger 1–3650 · nullNoLifetime in days. null or omitted means the key never expires. The UI offers 30, 90, 365 days or never, but the API accepts any whole number in that range.
Request
curl -X POST https://biblioscan.ai/api/apikeys \
  -H "Authorization: $SESSION_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"name": "My export script", "expiresInDays": 90}'
Response 201
{
  "id": "6a6c5bca60e97f365dc20a01",
  "key": "bsk_fce06ed9fabf22cb46a22a3f24d575b594f7506ca8e96902",
  "name": "My export script",
  "prefix": "bsk_fce06ed9",
  "createdAt": "2026-07-31T08:24:42.020Z",
  "expiresAt": "2026-10-29T08:24:42.017Z"
}

400 errors: missing or over-long name, out-of-range expiresInDays, or the 20-key limit reached. The exact reason comes back in the error field.

List your keys

GET/api/apikeys

Returns an array sorted newest first. No secret values: only the prefix, enough to recognize a key without revealing it.

Request
curl https://biblioscan.ai/api/apikeys \
  -H "Authorization: $SESSION_TOKEN"
Response 200
[
  {
    "id": "6a6c5bca60e97f365dc20a01",
    "name": "My export script",
    "prefix": "bsk_fce06ed9",
    "createdAt": "2026-07-31T08:24:42.020Z",
    "expiresAt": "2026-10-29T08:24:42.017Z",
    "lastUsedAt": "2026-07-31T09:12:03.511Z"
  }
]
FieldDescription
idKey identifier — pass it to the revoke route.
nameThe name given at creation.
prefixThe first 12 characters of the key, so you can spot it in a list.
createdAtCreation date, ISO 8601 UTC.
expiresAtISO 8601 expiry date, or null when the key never expires.
lastUsedAtLast authenticated call made with this key, or null if it was never used. Refreshed at most once a minute.

Revoke a key

DELETE/api/apikeys/:id

Immediate: the very next request with that key returns 401. A revoked key also drops out of the list.

Request
curl -X DELETE https://biblioscan.ai/api/apikeys/6a6c5bca60e97f365dc20a01 \
  -H "Authorization: $SESSION_TOKEN"
Response 200
{ "message": "API key revoked" }

Errors: 404 when the key does not exist or belongs to another account, 400 when the id is malformed.

04

Expiration and revocation

A key goes away in two ways: it expires, or you revoke it.

  • Expiration is set at creation and cannot be edited. To change the lifetime, revoke the key and create a new one.
  • An expired key returns 401 with "API key expired", then is deleted from the database automatically — it leaves the list on its own.
  • An unknown or revoked key returns 401 with "Unauthorized".
  • Each account holds at most 20 active keys. Revoking one frees a slot immediately.
05

Endpoint reference

Every route below behaves exactly as it does with a session token: same parameters, same responses, same credits consumed. Colon-prefixed segments are URL parameters.

Watch your credits: a shelf scan or a barcode scan fired from a script spends credits just like one fired from the app. Put a guard in your loop.

Click a route to unfold its explanation, its parameters and the matching curl command.

Account

Shelf scans

Barcodes

Metadata

Buy lists

Inventory

Amazon SP-API

Billing (read-only)

06

Response conventions

Four conventions run through the whole API. Ignoring them is the most common source of integration bugs.

Ids live in _id, not id

Every entity — scan, job, spine, result — carries a MongoDB _id. Reading id silently returns undefined, which breaks every key lookup further down your code.

Image URLs are relative and prefixed with ../

An image comes back as ../images/spines/abc123.png. Strip the ../ and prepend the base URL to get something a browser can load.

A scan is a nested structure

The shape is always scan → jobs[] → spines[] → results[], with metadata and sources.keepa under each result. Large scans are heavy: read only the fields you need.

Bounding boxes are in original image pixels

spines[].bounding_box holds coordinates in the image as uploaded. The API does not return that image's dimensions — measure it yourself and scale before drawing an overlay.

Scan shape (trimmed)
{
  "_id": "68a1f0c2d4e5a60012ab34cd",
  "name": "Emmaus 2026-07-31",
  "createdAt": "2026-07-31T08:24:42.020Z",
  "jobs": [
    {
      "_id": "68a1f0c2d4e5a60012ab34ce",
      "image": "../images/jobs/68a1f0c2.jpg",
      "spines": [
        {
          "_id": "68a1f0c2d4e5a60012ab34cf",
          "image": "../images/spines/abc123.png",
          "bounding_box": [[120, 40], [260, 40], [260, 610], [120, 610]],
          "results": [
            {
              "_id": "68a1f0c2d4e5a60012ab34d0",
              "score": 92,
              "metadata": { "isbn": "9782070408504", "title": "Le Petit Prince" },
              "sources": { "keepa": { "salesRank": 24500 } }
            }
          ]
        }
      ]
    }
  ]
}
Rebuilding an image URL
// Image paths come back relative, prefixed with "../".
//   "../images/spines/abc123.png"
// Strip the "../" and prepend the API base:
const url = "https://biblioscan.ai/" + path.replace(/^\.\.\//, "");
// → https://biblioscan.ai/images/spines/abc123.png
07

What a key cannot do

An API key never touches the account itself. The routes below always return 403, even with a valid key.

RouteReason
POST/api/user/changepassword
Password change.
POST/api/user/delete
Account deletion.
POST/api/auth/logout
Session management.
POST/api/auth/deleteothersession
Session management.
POST/api/paddle/subscriptions/:subscriptionId/cancel
Subscription cancellation.
*/api/apikeys
A key cannot create, list, or revoke keys.
Response 403
{ "error": "This action requires a logged-in session and cannot be performed with an API key" }
The direct consequence: a leaked key cannot take over the account, cannot lock you out of it, and cannot make itself permanent.
08

Error codes

Errors come back as JSON with an error field. The HTTP status tells you what to do; the message tells you why.

CodeWhenWhat to do
401 UnauthorizedUnknown key, revoked key, or missing Authorization header.Check the header is actually sent and that the key was not revoked from settings.
401 API key expiredThe expiry date has passed.Create a new key. The old one is deleted automatically.
403 …requires a logged-in sessionA sensitive route was called with an API key.Do that action from the app or the website, with a login session.
400 Bad RequestInvalid parameters: missing or over-long name, out-of-range expiresInDays, 20-key limit reached.Read the error field — it names the exact cause.
404 Not FoundThe resource does not exist, or belongs to another account.Check the id — remember it is _id, not id.
Example error response
HTTP/1.1 401 Unauthorized
Content-Type: application/json

{ "error": "API key expired" }
09

Full examples

The full cycle, from first call to revocation.

curl — a key's lifecycle

Create the key with a session token, use it everywhere else, then revoke it once the script no longer needs it.

bash
# 1. Create a key (with a session token from /api/auth/login)
curl -X POST https://biblioscan.ai/api/apikeys \
  -H "Authorization: $SESSION_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"name": "test key", "expiresInDays": 90}'

# 2. Use the key on any allowed route
curl https://biblioscan.ai/api/user/me \
  -H "Authorization: bsk_..."

# 3. List your keys
curl https://biblioscan.ai/api/apikeys -H "Authorization: $SESSION_TOKEN"

# 4. Revoke one
curl -X DELETE https://biblioscan.ai/api/apikeys/<id> -H "Authorization: $SESSION_TOKEN"

JavaScript — scan an ISBN and wait for the result

The barcode route is asynchronous: it returns an id, then you poll the detail route until the status is no longer pending.

js
const API_KEY = process.env.BIBLIOSCAN_API_KEY; // bsk_...

async function biblioscan(path, options = {}) {
  const res = await fetch("https://biblioscan.ai" + path, {
    ...options,
    headers: {
      "Content-Type": "application/json",
      // Raw key value — no "Bearer" prefix.
      Authorization: API_KEY,
      ...options.headers,
    },
  });

  if (!res.ok) {
    const body = await res.json().catch(() => ({}));
    throw new Error(body.error || `BiblioScan API error ${res.status}`);
  }
  return res.json();
}

// Scan an ISBN, then poll until the data is fresh.
// Note the capital "ISBN" key — the backend is case-sensitive here.
// This POST spends one barcode credit; the GETs below are free.
let snap = await biblioscan("/api/barcode/scan", {
  method: "POST",
  body: JSON.stringify({ ISBN: "9782070408504", db_lang: "fr" }),
});

// Backoff suggested by the backend: 500ms, 1s, 2s, 4s, 8s, then give up.
for (const delay of [500, 1000, 2000, 4000, 8000]) {
  if (snap.fresh || snap.state === "error") break;
  await new Promise((r) => setTimeout(r, delay));
  snap = await biblioscan("/api/barcode/scan/" + snap.barcodeScanId);
}

// state "error" with a non-null metadata means a stale cache — still usable.
console.log(snap.state, snap.fresh, snap.metadata?.sources?.keepa);

Python — export your inventory

Inventory comes back as plain JSON, ready to write to a CSV or push into a spreadsheet.

python
import os, time, requests

API = "https://biblioscan.ai"
HEADERS = {"Authorization": os.environ["BIBLIOSCAN_API_KEY"]}

def biblioscan(path, method="GET", **kwargs):
    res = requests.request(method, API + path, headers=HEADERS, **kwargs)
    res.raise_for_status()
    return res.json()

# Export the whole inventory as CSV-ready rows.
stock = biblioscan("/api/stock/")
for item in stock:
    print(item["_id"], item.get("isbn"), item.get("purchase_price"))
10

Best practices

A key is worth an application password. Four habits keep it safe.

Never in code

Put the key in an environment variable or a secret manager. A key pushed to a public repo is compromised within minutes, even if the commit is removed afterwards.

One key per use

One name per script or per machine. The day one of them leaks, you revoke that key without breaking your other integrations.

Short expiry by default

90 days covers most scripts. Save "never" for integrations you genuinely keep an eye on.

Watch lastUsedAt

A key that was never used, or that has been idle for months, has no reason to exist. Revoke it — it is free and instant.

Ready to wire up your tools?

Create your first key from your settings — it takes under a minute.

https://biblioscan.ai