Developers

API reference

Everything in the dashboard is available over HTTPS with JSON. Authenticate with an API key from Settings. Prefer natural language? Use the MCP server.

Authentication

Send your key as a bearer token. Keys start with htk_live_ and can be revoked anytime.

Authorization: Bearer htk_live_YOUR_KEY

Publish in one call

Request
curl -X POST https://htmltoolz.com/api/v1/sites \
  -H "Authorization: Bearer $HTMLTOOLZ_KEY" \
  -H "Content-Type: application/json" \
  -d '{"html": "<h1>Hello, Acme</h1>", "title": "Acme preview", "access": "password", "password": "acme-q4"}'

Upload a folder as JSON files ([{"path":"index.html","content":"…"},{"path":"img/logo.png","content":"…base64…","encoding":"base64"}]) or send a .zip as multipart file.

Response · 201
{
  "id": "k3Vq9TbXw2LmPa",
  "slug": "acme-preview",
  "url": "https://acme-preview.htmltoolz.page/",
  "title": "Acme preview",
  "access": "password",
  "status": "live",
  "version": 1,
  "score": 94,
  "checks": { "score": 94, "grade": "A", "issues": [] },
  "fixed": []
}

You can also send the page as a raw text/html body, or a .zip body with Content-Type: application/zip. Add "autofix": true to apply safe launch-check fixes (viewport, title, social tags…) before publishing.

Endpoints

MethodPathDescription
GET/api/v1/reviewsClient review queue (Pro). Filter and paginate with ?workspace=, ?q=, ?status=, ?before=, ?limit=.
GET/api/v1/reviews/{id}One client review task in the selected workspace (Pro).
PATCH/api/v1/reviews/{id}Change priority, state, due_date or assigned_to (Pro, full-access key).
POST/api/v1/sitesPublish a new link. JSON body with html or files[], or multipart with a file / .zip.
GET/api/v1/sitesList your links. Filters: ?query=, ?client=
GET/api/v1/sites/{slug}One link, including the latest launch checks.
PUT/api/v1/sites/{slug}Publish a new version to the same URL (same body as create, optional note).
PATCH/api/v1/sites/{slug}Update title, client, access (+password) and feature flags.
DELETE/api/v1/sites/{slug}Delete a link and everything attached to it.
GET/api/v1/sites/{slug}/versionsVersion history with launch-check scores.
POST/api/v1/sites/{slug}/restoreMake an earlier version live again: {"version": 3}
GET/api/v1/sites/{slug}/feedbackComments, approvals and the current decision.
GET/api/v1/sites/{slug}/activityTotals, daily series, recent opens and known viewers. ?days=
GET/api/v1/eventsWorkspace history (Pro). ?workspace=, ?action=, ?via=, ?before=, ?limit=
GET/api/v1/sites/{slug}/eventsHistory for one link (Pro). ?action=, ?via=, ?before=, ?limit=
GET/api/v1/sites/{slug}/analyticsDate ranges, daily totals and equal-period comparisons (Pro).
GET/api/v1/sites/{slug}/pagesReading time and completion by page (Pro).
GET/api/v1/sites/{slug}/clicksClicks and click-through rates by page and destination (Pro).
GET/api/v1/sites/{slug}/audienceDevices, countries and sources with reading engagement (Pro).
GET/api/v1/sites/{slug}/campaignsCampaign tags, reading engagement and click-through (Pro). ?source=, ?campaign=
GET/api/v1/sites/{slug}/viewersKnown readers within the retained range (Pro). ?before=, ?limit=
GET/api/v1/sites/{slug}/viewers/{id}One reader’s pages and clicks (Pro). ?before=, ?limit=
GET/api/v1/followupsInterested readers across live links, with suggested drafts (Pro). ?workspace=, ?client=, ?min_score=
GET/api/v1/sites/{slug}/submissionsForm submissions. ?limit=
GET/api/v1/sites/{slug}/recipientsPersonal links and how each person engaged (Pro).
POST/api/v1/sites/{slug}/recipientsCreate personal links: {"name", "email"} or {"recipients": [...]}
DELETE/api/v1/sites/{slug}/recipients/{id}Revoke a personal link.
POST/api/v1/sites/{slug}/duplicateCopy a link (live files, settings, access) to a new address. Optional {"title"}.

Reporting & follow-ups Pro

Connect your own reports or CRM with a read-only API key. The reporting endpoints accept days (default 7), or from and to as YYYY-MM-DD dates. The response gives the actual range, timezone and whether the start was capped by your plan or visitor retention setting. Future dates are rejected.

visitor_days counts anonymous visitors separately each day. It does not identify unique people across days. Time and depth depend on the visitor’s browser sending measurements; a missing measurement is not proof that someone did not read.

The summary compares the preceding period of the same length. If that full period is outside your history, comparison.available is false. A percentage change from zero is null. Page, click, campaign and audience lists mark omitted rows with truncated.

Find who to follow up with
curl 'https://htmltoolz.com/api/v1/followups?days=7&client=Acme' \
  -H "Authorization: Bearer $HTMLTOOLZ_KEY"

Follow-ups rank known readers by recent engagement (60/100 by default). Each suggestion includes the reason, a dashboard activity URL and an editable message. Nothing is sent. Approved, archived, closed, future, sample and template links are excluded, along with blocked viewers and revoked personal links. A shared personal link may identify someone else; check the context before contacting them.

