The book factory is an API
Upload, inspect, build, download — from a terminal or your own pipeline
Everything the factory page does, it does by calling six HTTP endpoints. If you already have a publishing pipeline — a lit mag on a deadline, a small press with a folder of manuscripts, a script that wakes up when a Google Doc changes — you can call the same endpoints yourself. Same pass, same credits, same outputs; the browser is optional. This page is the whole recipe. Every call on it was run against the live server the day it was written.
0. What you need
| Thing | Where it comes from |
|---|---|
| Base URL | https://jdbbs.exe.xyz |
| Project id | A number. Your Factory Pass email links to your factory page, /{client}/{project}/factory/. Take the two slugs and ask GET /api/clients/{client}/projects; the row whose project_slug matches carries "id". |
| Token | The project password the studio set for your project — the same thing the factory page asks for when it says Password. The studio mints it; ask if you don't have one. |
| A live pass | Upload, inspect and build all return 403 {"error":"this project has no factory pass"} without one. Redeem or buy on the factory page. |
Send the token on every call, any of three equivalent ways:
Authorization: Bearer <token> # what most HTTP tooling has a switch for X-Auth-Token: <token> Cookie: prodcal_auth_<project id>=<token> # what the browser uses
A wrong or missing token is 401 {"error":"unauthorized"} on every route. The examples below assume:
B=https://jdbbs.exe.xyz
P=14 # your project id
H="Authorization: Bearer $TOKEN"
1. Check the pass — free
curl -s -H "$H" $B/api/projects/$P/pass
{"exists":true,"status":"active","live":true,"builds_included":3,"builds_extra":0,
"builds_used":1,"credits_remaining":2,"expires_at":"2027-03-19T12:41:46Z","customer_name":"…"}
2. Upload the manuscript — free
Multipart; fields file, title, author, project_id — all four required (400 {"error":"title and author required"} otherwise). Each upload is a new book row on the project; it does not overwrite the last one.
BOOK=$(curl -s -H "$H" -F file=@manuscript.docx -F title='My Book' -F author='Me' \
-F project_id=$P $B/api/books/upload | jq .id)
201 {"author":"Me","id":26,"status":"uploaded","title":"My Book"}
3. Inspect / preflight — free, unlimited
POST runs it and returns the report; GET …?book_id= re-reads the latest. The human-readable version is at report_url.
curl -s -H "$H" -X POST $B/api/projects/$P/preflight \
-H 'Content-Type: application/json' -d "{\"book_id\":$BOOK}" | jq .
curl -s -H "$H" "$B/api/projects/$P/preflight?book_id=$BOOK" | jq .summary
curl -s -H "$H" "$B/api/projects/$P/preflight/report?book_id=$BOOK" -o report.html
The JSON is {exists, project_id, book_id, status: ready|error, source_filename, updated_at, summary:{total, high, medium, low, preserved, by_type:{…}}, book_map:{sections:[{title, kind, paras, words}], warnings, notes, summary}, report_url, history:[…]}. Per-finding detail (what to fix, and where) is in the HTML report today; the JSON carries counts and the book map.
4. Build — proofs free, finals one credit
kind is "proof" (free and unlimited within reason — 30 a day per project; the PDF carries a PROOF line on every page) or "final" (the default when omitted: one credit, clean PDF, the delivery email). format is "both" (default: print PDF + EPUB), "pdf" or "epub"; an EPUB-only build is always a free proof. Optional callback_url (public https): the server POSTs the status JSON from step 5 there once, when the build ends. Optional "index": true sets the book’s reviewed back-of-book index at the back of the print PDF — needs the Index add-on on the pass (else 402 with "addon":"index") and a draft to include (400 until POST /api/books/{id}/index/draft has run; GET /api/books/{id}/index shows its status and entries, PUT saves your edits).
curl -s -H "$H" -X POST $B/api/books/$BOOK/convert \
-H 'Content-Type: application/json' -d '{"format":"both","kind":"proof"}'
200 {"book_id":26,"format":"both","kind":"proof","status":"converting","status_url":"/api/books/26"}
402 {"error":"no finals remaining","credits_remaining":0}
429 {"error":"proof limit reached: 30 proofs in the last 24 hours for this project; try again later"}
409 {"error":"a build is already running for this project; wait for it to finish"}
For a final, the credit is debited when convert is accepted; a final that fails refunds it automatically. Proofs touch no credit. Inspect, upload, status and downloads are free. A final transmittal saved after the spec was last written is pulled into the spec at convert time, so you don't need to fetch the Word template first.
5. Poll until done
until curl -s -H "$H" $B/api/books/$BOOK | jq -e '.status=="ready" or .status=="error"' >/dev/null; do sleep 3; done curl -s -H "$H" $B/api/books/$BOOK | jq .
Success — a 10-page sample takes about 10 s, a 110-page book about 30 s:
{"book_id":26,"project_id":14,"title":"My Book","author":"Me","source_filename":"manuscript.docx",
"status":"ready","updated_at":"2026-09-19T12:42:33Z",
"outputs":[{"id":73,"format":"epub","kind":"proof","size_bytes":5927,"download_url":"/api/books/26/outputs/73/download"},
{"id":72,"format":"pdf","kind":"proof","size_bytes":38954,"download_url":"/api/books/26/outputs/72/download"}]}
Failure — status is "error", outputs is empty, and error is the same text the factory page shows: a plain explanation and what to do, an optional Near: “…” (text from the manuscript next to the fault, so the author can search for it in Word), and an optional Technical detail: line.
{"book_id":27,"status":"error","outputs":[],
"error":"We couldn't read this .docx. Export it again from your editor (Word: File → Save As → Word Document; Google Docs: File → Download → Microsoft Word; …) and try again. …\nTechnical detail: pandoc typst: exit status 63"}
Statuses you will see: uploaded → converting → ready | error.
6. Download
By format (latest output of that format) or by the download_url from the status JSON (a specific output). Both set Content-Disposition with a dated filename.
curl -s -H "$H" -o book.pdf $B/api/books/$BOOK/download/pdf # application/pdf curl -s -H "$H" -o book.epub $B/api/books/$BOOK/download/epub # application/epub+zip curl -s -H "$H" -o final.pdf $B/api/books/$BOOK/download/pdf?kind=final # newest of a kind: ?kind=final|proof curl -s -H "$H" -o out.pdf $B/api/books/$BOOK/outputs/72/download
The whole thing, one screen
B=https://jdbbs.exe.xyz; P=14; H="Authorization: Bearer $TOKEN"
BOOK=$(curl -s -H "$H" -F file=@ms.docx -F title='My Book' -F author='Me' -F project_id=$P $B/api/books/upload | jq .id)
curl -s -H "$H" -X POST $B/api/projects/$P/preflight -H 'Content-Type: application/json' -d "{\"book_id\":$BOOK}" | jq .summary
curl -s -H "$H" -X POST $B/api/books/$BOOK/convert -H 'Content-Type: application/json' -d '{"format":"both","kind":"final"}'
until curl -s -H "$H" $B/api/books/$BOOK | jq -e '.status!="converting" and .status!="uploaded"' >/dev/null; do sleep 3; done
curl -s -H "$H" $B/api/books/$BOOK | jq -r '.error // "ok"'
curl -s -H "$H" -o book.pdf $B/api/books/$BOOK/download/pdf; curl -s -H "$H" -o book.epub $B/api/books/$BOOK/download/epub
Or: Python, stdlib only
factory-cli.py is the same six calls as a 90-line script with no dependencies. Copy it into your own tooling; the Factory class at the top is the part you'd keep.
curl -sO https://jdbbs.exe.xyz/factory/api/factory-cli.py python3 factory-cli.py --base $B --project $P --token $TOKEN inspect ms.docx python3 factory-cli.py --base $B --project $P --token $TOKEN build ms.docx --out ./out
inspect = upload → preflight (free) → print the report JSON. build = upload → convert (--kind proof by default, free; --kind final one credit) → poll → download both outputs into --out.
Also callable — not needed for a build
GET/PUT /api/projects/{id}/transmittal— read or fill the transmittal as JSON,{"status":"final","data":{…}}; a final one is applied to the spec at the next convert.GET /api/projects/{id}/word-template— the styled .docx template (409until the transmittal is final).GET /api/projects/{id}/books— every book on the project (ids, statuses).GET /api/books/{id}/outputs— all output rows for a book.
Not available to a token caller — ask the studio
- Minting or rotating the token.
- Editing the book spec directly; the transmittal is the customer-side lever.
- Buying credits; deleting books.
- Per-finding preflight detail as JSON (HTML report only, for now).
Errors, in one table
| Status | Means |
|---|---|
| 401 | Token missing or wrong. |
| 402 | No builds remaining on the pass (credits_remaining is in the body), or "index": true without the Index add-on ("addon":"index"). |
| 403 | The project has no live Factory Pass. |
| 400 | A required field is missing (title, author, book_id…). The body says which. |
| 409 | A build is already running on this project, or the transmittal isn't final yet (template). |
200 + "status":"error" | The build itself failed. Credit refunded; read error. |