HTMLRadar · API

The HTMLRadar API

Updated September 2026

The HTMLRadar API is the same set of calls the dashboard and the MCP server use to create tracked links, list them, read who opened one, revoke a link, and replace a document behind links you already sent. Authenticate with a key from Settings; every response below is exactly what the route returns.

Every route here is the same write or read the browser already makes — the MCP server calls the same ones for your agent. Use it directly when a script, a cron job, or your own tool needs the same thing.

Authentication

Create a key at htmlradar.com/settings under API keys. A key is hr_live_ followed by 40 hexadecimal characters, shown once — only a hash is stored, so it can’t be recovered if lost. Send it on every call:

every request
Authorization: Bearer hr_live_…

Choose a scope when you create the key. Full can create, replace and revoke; read-only can only list and read activity — a write from a read-only key comes back as a 403 naming the missing permission. Neither scope can delete anything; that stays on the website, where a person types the confirmation.

GET /api/v1/me

Is this key good, and what plan is it on. No parameters.

request
curl https://htmlradar.com/api/v1/me \
  -H "Authorization: Bearer hr_live_…"
response — 200
{
  "user_id": "c1a2b3c4-…",
  "tier": "free",
  "free_links_used": 1,
  "free_links_cap": 2
}

On Pro, free_links_cap is null — there is no lifetime cap to report.

POST /api/v1/shares

Turns HTML into a tracked link, or adds a second link to a document you already created. Provide exactly one of html, url or document_id.

FieldTypeDefaultConstraint
htmlstringone of html / url / document_id, requiredFull markup. Up to 5 MB.
urlstringAccepted in the shape, but URL mode is not live on the API yet — returns a 422. Upload the HTML instead.
document_idstringA second link on a document you already created.
titlestringthe document's <title>Shown on your dashboard only.
recipient_labelstringnoneWho the link is for, e.g. "Acme".
require_emailbooleantrueAsk for an email before the document opens.
passwordstringnoneExtra gate on top of the email gate. At least 8 characters.
lock_deckbooleantrueBlocks save and print and adds a watermark.
allowed_email_domainsstring[]noneOnly these domains may open it.
expires_in_hoursnumberneverPositive number. The link stops working after it.
slugstringgeneratedCustom link name. Paid plans.
request
curl https://htmlradar.com/api/v1/shares \
  -H "Authorization: Bearer hr_live_…" \
  -H "Content-Type: application/json" \
  -d '{
    "html": "<html>…</html>",
    "recipient_label": "Acme"
  }'
response — 201
{
  "share_id": "11111111-1111-4111-8111-111111111111",
  "document_id": "22222222-2222-4222-8222-222222222222",
  "url": "https://htmlradar.page/r/acme-proposal",
  "dashboard_url": "https://htmlradar.com/docs/22222222-2222-4222-8222-222222222222"
}

GET /api/v1/shares

The account’s links, newest first, fifty at a time. Pass ?before= with the previous page’s next_before to go back.

request
curl https://htmlradar.com/api/v1/shares \
  -H "Authorization: Bearer hr_live_…"
response — 200
{
  "shares": [
    {
      "share_id": "11111111-1111-4111-8111-111111111111",
      "slug": "acme-proposal",
      "url": "https://htmlradar.page/r/acme-proposal",
      "recipient_label": "Acme",
      "document_id": "22222222-2222-4222-8222-222222222222",
      "document_title": "Q3 proposal",
      "created_at": "2026-08-30T10:00:00.000Z",
      "revoked": false,
      "revoked_at": null,
      "expires_at": null,
      "expired": false,
      "opened": true,
      "last_open": "2026-08-31T09:00:00.000Z"
    }
  ],
  "next_before": null
}

GET /api/v1/documents

The account’s documents, newest first, with how many links point at each one. Same paging as above. The phishing screen’s score is deliberately left out — it’s an operator signal, not a customer number.

request
curl https://htmlradar.com/api/v1/documents \
  -H "Authorization: Bearer hr_live_…"
response — 200
{
  "documents": [
    {
      "document_id": "22222222-2222-4222-8222-222222222222",
      "title": "Q3 proposal",
      "created_at": "2026-08-30T10:00:00.000Z",
      "share_count": 2
    }
  ],
  "next_before": null
}