Use limit (1–100, default 50). History and viewer responses return next_before: pass it as before until it is null. Page through viewers by descending viewer ID and visits by descending view ID. Other reports are ranked lists; narrow the range or client when truncated is true. Follow-ups inspect at most the 5,000 most recently active reader/link pairs.

Workspace endpoints default to your account. Teammates may pass workspace with an Agency owner’s account ID; accepted membership and the owner’s plan are checked. Dashboard workspace cookies do not affect API calls. Named readers and workspace history are private: do not forward them into a client-facing report.

In Claude or ChatGPT, use get_link_analytics, get_viewer_history, get_workspace_history or get_followups. Read-only keys support all four.

Webhooks

Add endpoints in Settings → Webhooks (Pro: 3, Agency: 10). Each event is a JSON POST with these headers:

  • HTMLToolz-Event: link.opened, link.published, comment.created, approval.decided, form.submitted, cta.clicked, viewer.removed, files.broken, review.updated or ping
  • HTMLToolz-Delivery: the event id, the same on every retry, so you can ignore duplicates
  • HTMLToolz-Signature: t=1791500000,v1=…, where v1 is the hex HMAC-SHA256 of {t}.{raw body} with your endpoint's signing secret

Answer with any 2xx within 6 seconds. Anything else is retried after 1 minute, 5 minutes, 30 minutes, 2 hours and 12 hours. After 25 failures in a row the endpoint is paused until you resume it.

form.submitted
{
  "id": "evt_6f1c0a9be24d47c1a3e8d2f0",
  "type": "form.submitted",
  "created_at": "2026-10-08T21:14:03Z",
  "data": {
    "link": { "id": "k3Vq9TbXw2LmPa", "slug": "acme-preview", "title": "Acme preview", "client": "Acme", "url": "https://acme-preview.htmltoolz.page/" },
    "form": { "name": "contact", "page": "/" },
    "fields": { "name": "Jo", "email": "jo@acme.com", "message": "Let's talk" },
    "email": "jo@acme.com"
  }
}
Verify a signature (Node.js)
const crypto = require('crypto');
function verify(rawBody, header, secret) {
  const { t, v1 } = Object.fromEntries(header.split(',').map(p => p.split('=')));
  if (Math.abs(Date.now() / 1000 - Number(t)) > 300) return false; // replayed
  const want = crypto.createHmac('sha256', secret).update(t + '.' + rawBody).digest('hex');
  return typeof v1 === 'string' && v1.length === want.length && crypto.timingSafeEqual(Buffer.from(want), Buffer.from(v1));
}

Other events carry viewer (link.opened), version (link.published), comment (comment.created) or decision, note and by (approval.decided), button and viewer (cta.clicked), viewer (viewer.removed) or files, the outside URLs that stopped loading (files.broken), always beside link.

Errors & limits

Errors return a JSON body {"error": {"code": "…", "message": "…"}} with a meaningful status: 401 bad key, 402 the feature needs a higher plan, 404 not your link, 409 slug taken, 413 upload too large, 422 invalid or blocked content, 429 daily request limit (see Retry-After).

Free accounts get 200 API requests a day; paid plans get 10,000. Size, file and link limits follow your plan.

Client review tasks

Pro workspaces can read GET /api/v1/reviews and GET /api/v1/reviews/{id}, then update a task with PATCH /api/v1/reviews/{id} using a full-access key. Read-only keys and MCP get_review_tasks can read the queue.

PATCH accepts only priority (normal, high, urgent), state (open, in_progress, waiting, resolved), due_date (YYYY-MM-DD or empty to clear) and assigned_to (owner/current teammate ID or 0 to clear). Client comments and private team replies are separate from these work controls.

Filter the queue by client, site, q, path, version (live/older), status (open/resolved/all), state, priority, assigned (all/mine/unassigned), and due (overdue/today/week/none). Use limit (1–100) and before; keep the filters and follow next_before for more threads. Results are newest-thread-first, exclude archived links and include the timezone used for due dates.

Teammates choose ?workspace=OWNER_ID explicitly, including on PATCH. Dashboard cookies never select an API workspace. A removed teammate loses access immediately. Responses omit viewer emails, authentication material and private reply text.

The review.updated webhook carries the thread ID and changed task fields beside the link summary, with no comment text. Changes also appear in the workspace activity log. CSV from Review work exports only the current filtered page.

MCP

The remote MCP endpoint is https://htmltoolz.com/mcp (Streamable HTTP, same bearer key). Setup for Claude Code, Claude Desktop and Cursor is on the MCP page.

Use it in ChatGPT

Give your own GPT a "publish this" button. ChatGPT builds the page, then hands you a live link.

  1. In ChatGPT, open Explore GPTs → Create, then Configure → Actions → Create new action.
  2. Choose Import from URL and paste https://htmltoolz.com/openapi.json.
  3. Under Authentication pick API Key → Bearer and paste a key from Settings.
  4. Add to the GPT's instructions: "When I ask to share, send or host a page, call publishSite with the full HTML and show me the url. To change it later, call updateSite with the same slug."

Privacy policy URL for the GPT: https://htmltoolz.com/privacy.