HaveTML Docs
Publish, share, and automate your artifacts
HaveTML gives single-file HTML and Markdown a permanent link, a project home, feedback tools, and API access for browsers, scripts, and AI tools.
Upload
Drop HTML or Markdown into the dashboard.
Share
Send the artifact or project link to readers.
Automate
Use REST, MCP, or the Chrome extension.
01
Start with a link
- Create an account, or sign in if you already have one.
- Open the Dashboard and drop a
.html,.htm,.md, or.markdownfile into the upload zone. - Open the artifact link at
/f/:slug, then share it with anyone who should read it.
Markdown files render as readable documents. HTML files run in a sandboxed viewer so the artifact cannot access the HaveTML app origin.
02
Manage the record
The dashboard is where owners organize projects, update files, and manage reader context. Re-uploading a new version keeps the artifact link stable while preserving version history.
- Projects: group artifacts under a public
/p/:slugpage with a Markdown description. - Feedback: collect comments and pins from signed-in readers, or turn comments off per artifact.
- Reader context: add Markdown notes, links, and supporting Markdown docs beside an artifact.
- Access controls: add a password or expiry date when a share link should be limited.
03
Create an API token
API tokens let the extension, MCP server, and your own scripts upload artifacts. Create and revoke tokens from Dashboard API access. The full token is shown once, so store it somewhere private.
Authorization: Bearer havetml_your_token_here04
REST API
Send JSON requests with a bearer token. File uploads accept filename, content, optional title, and optional project slug. Re-uploading the same title or filename into the same project creates a new version.
curl -X POST https://havetml.com/api/v1/files \
-H "Authorization: Bearer havetml_your_token_here" \
-H "Content-Type: application/json" \
-d '{
"filename": "dashboard.html",
"content": "<!doctype html><html>...</html>",
"title": "Dashboard",
"project": "launch-notes"
}'GET /api/v1/files
GET /api/v1/files?project=launch-notes
POST /api/v1/files
GET /api/v1/projects
POST /api/v1/projects
GET /api/v1/artifacts/:slug/comments
POST /api/v1/artifacts/:slug/comments
GET /api/v1/artifacts/:slug/collaborators
POST /api/v1/artifacts/:slug/collaborators
DELETE /api/v1/artifacts/:slug/collaborators
GET /api/v1/projects/:slug/members
POST /api/v1/projects/:slug/members
DELETE /api/v1/projects/:slug/members
POST /api/v1/projects body:
{
"name": "Launch notes",
"description": "Markdown shown on the public project page"
}
POST /api/v1/artifacts/:slug/comments body:
{
"body": "Comment text",
"anchor": { /* optional pin location */ },
"parent_id": "uuid", // optional for threaded replies
"version": 1 // optional, defaults to current
}
POST /api/v1/artifacts/:slug/collaborators body:
{
"email": "editor@example.com",
"role": "editor" // "viewer" or "editor", defaults to "viewer"
}
DELETE /api/v1/artifacts/:slug/collaborators body:
{
"email": "editor@example.com"
}
POST /api/v1/projects/:slug/members body:
{
"email": "member@example.com",
"role": "editor" // "viewer" or "editor", defaults to "viewer"
}
DELETE /api/v1/projects/:slug/members body:
{
"email": "member@example.com"
}| Error | What to do |
|---|---|
| missing_token | Add Authorization: Bearer havetml_... |
| invalid_token | Create a new token or check the one you sent. |
| project_not_found | Use an existing project slug. |
| unsupported_type | Upload .html, .md, .yaml, .json, or .jsx/.tsx. |
| too_large | Keep single-file artifacts under 3 MB. |
| empty_content | Send a non-empty content string. |
| name_required | Send a non-empty project name. |
05
MCP for AI tools
Signed in? The connect wizard does this for you: pick your tool, copy one prefilled config, and check the connection. The configs below are the same ones it writes.
HaveTML exposes a Streamable HTTP MCP endpoint at /api/mcp. Use a dashboard token in the Authorization header. Set up whichever client you use:
Claude asking for OAuth?
If Claude shows an Add custom connector form with OAuth Client ID and OAuth Client Secret, paste https://havetml.com/api/mcpas the remote MCP server URL and leave both OAuth fields blank. Claude will discover HaveTML's OAuth flow and redirect you here to approve access.
For local-only Claude Desktop setup, you can still use Desktop app settings, then Developer, then Edit Config. That path uses a localmcp-remotebridge instead of Claude's cloud connector.
Claude connector
Name: HaveTML
Remote MCP server URL: https://havetml.com/api/mcp
OAuth Client ID: (leave blank)
OAuth Client Secret: (leave blank)Claude → Customize → Connectors → Add custom connector. Paste the URL, leave OAuth Client ID/Secret blank, then approve access on HaveTML.
Claude Code
claude mcp add --transport http havetml https://havetml.com/api/mcp \
--header "Authorization: Bearer havetml_your_token_here"Run in any terminal, then type /mcp in Claude Code to confirm it connected.
Codex
# ~/.codex/config.toml
[mcp_servers.havetml]
url = "https://havetml.com/api/mcp"
bearer_token_env_var = "HAVETML_TOKEN"
# then, in your shell profile:
export HAVETML_TOKEN="havetml_your_token_here"Add to ~/.codex/config.toml. ChatGPT desktop, Codex CLI, and the Codex IDE extension share this config.
Cursor
{
"mcpServers": {
"havetml": {
"url": "https://havetml.com/api/mcp",
"headers": {
"Authorization": "Bearer havetml_your_token_here"
}
}
}
}Global: ~/.cursor/mcp.json — or .cursor/mcp.json inside a project.
Claude Desktop
{
"mcpServers": {
"havetml": {
"command": "npx",
"args": [
"-y",
"mcp-remote",
"https://havetml.com/api/mcp",
"--header",
"Authorization:${AUTH_HEADER}"
],
"env": {
"AUTH_HEADER": "Bearer havetml_your_token_here"
}
}
}
}Use Desktop app → Developer → Edit Config, not Customize → Connectors. This uses mcp-remote as a local bridge; restart Claude Desktop after saving.
Other MCP clients
{
"mcpServers": {
"havetml": {
"url": "https://havetml.com/api/mcp",
"headers": {
"Authorization": "Bearer havetml_your_token_here"
}
}
}
}Use this for tools that support Streamable HTTP remote servers. For stdio-only clients, use the Claude Desktop bridge pattern.
upload_artifact: upload HTML or Markdown and get a permanent link.create_project: create a shareable project page.list_projects: list project slugs, names, and file counts.list_artifacts: list artifacts, optionally scoped to one project slug.add_comment: add a comment to an artifact, with optional anchor or parent_id for threaded replies.list_comments: list all comments on an artifact.invite_collaborator: invite someone by email to view or edit an artifact.list_collaborators: list collaborators with access to an artifact.revoke_collaborator: revoke access to an artifact.invite_project_member: invite someone to a project. Project editors inherit edit access to all artifacts in that project.list_project_members: list members of a project.revoke_project_member: revoke project access.list_events: poll for changes to your artifacts — JSON state updates and new comments.register_webhook: get those same changes pushed to an HTTPS endpoint instead of polling.list_webhooksanddelete_webhook: manage registered webhooks.
Knowing when an artifact changed
People edit artifacts after you hand them over: they tick things off an interactive checklist, fill in a form, leave a comment. Every one of those writes appends an event you can read back, either by polling or by having it pushed to you.
list_events is the simple path. Call it with no arguments for everything you own, or with a slug for one artifact. Each response carries a cursor; pass it back as sincenext time and you get only what has happened since. Events are summaries — the slug, which top-level JSON keys changed, a comment's id and the first 280 characters of its body — so follow up with get_artifact when you need the full state.
Verifying a webhook delivery
register_webhook returns a signing secret once, at registration. Every delivery is a JSON POST carrying X-HaveTML-Signature: the hex HMAC-SHA256 of the raw request body under that secret. Compute it over the bytes you received, before parsing, and compare with a constant-time check.
Delivery is best-effort and retried with backoff, so treat it as at-least-once: the same id can arrive twice, and events are not guaranteed to arrive in order. Reply 2xx quickly and do the work afterwards.
POST https://your-endpoint.example.com/havetml
X-HaveTML-Signature: 9f86d081884c7d65...
X-HaveTML-Event: json_updated
{
"id": 1284,
"type": "json_updated",
"slug": "a1b2c3d4",
"file_id": "3f1e...",
"created_at": "2026-09-04T12:00:00.000Z",
"data": {
"slug": "a1b2c3d4",
"title": "Launch checklist",
"size_bytes": 412,
"previous_size_bytes": 388,
"changed_keys": ["tasks"]
}
}06
Chrome extension
The extension uploads HTML or Markdown from the browser toolbar and copies the share link to your clipboard.
Install HaveTML from the Chrome Web Store ↗
- Install the extension from the Chrome Web Store and pin it to your toolbar.
- Open the HaveTML extension settings and paste an API token from Dashboard API access.
- Pick a project if needed, then drop a
.htmlor.mdfile.
Re-uploading a file with the same name into the same project creates a new version.