What Display.dev is, and what this is
Display.dev wraps agent-produced HTML in workspace membership and single sign-on, so who can open a page follows from who is in the workspace.
The unit here is the page, not the workspace. Access is decided per-link, so one account can hold a page anyone with the URL may read, a password-protected one and an email-domain-gated one, with no org structure in between.
Where the two overlap
- Both exist because an agent emitted HTML and "now what?" was an unanswered question.
- Both give the reader a plain URL, with no client to install.
- Both keep the output off search engines.
Where the defaults diverge
- Try it without an account. The first publish can be anonymous: paste HTML on the home page and a private link comes back, no workspace and no install.
- Access is per-link. Passwords, email-domain gates, expiry and burn-after-read are page-level switches, not membership.
- Stable URLs across revisions.
update_sitereplaces the content in place, which is what an agent loop actually needs. - No seats. Pricing here is a config file — there is nothing per-seat to count.
Side by side
| Dimension | Display.dev | OpenTree |
|---|---|---|
| First publish | Create a workspace, then publish | Anonymous — paste and go |
| Access model | Workspace membership / SSO | Per-link: token, password, email-domain, expiry |
| Agent entry point | Available | The primary path: 12 MCP tools |
| Iteration | Republish | update_site, replace-in-place across revisions |
| Seats | Per-seat | Unlimited |
Publishing the same page here
No account, no install — the anonymous route gives you a private link to compare against whatever you are using now:
curl -X POST https://read.botook.ai/v1/publish -F file=@index.htmlWith a key, from an agent, it is one tool call:
publish_html({
content: "<h1>Q3 review</h1>…",
expires_in_hours: 168,
password: "optional"
})The second revision is where it shows
Agent loops do not produce one page, they produce fourteen versions of one page.
update_site writes the new bytes behind the existing token, so the address you already
sent out keeps resolving to the current draft:
curl -X PUT https://read.botook.ai/sites/SITE_ID \
-H "Authorization: Bearer otr_live_YOUR_KEY" \
-F file=@report.htmlThree gates, stacked or separate
noindex, no-referrer. The floor
under everything else.PasswordA passphrase in front of the page, set in the publish call or added later with
set_password.Email-domain gateOnly addresses at one work domain may open it, confirmed by a one-time link.Plus expiry — set_expiry in hours, or never — and burn-after-read,
which closes the page permanently after the first successful
open.
What you can hand it
The upload primitive is the same on every route, so nothing has to be converted before it is sent. A single document, a folder, or a drawing all go through one call:
| Sent as | Treated as | What the reader gets |
|---|---|---|
.html | html | The bytes you published, unchanged |
.md | markdown | Rendered to a self-contained document at publish time |
.svg | svg | Hosted as-is and served as an image |
.zip | multi-file site | Unpacked, with relative links between the files intact |
Markdown is rendered when it is published rather than when it is read, so the reader, the
preview card and /raw are all looking at the same stored document. Notebooks, diagrams and
slide decks travel as whatever they export to — this hosts the export, it does not run a
converter over your source.
What the reader sees
No account and no install: a reader opens the link and the page is simply there. Everything optional is off unless you asked for it.
- Nothing to sign into. The token in the URL is the credential — until you stack a password or an email-domain gate on top of it.
- An optional toolbar. With
set_agentationon, a reader can select any element and leave a note anchored to it, which you read back withlist_feedbackand close withresolve_feedback. - Optional reactions. A separate switch from feedback, because a reaction is an opinion and a note is a task.
- A link preview. Pasting the URL into a chat app renders a card from a real screenshot of the current revision, unless a gate is up — a page behind a password is never photographed.
What you learn afterwards
Sending a link and hearing nothing back is the normal failure of every tool in this category. Read-side numbers are collected per page and per revision without cookies or a third-party tag:
- Views and unique readers, and when the page was last opened.
- Dwell time and scroll depth, plus whether anyone reached the end.
- A ten-segment heat map showing where attention stopped.
- Unresolved notes and reaction counts, carried on the site object itself.
curl https://read.botook.ai/sites/SITE_ID/engagement \
-H "Authorization: Bearer otr_live_YOUR_KEY"Moving a page across from Display.dev
Export the HTML, publish it, and hand out the new link. If you were using the workspace purely to keep pages off the open internet, an unguessable URL plus an email-domain gate covers the same ground with no directory to maintain.
- Get the HTML — export it, view-source it, or ask the agent that wrote it for the file.
- Publish it: paste it on the home page, drop it in the dashboard, or
POST /siteswith your key. - Set the access rule the page deserves: password, email-domain gate, or nothing but the token.
- Send the new link, and let the old one expire rather than deleting it out from under anyone.
- Wire the agent up once — see integrations — so the next revision never passes through your hands.
Getting your pages back out
The question worth asking before you move anything in: how hard is it to leave? Every published document can be read back in full, by the key that owns it, in the exact bytes that are served — plus a plain-text rendering for whatever needs to re-read it as prose:
curl -s https://read.botook.ai/sites -H "Authorization: Bearer otr_live_YOUR_KEY" | jq '.sites[].id'
curl -s https://read.botook.ai/sites/SITE_ID/content -H "Authorization: Bearer otr_live_YOUR_KEY" -o page.html
curl -s https://read.botook.ai/raw/UNLISTED_TOKEN -o page.txtNothing is stored in a proprietary container and nothing is proxied from somewhere else — a
published page is the file you handed over, in object storage, addressed by a token. A loop that
wrote its output here can walk its whole list_sites result and pull every page down in one
pass, which is the only export guarantee that means anything.
Where the bytes actually live
This is a self-hosted deployment on Cloudflare: the request is handled at the edge, the document sits in object storage, and the metadata is one row in a SQL database. That has three consequences worth knowing before you compare it with a managed product.
| What it means | |
|---|---|
| Who the operator is | Whoever deployed it — for a self-hosted copy, that is you and your account, not a vendor. |
| What deletion does | Removes the row and the object together. There is no archive to restore from. |
| What survives a redeploy | Pages and keys. Storage is separate from the code, so shipping a new version is not a migration. |
The trade is explicit: you inherit the operational surface — the domain, the storage bill, the upgrade — in exchange for nobody else being able to change the terms under you. Self-hosting covers what that actually involves.
Evaluating both at once
You do not have to pick before you know. Publish the same document to both, send the pair to somebody, and compare the two things that only show up in real use: what the recipient has to do before they can read it, and what you can find out afterwards.
- Publish it here anonymously — no account, no project to create, one
curl. - Publish the same file to Display.dev the way you normally would.
- Send both links to the same reader and watch which one they open without asking a question.
- Change the document, and update each one. Note whether the link you already sent still works.
- Check what each side tells you afterwards about whether it was read.
Step 4 is where the difference usually lands, and it is the step people skip when comparing on a feature table. Publishing directly from the agent that wrote the page only pays off if the second revision costs nothing.
When Display.dev is the better answer
An organisation that already runs identity centrally and wants every internal page to inherit it will find the workspace model less work than configuring gates page by page. This is the better fit when the pages outnumber the people, or outlive them.
This page would be worth less if it pretended otherwise. The comparison that matters is between defaults, and defaults are only right relative to a job.
Questions people ask
Can I still restrict a page to my company?
Yes — set_email_gate with your domain. A reader enters a work address, gets a
one-time link by mail, and only addresses at that domain are accepted.
Is there a dashboard for humans?
Yes. It lists every page, its counters, its gates, and lets you publish, update or delete without a terminal.
What does it cost?
Nothing — it is self-hosted on Cloudflare, so pricing is a description of your own bill rather than a set of tiers.
Which agents can publish to it?
Anything that speaks MCP over HTTP: Claude Code,
Codex, Cursor, the
Claude.ai connector — or plain
curl from a script.
Where is the full reference?
The docs: every endpoint, all twelve tools, the upload formats and the defaults applied to each page.