Publish an HTML page, get back a link that only people with its password can open. No account, no signup.
Hand dripdex an HTML page — dropped in a browser, POSTed, or published by an agent — and you get back two things:
The URL is not the secret. The password is the access control, it is checked on the server, and it is shown to you exactly once. Only a hash of it is stored, so nothing and nobody can read it back afterwards — not support, not the API, not the database. If you lose it, you rotate it; you do not recover it.
Give whoever you are sending it to both. A page whose password was not passed on is a page nobody can open.
There is one tier. Nothing here is raised by paying.
HTML and CSS only. These are not style preferences — they are enforced when the page is ingested, and content that breaks them is removed. Every removal is reported back to you.
| Fine | Removed |
|---|---|
HTML, inline <style>, style attributes | JavaScript of any kind: <script>, on* handlers, javascript: URLs |
| Images, fonts and CSS from inside your own upload | Anything loaded from elsewhere: remote <img>, webfont CDN, external stylesheet, @import |
data: URIs | <iframe> to another site, <base>, <meta http-equiv=refresh>, form actions |
Links out — <a href>, mailto: | SVG containing <script> or <foreignObject> |
<img> on a private page
reports who opened it, and when, to whoever serves that image. Upload the file
with the page instead, or inline it as a data: URI.
File types accepted: .html .htm .css
.png .jpg .jpeg .gif
.webp .avif .svg .ico
.woff .woff2. Anything else is rejected.
Write a complete document: doctype, <meta charset="utf-8">,
a <title>, and enough CSS that it reads well on a phone.
Four ways in. They are the same thing underneath — a page published one way is identical to a page published another.
No account and no sign-up. Two conveniences worth knowing:
A multi-file upload needs an index.html, but
dropping deck.html on its own renames it and tells you so, rather
than refusing over a filename.
Your first drop quietly creates an anonymous account, so later drops land in the same place and the page can list — and delete — what you have already dropped.
This is the door to send a person to. Point them here rather than explaining
curl to them.
curl -X POST https://dripdex.ai/v1/pages \
-H 'content-type: application/json' \
-d '{"html":"<!doctype html><h1>Q4</h1>","title":"Q4 deck"}'
Response (201):
{
"url": "https://dripdex.app/p/7p2wr6gq33",
"password": "otter-lantern-brisk-copper-mint",
"pageId": "…",
"slug": "7p2wr6gq33",
"token": "dd_live_…",
"tokenNote": "Save this. It is shown once and cannot be recovered."
}
Build the JSON with a tool rather than by hand — HTML is full of quotes and
backslashes, and a hand-escaped -d string breaks on the first one:
jq -Rs --arg t "Q4 deck" '{html:., title:$t}' page.html \
| curl -sS -X POST https://dripdex.ai/v1/pages \
-H 'content-type: application/json' \
${DRIPDEX_TOKEN:+-H "authorization: Bearer $DRIPDEX_TOKEN"} -d @-
password is generated unless you supply one, and is returned
either way. token comes back on the first call only
— see Your account is a token.
There is no list endpoint: the API can create, update and delete a page, not enumerate your pages.
dripdex publish deck.html
# https://dripdex.app/p/7p2wr6gq33
# password: otter-lantern-brisk-copper-mint
cat deck.html | dripdex publish - --json | jq -r .url
| Command | Does |
|---|---|
dripdex publish <file.html> | Publish a new page. - reads stdin. |
dripdex update <page-id> [file.html] | Replace the content, retitle, or rotate the password. |
dripdex rm <page-id> | Delete a page. |
Options: --title, --password, --rotate,
--json, --api <origin>.
Your token is saved to ~/.dripdex/config.json on the first
publish and reused after. $DRIPDEX_TOKEN overrides it. Everything
except the result goes to stderr, so --json on stdout stays
pipeable.
Agents can publish for you. There are three paths, in order of preference.
https://dripdex.ai/mcp is a single Streamable HTTP endpoint
exposing three tools:
publish_page(html, title?, password?)
update_page(pageId, html?, title?, password?, rotatePassword?)
delete_page(pageId)
No account, no OAuth, no key. Protocol revisions 2025-03-26
through 2026-07-28 are all served on the one endpoint. This works
where the agent's own request does not, because a connector is called by the
agent's client, outside the sandbox. The agent cannot add it —
you add https://dripdex.ai/mcp to your client's
connectors, once.index.html where you will receive it and sends you to
dripdex.ai to drop it yourself. It should then
stop — the password is shown once, on your screen, so an agent that reports a
URL and password it never saw has invented them.dripdex.ai. The reading domain is for your
recipients and never needs to be reachable from the sandbox.
Send the URL and the password. That is the whole handover; there is nothing to invite, provision, or add to a team.
Passwords are five words — otter-lantern-brisk-copper-mint —
because this string gets read off one screen and typed into another, dictated on
a call, and copied out of a chat by hand. A typo in it is a misspelt word rather
than an invisible character swap.
If you would rather choose your own, pass one at publish time. It is stored the same way and is equally unrecoverable.
What your recipient does:
Guessing is metered: 10 attempts per person per page per 10 minutes, and 200 per page per hour across everyone.
A link that names nothing, a page belonging to someone else, and a deleted page all answer the same blank “not found”. That is deliberate: it means these URLs cannot be used to discover which pages exist.
All three need your token. There is no anonymous way to change a page.
# replace the content — the link and password are unchanged
curl -X PATCH https://dripdex.ai/v1/pages/<pageId> \
-H "authorization: Bearer $DRIPDEX_TOKEN" -H 'content-type: application/json' \
-d '{"html":"<!doctype html><h1>Revised</h1>"}'
# rotate the password — the old one stops working immediately
curl -X PATCH https://dripdex.ai/v1/pages/<pageId> \
-H "authorization: Bearer $DRIPDEX_TOKEN" -H 'content-type: application/json' \
-d '{"rotatePassword":true}'
# delete
curl -X DELETE https://dripdex.ai/v1/pages/<pageId> \
-H "authorization: Bearer $DRIPDEX_TOKEN"
dripdex update <page-id> revised.html --title "v2"
dripdex update <page-id> --rotate
dripdex rm <page-id>
PATCH takes any subset of html, title,
password, rotatePassword. An empty patch is an error
rather than a silent no-op, and the response reports what actually changed rather
than echoing what you asked for.
Four behaviours to plan around:
Sending new html publishes a new version behind
the same URL, so a link you already sent a client keeps working and starts
showing the new content.
Not just future visitors. Anyone currently viewing loses access on their next click.
If they unlock in the same few seconds you change the password. That is the system erring toward locked, which is the right direction.
The link stops working for everyone, including people already reading.
The token is the whole credential. dd_live_… is
returned exactly once, on the first call that creates your account, and only a
hash of it is kept. There is no password reset, because there is no account to
reset.
Authorization: Bearer dd_live_… to keep pages under
one account. Omit it and every page belongs to a fresh, unrelated account you
cannot come back to.The email field is not a login. Typing an address records a claim on that upload — nothing more. It does not sign you in and it cannot reach an account that already exists. A claim becomes ownership only when a link sent to that address is opened, which is the one piece of evidence this system will accept that the address is yours; your pages then move to the verified account.
| Limit | Value | Notes |
|---|---|---|
| Page size | 4 MiB | Through every door. Measured as UTF-8 bytes of what you send. |
| Request body (API) | 5 MiB | Rejected before the page is even looked at. |
| Files per upload | 50 | |
| New pages | 30 per day | Rolling, per account — see below. |
| Publishes per hour | 60 | |
| Unlock attempts | 10 / person / page / 10 min 200 / page / hour | |
| Stays unlocked | 7 days | Per page, per reader. |
The 30 a day is a rolling window, not a midnight reset. Each page frees its own slot 24 hours after you create it. There is no cap on how many pages an account holds in total, and deleting a page does not buy back an allowance — the budget is spent on creation, so a page you deleted still counted. One tier, no paid upgrade: nothing raises either number.
The page size is not a pricing decision. A page reaches this service inside a single request payload capped at 6 MiB, measured after the transport re-encodes your request into it — 4 MiB is what survives that for every shape of HTML. The API's 5 MiB body cap sits a little above the page ceiling because escaping a full-size page into JSON costs more than the page itself.
Inline data: URIs count in full, and base64 is about a third
larger than the bytes it carries, so images are what put a page over,
essentially always. Too big is not something to retry — downscale images
to the width they are actually displayed at, re-encode photographs as JPEG or
WebP, and use SVG for anything drawn. Publishing an oversized page and then
patching a smaller one still spends the quota on the failed attempt.
JSON, with a stable code.
| Status | code | Means |
|---|---|---|
| 400 | html-required, title-invalid, password-invalid, rotate-password-invalid | Bad request body |
| 400 | nothing-to-update | Empty PATCH |
| 401 | unauthorized | Unknown or revoked token |
| 413 | content-rejected | Over the size cap, or body over 5 MiB |
| 422 | content-rejected | Rejected on ingest; details says exactly what and where |
| 429 | rate-limited | Too fast, or the day's 30 pages are spent; carries Retry-After |
| 404 | not-found, gone | No such page, not yours, or already deleted |
A page belonging to another account answers 404, identically to
one that never existed. That is deliberate and not a bug to work around.
They pointed at another site and were removed. Upload them together with the
page, or inline them as data: URIs. The publish response lists
every removal with the file and element involved.
An external stylesheet or @import went the same way. Move the
CSS into a <style> block, or upload the .css
file with the page.
Correct, and permanent: no JavaScript, ever. That constraint is what makes a
hosted page safe to open. You can still do a surprising amount with CSS alone —
:target, :checked, <details> — or
publish a few linked pages.
Read details. It names the reason
(external-resource, script-element, and so on), the
file, and the element or attribute.
The page is too big. Shrink the images; do not retry as-is.
Check Retry-After. Your allowance frees up as your earliest
pages of the day pass 24 hours old. Nothing raises it — there is one tier.
Either this call used a different token than the one that published it, or the page was deleted. The two are indistinguishable on purpose.
It cannot be recovered. Rotate it — --rotate, or
{"rotatePassword":true} — and send the new one out. Everyone
currently reading loses access.
Your pages keep serving; you can no longer update or delete them. New publishes will start a fresh account unless you are in the browser that holds the cookie.
Ready? Drop a page at dripdex.ai. What we keep, and for how long: privacy.