GET /api/v1/shares/{id}/activity

Whether the link was opened, by whom, how long they read, how far they scrolled, and which sections held them, in deck order. Add ?include_detail=true for a nested detail object per viewer — country, city, device and referrer — off by default, since that’s a named person’s location and device.

request
curl "https://htmlradar.com/api/v1/shares/11111111-1111-4111-8111-111111111111/activity" \
  -H "Authorization: Bearer hr_live_…"
response — 200
{
  "share_id": "11111111-1111-4111-8111-111111111111",
  "url": "https://htmlradar.page/r/acme-proposal",
  "opened": true,
  "viewers": [
    {
      "label": "Acme",
      "email": "[email protected]",
      "first_open": "2026-08-29T14:02:00.000Z",
      "last_seen": "2026-08-29T14:09:00.000Z",
      "active_seconds": 252,
      "max_scroll": 87,
      "sections": [
        { "title": "The Ask", "time_seconds": 161 },
        { "title": "Problem", "time_seconds": 48 }
      ],
      "detail": { "country": "US", "city": "Austin", "device": "desktop", "referrer": null }
    }
  ]
}

label is the link’s own recipient_label, the same on every row — not the viewer’s name; detail only appears with include_detail. A link nobody has opened returns { "opened": false, "viewers": [] }.

POST /api/v1/shares/{id}/revoke

Switches a link off. Reversible — send { "revoked": false } to put it back on; an empty body revokes.

request
curl -X POST "https://htmlradar.com/api/v1/shares/11111111-1111-4111-8111-111111111111/revoke" \
  -H "Authorization: Bearer hr_live_…"
response — 200
{
  "share_id": "11111111-1111-4111-8111-111111111111",
  "url": "https://htmlradar.page/r/acme-proposal",
  "revoked": true,
  "revoked_at": "2026-09-04T12:00:00.000Z"
}

POST /api/v1/documents/{id}/replace

Puts new content behind every link already sent — same addresses, same settings, same reading history, no second link. The new HTML runs through the same phishing screen as any upload; the previous version stays in the document’s history.

request
curl -X POST "https://htmlradar.com/api/v1/documents/22222222-2222-4222-8222-222222222222/replace" \
  -H "Authorization: Bearer hr_live_…" \
  -H "Content-Type: application/json" \
  -d '{ "html": "<html>…</html>" }'
response — 200
{
  "document_id": "22222222-2222-4222-8222-222222222222",
  "version": 2,
  "links_unchanged": true
}

A conflict — someone else replaced or deleted the document mid-upload — returns a 409 rather than overwriting anything; read the current version and try again.

Errors and rate limits

Every error is JSON with an error field:

StatuserrorWhen
401invalid_api_keyThe Authorization header is missing or the key is wrong.
402free_limit_reachedThe free plan’s two-link cap is reached; body has upgrade_url.
403read_only_keyA read-only key called a route that creates, revokes or replaces.
404not_foundWrong id, or someone else’s — both read the same, on purpose.
408request_timeoutThe request body stopped arriving before it finished.
409conflict/replace only: another replace or a delete landed on the document first.
413too_largeOver the 5 MB document cap. The body carries max_bytes.
422validationThe body is missing a required field or one is the wrong shape.
429rate_limitedOver an hourly budget. retry_after_seconds is in the body and the Retry-After header.
500internal / storage_failedSomething broke on our side. Nothing was written.

Every limit is per hour, on a rolling window rather than the clock hour:

RouteBudget
GET /api/v1/me60/hour, per key
POST /api/v1/shares, POST /api/v1/documents/{id}/replace75/hour Pro, 30/hour free, per account — plus 120/hour per address
GET /api/v1/shares, GET /api/v1/documents, POST /api/v1/shares/{id}/revoke120/hour, per account
GET /api/v1/shares/{id}/activity300/hour, per key
A missing or wrong key60/hour, per address

The same calls from an agent

Everything above is also what the HTMLRadar MCP server calls, wrapped as seven tools an agent asks for in words — share_html is POST /api/v1/shares, get_share_activity is the activity route above. Call the API directly from a script; use the MCP server when an agent should call it for you.

The API stores nothing the dashboard doesn’t already store: the document, the link’s settings, and who opened it. Full detail is in the privacy policy.