[jdbb]studio
Factory Pass · API HTTP + JSON · bearer token · six calls
Factory Pass · For your own factory

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

ThingWhere it comes from
Base URLhttps://jdbbs.exe.xyz
Project idA 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".
TokenThe 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 passUpload, 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: uploadedconvertingready | 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 (409 until 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

StatusMeans
401Token missing or wrong.
402No builds remaining on the pass (credits_remaining is in the body), or "index": true without the Index add-on ("addon":"index").
403The project has no live Factory Pass.
400A required field is missing (title, author, book_id…). The body says which.
409A 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.
Rate and courtesy. One build runs per project at a time; polling every few seconds is fine. The API is the same one the factory page uses, so anything the page can do, you can do — and nothing more. Questions, or a use you'd like supported: j@djinna.com.

Factory API recipe · v1 · 19 September 2026 · jdbb studio · Terms