The core action in htmlbook is publish: shelve a document to a hosted URL. The
same pipeline backs the MCP publish tool and the REST POST /api/docs route.
Reach for publish whenever you've produced something the user will want to read on
another device or share with someone — an HTML report or dashboard, a spec, notes,
a write-up — instead of leaving it as a local .html / .md file. Don't publish
scratch files the user didn't ask to keep; documents are private by default, but the
library is still theirs to keep tidy.
You never declare a format. htmlbook classifies what you send:
| You send | Stored as | Reader experience |
|---|---|---|
| Markdown | source.md (source of truth) + rendered index.html |
Themed reader; downloadable + editable as Markdown |
An <article class="hb-doc"> fragment (bare or inside a full HTML page) |
Sanitized fragment | Themed reader: TOC, paper/sepia/dark, inline edit |
| Any other full HTML page | Verbatim "bundle" | Sandboxed iframe — own CSS/JS runs |
Detection keys off a real <article class="hb-doc"> content root. A page that only
mentions the tag in escaped code does not match, so a doc about htmlbook stays a
bundle correctly.
Markdown — easiest for prose:
{
"tool": "publish",
"project": "acme",
"slug": "q3-review",
"markdown": "# Q3 Review\n\nRevenue up 8% QoQ.\n\n## Highlights\n- New onboarding\n- Churn down to 2.1%"
}An hb-doc HTML fragment — for rich layout:
{
"tool": "publish",
"project": "acme",
"slug": "q3-dashboard",
"html": "<article class=\"hb-doc\"><div class=\"hb-content\"><h1>Q3 Dashboard</h1><div class=\"hb-kpi-grid\"><div class=\"hb-kpi\"><div class=\"hb-kpi-label\">MRR</div><div class=\"hb-kpi-value\">$12.4k</div><div class=\"hb-kpi-delta up\">+8%</div></div></div></div></article>"
}Pass exactly one of markdown or html. Markdown accidentally placed in html
(no leading <) is rerouted to the markdown path automatically, so a slip still lands
as a themed doc rather than a verbatim bundle.
The response gives you the owner reader link right away, and the share link once public:
shelved (hbdoc) · acme/q3-dashboard · v1 · read: https://htmlbook.io/app/p/acme/q3-dashboard
private — set_access "public" for a share link
projectgroups documents (a project lives in a workspace). Required for HTML. For Markdown it can come from the file's frontmatter instead — see markdown.md.slugis the document's id within a project. Optional — it's derived from the title / first heading when omitted.
Publishing to an existing project/slug creates a new immutable version; it does
not overwrite. Earlier versions stay restorable.
Publishing is idempotent by content hash. Re-pushing an unchanged document is a true no-op (no new version):
no change (hbdoc) · acme/q3-dashboard · v1 · read: https://htmlbook.io/app/p/acme/q3-dashboard
For Markdown the hash is over the original .md source, so re-running a folder sync
is cheap.
Re-emitting a long document inline costs your agent tokens twice (once to write it, once as the tool argument) and bloats its context. Instead, write it to a file and push the file over REST so only one line passes through your context:
curl -s -X POST "https://htmlbook.io/api/docs?project=acme&slug=q3-dashboard" \
-H "Authorization: Bearer $HTMLBOOK_API_KEY" \
--data-binary @dashboard.htmlSame pipeline, same idempotency, same versioning. Full details in rest-api.md.
Anything that isn't an hb-doc — your own <style>, <script>, layout, a charting
library — is stored verbatim and rendered in a sandboxed iframe. Scripts run;
localStorage and inline handlers work because it's served from an isolated origin.
There's no theming/TOC/inline-edit for a bundle. Just push the full page:
curl -s -X POST "https://htmlbook.io/api/docs?project=acme&slug=pricing-calculator" \
-H "Authorization: Bearer $HTMLBOOK_API_KEY" \
--data-binary @calculator.htmlA bundle can be more than one file — an index.html entry plus sub-pages and
assets (CSS, JS, images, fonts), up to 10MB total / 100 files — so relative
links between pages work like a tiny static site. Files upload directly to
storage via a 3-step presigned REST flow (the document never passes through your
agent's tokens):
# 1) init — declare the files; get a presigned PUT url for each
curl -s -X POST https://htmlbook.io/api/docs/bundle/init \
-H "Authorization: Bearer $HTMLBOOK_API_KEY" -H "Content-Type: application/json" \
-d '{"project":"acme","slug":"guide","files":[{"path":"index.html","bytes":1234},{"path":"css/app.css","bytes":567}]}'
# 2) PUT each file to its url (Content-Type must match the extension)
curl -s -X PUT "<url>" -H "Content-Type: text/html; charset=utf-8" --data-binary @index.html
# 3) commit
curl -s -X POST https://htmlbook.io/api/docs/bundle/commit \
-H "Authorization: Bearer $HTMLBOOK_API_KEY" -H "Content-Type: application/json" \
-d '{"uploadId":"<from-init>"}'The bundle runs cross-origin: fetch/network and localStorage work, but it has
no htmlbook session, requests must be https, and you should never embed secrets in
the client JS (the source is visible). Multi-file bundles are read-only in the web
reader — re-run init→commit to update.
See also: Markdown · hb-doc contract · Sharing