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
Quickstart
Three steps, no library to install.
- 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.
- 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.
- 03
Call any allowed route
Same parameters, same responses, same credits as a session. Start with /api/user/me to confirm the key works.
curl https://biblioscan.ai/api/user/me \
-H "Authorization: bsk_fce06ed9fabf22cb46a22a3f24d575b594f7506ca8e96902"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.
Authorization: bsk_fce06ed9fabf22cb46a22a3f24d575b594f7506ca8e96902The 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.
Managing keys
Three routes handle the key lifecycle. They live under /api/apikeys and return JSON like the rest of the API.
Create a key
/api/apikeysReturns 201 with the full key. This is the only response that ever carries the key field — display or store it right away.
| Field | Type | Required | Description |
|---|---|---|---|
name | string (1–50) | Yes | Label shown in settings. Used only so you can tell your keys apart. |
expiresInDays | integer 1–3650 · null | No | Lifetime 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. |
curl -X POST https://biblioscan.ai/api/apikeys \
-H "Authorization: $SESSION_TOKEN" \
-H "Content-Type: application/json" \
-d '{"name": "My export script", "expiresInDays": 90}'{
"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
/api/apikeysReturns an array sorted newest first. No secret values: only the prefix, enough to recognize a key without revealing it.
curl https://biblioscan.ai/api/apikeys \
-H "Authorization: $SESSION_TOKEN"[
{
"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"
}
]| Field | Description |
|---|---|
id | Key identifier — pass it to the revoke route. |
name | The name given at creation. |
prefix | The first 12 characters of the key, so you can spot it in a list. |
createdAt | Creation date, ISO 8601 UTC. |
expiresAt | ISO 8601 expiry date, or null when the key never expires. |
lastUsedAt | Last authenticated call made with this key, or null if it was never used. Refreshed at most once a minute. |
Revoke a key
/api/apikeys/:idImmediate: the very next request with that key returns 401. A revoked key also drops out of the list.
curl -X DELETE https://biblioscan.ai/api/apikeys/6a6c5bca60e97f365dc20a01 \
-H "Authorization: $SESSION_TOKEN"{ "message": "API key revoked" }Errors: 404 when the key does not exist or belongs to another account, 400 when the id is malformed.
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.
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.
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)
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.
{
"_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 } }
}
]
}
]
}
]
}// 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.pngWhat a key cannot do
An API key never touches the account itself. The routes below always return 403, even with a valid key.
| Route | Reason |
|---|---|
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. |
{ "error": "This action requires a logged-in session and cannot be performed with an API key" }Error codes
Errors come back as JSON with an error field. The HTTP status tells you what to do; the message tells you why.
| Code | When | What to do |
|---|---|---|
| 401 Unauthorized | Unknown 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 expired | The expiry date has passed. | Create a new key. The old one is deleted automatically. |
| 403 …requires a logged-in session | A sensitive route was called with an API key. | Do that action from the app or the website, with a login session. |
| 400 Bad Request | Invalid 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 Found | The resource does not exist, or belongs to another account. | Check the id — remember it is _id, not id. |
HTTP/1.1 401 Unauthorized
Content-Type: application/json
{ "error": "API key expired" }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.
# 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.
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.
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"))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