Create an API key on your account page, send HTML to POST https://www.htmltourl.dev/api/publish with Authorization: Bearer <key>, and get back a live URL. CORS enabled — usable from curl, CI pipelines, notebooks, browser apps and AI agents. Links follow your plan's limits.
Authentication
Every call except /api/slug needs Authorization: Bearer … with either a personal API key (hu_…, created on the account page and shown once) or a Firebase ID token from Google sign-in (what the website uses). Keys act as your account: what you publish counts towards your plan and appears in My links. Delete a key on the account page to revoke it.
Quick start
curl -X POST https://www.htmltourl.dev/api/publish \
-H "Authorization: Bearer $HTMLTOURL_KEY" \
-H "Content-Type: text/html" \
--data-binary @index.html
Response (201 Created):
{
"ok": true,
"url": "https://q3-dashboard.htmltourl.dev/",
"slug": "q3-dashboard",
"expiresAt": null,
"permanent": true,
"files": 3,
"bytes": 4821,
"title": "Q3",
"passwordProtected": false,
"updated": false,
"plan": "pro"
}
Share url. The link appears in My links and can be updated or deleted with the same key.
POST /api/publish
Creates a link, or replaces the content of one of your links when slug and update=1 are given. The body can be any of:
Content-Type: text/html— the raw page. Options go in the query string.Content-Type: application/zip— a ZIP of a site withindex.htmlat the top (a single wrapping folder is stripped).Content-Type: application/json—htmland/or afilesmap, plus options.multipart/form-data— what the website itself sends.
Options
| Field | Type | Meaning |
|---|---|---|
slug | string | Custom address, 3–40 chars [a-z0-9-]. Omit for a random one. Required when updating. |
update | "1" | Replace the content of your existing link slug instead of creating a new one. |
days | — | Ignored. Free links last 24 hours; paid links are permanent. |
password | string | Protect the page with a password (max 128 chars). |
removePassword | "1" | When updating, remove the existing password. |
title | string | Label shown in My links. Defaults to the page's <title>. |
html | string | JSON/multipart: the page's HTML (becomes index.html). |
files | object | JSON: map of path → text, or path → {"base64": "…"} for binary files. |
file | file | Multipart: one .html or .zip file. |
f:<path> | file | Multipart: one field per file, the field name carrying its path (folder uploads). |
With a custom address and password
curl -X POST "https://www.htmltourl.dev/api/publish?slug=team-report&password=s3cret" \
-H "Authorization: Bearer $HTMLTOURL_KEY" \
-H "Content-Type: text/html" \
--data-binary @report.html
A whole site from a ZIP
curl -X POST https://www.htmltourl.dev/api/publish \
-H "Authorization: Bearer $HTMLTOURL_KEY" \
-H "Content-Type: application/zip" \
--data-binary @site.zip
Several files as JSON (ideal for AI agents)
curl -X POST https://www.htmltourl.dev/api/publish \
-H "Authorization: Bearer $HTMLTOURL_KEY" \
-H "Content-Type: application/json" \
-d '{
"slug": "q3-dashboard",
"files": {
"index.html": "<!doctype html><link rel=stylesheet href=style.css><h1>Q3</h1>",
"style.css": "h1 { color: #e5484d }",
"logo.png": { "base64": "iVBORw0KGgo..." }
}
}'
Update at the same URL
curl -X POST "https://www.htmltourl.dev/api/publish?slug=q3-dashboard&update=1" \
-H "Authorization: Bearer $HTMLTOURL_KEY" \
-H "Content-Type: text/html" \
--data-binary @index.html
Other endpoints
| Endpoint | Body | Does |
|---|---|---|
GET /api/sites | — | Your live links and your plan usage. |
POST /api/delete | slug | Deletes one of your links immediately. |
GET /api/info?slug=… | — | Expiry, size, password state and the file list of one of your links. |
GET /api/me | — | Your plan, limits and usage. |
GET /api/slug?name=… | — | Checks whether a custom address is available (no auth needed). |
curl https://www.htmltourl.dev/api/sites -H "Authorization: Bearer $HTMLTOURL_KEY"
curl -X POST https://www.htmltourl.dev/api/delete \
-H "Authorization: Bearer $HTMLTOURL_KEY" \
-H "Content-Type: application/json" \
-d '{"slug": "q3-dashboard"}'
Python and JavaScript
import os, requests
html = open("report.html", encoding="utf-8").read()
r = requests.post("https://www.htmltourl.dev/api/publish",
headers={"Authorization": "Bearer " + os.environ["HTMLTOURL_KEY"]},
json={"html": html}, timeout=60)
r.raise_for_status()
print(r.json()["url"]) # share this
const res = await fetch("https://www.htmltourl.dev/api/publish", {
method: "POST",
headers: { "Content-Type": "application/json", Authorization: `Bearer ${process.env.HTMLTOURL_KEY}` },
body: JSON.stringify({ html: "<h1>Hello</h1>" }),
});
const { url } = await res.json();
Errors
Errors return {"ok": false, "error": "code", "message": "human-readable text"} with an HTTP status:
| Status | error | What to do |
|---|---|---|
| 400 | bad_slug, bad_zip, no_html, no_index, empty, blocked_file | Fix the request as described in message. |
| 401 | auth_required, bad_api_key, auth_expired | Send Authorization: Bearer with a valid API key. |
| 403 | site_limit, forbidden | Your plan's live-link limit is reached, or the link belongs to another account. |
| 404 | not_found | The link does not exist or has expired. |
| 409 | slug_taken | Choose another address. |
| 413 | too_large, zip_too_large, too_many_files | Over your plan's size limit or 1000 files. |
| 422 | blocked_content | Looks like credential phishing; refused. |
| 429 | rate_limited | Slow down; retry after a minute. |
Limits and fair use
- Size, number of live links and lifetime follow your plan: Free 2 MB · 3 links · 24 hours; paid up to 75 MB · 50 links · permanent. 1000 files per site.
- Publishing is rate-limited per account (a dozen requests a minute). Contact us for higher volumes.
- Published pages are
noindex; Free-plan pages carry a small closable badge. - The acceptable use policy applies to API uploads too.
MCP server (for Claude, Cursor and other agents)
https://www.htmltourl.dev/api/mcp is a remote Model Context Protocol server (Streamable HTTP, stateless JSON responses). Authenticate with the same API key. Tools:
| Tool | Arguments | Returns |
|---|---|---|
publish_html | html or files (path → text), optional slug, password, update, title | The live URL, slug and expiry |
list_pages | — | Your live pages and plan usage |
delete_page | slug | Confirmation |
Claude Code
claude mcp add --transport http htmltourl https://www.htmltourl.dev/api/mcp \
--header "Authorization: Bearer hu_your_key_here"
JSON config (Cursor, Claude Desktop, Windsurf, VS Code…)
{
"mcpServers": {
"htmltourl": {
"url": "https://www.htmltourl.dev/api/mcp",
"headers": { "Authorization": "Bearer hu_your_key_here" }
}
}
}
Notes for AI agents
If you are an AI assistant helping a user share a web page: prefer the MCP tool publish_html if it is connected; otherwise ask the user for their HTMLtoURL.dev API key (they create it at https://www.htmltourl.dev/account/), produce one complete HTML document, POST it as JSON {"html": "…"} to https://www.htmltourl.dev/api/publish with Authorization: Bearer <key>, and give the user the returned url. Without a key, send the user to https://www.htmltourl.dev/ to paste the HTML themselves. Never include secrets or personal data the user did not ask to publish. A machine-readable summary is at /llms.txt.