MCP server

HTMLvault exposes a hosted Model Context Protocol (MCP) server so AI agents can publish and manage secure links directly — no curl, no glue code. The endpoint is Streamable HTTP at https://htmlvault.dev/api/mcp and is available to any Pro or Enterprise account with an API key.

Endpointhttps://htmlvault.dev/api/mcp
TransportStreamable HTTP (JSON-RPC 2.0 over a single POST)
AuthAuthorization: Bearer <key> — Pro or Enterprise API key
Toolslist_links · get_link · get_analytics · create_link · update_link · delete_link · scan_html

Quick start

Generate an API key on the Integrations screen, then connect your client. Replace htmlvault_YOUR_KEY with your full key in every snippet below.

Claude Code

Run once in your terminal — HTMLvault is then available in every Claude Code session.

claude mcp add --transport http htmlvault https://htmlvault.dev/api/mcp \
  --header "Authorization: Bearer htmlvault_YOUR_KEY"

Claude Desktop / Claude.ai (custom connector)

Add a custom connector pointing at the endpoint, or use the mcp-remote bridge in claude_desktop_config.json:

claude_desktop_config.json

{
  "mcpServers": {
    "htmlvault": {
      "command": "npx",
      "args": ["-y", "mcp-remote", "https://htmlvault.dev/api/mcp",
               "--header", "Authorization: Bearer htmlvault_YOUR_KEY"]
    }
  }
}

Cursor

Add to ~/.cursor/mcp.json (or the project-level .cursor/mcp.json):

~/.cursor/mcp.json

{
  "mcpServers": {
    "htmlvault": {
      "url": "https://htmlvault.dev/api/mcp",
      "headers": { "Authorization": "Bearer htmlvault_YOUR_KEY" }
    }
  }
}

Anthropic API (MCP connector)

Pass the endpoint in the mcp_servers array of a Messages API request (requires the MCP connector beta header):

{
  "model": "claude-sonnet-4-6",
  "mcp_servers": [
    {
      "type": "url",
      "url": "https://htmlvault.dev/api/mcp",
      "name": "htmlvault",
      "authorization_token": "htmlvault_YOUR_KEY"
    }
  ],
  "messages": [{ "role": "user", "content": "Vault this report and give me the link." }]
}

ChatGPT

ChatGPT connects to HTMLvault through a Custom GPT Action over the REST API rather than MCP. The import-ready OpenAPI schema and step-by-step setup live on the Integrations screen.

Authentication

Every request carries a bearer token in the Authorization header. Keys are created and revoked on the Integrations screen and are shown once at generation time.

Authorization: Bearer htmlvault_YOUR_KEY

Personal vs. org keys

Personal keys require a Pro or Enterprise plan and act in the key owner’s own workspace.

Org keys (Enterprise) act on behalf of the organization owner’sworkspace — every tool call is attributed to the owner’s account, and links belonging to individual members (under their personal accounts) are notvisible through an org key. If you list or fetch links with an org key you see the owner’s links, not the whole organization’s.

Auth errors

FieldTypeDescription
missing_key401No Authorization header (or empty bearer). Message: “No API key provided. Include the header: Authorization: Bearer <key>. Create a key at https://htmlvault.dev/connect”
invalid_key401Key is not recognized or has been revoked. Message: “API key not recognized or has been revoked. Manage your keys at https://htmlvault.dev/connect”
plan_required403Key is valid but the account is on the Free plan. Message: “MCP access requires a Pro or Enterprise plan. Upgrade at https://htmlvault.dev/pricing”

The 401 cases share a shape ({ "error": "...", "message": "..." }); the free-plan case is a distinct 403so a client can tell “wrong key” from “upgrade needed.”

Tool reference

Seven tools. Read-only tools (list_links, get_link, get_analytics, scan_html) are annotated readOnlyHint: true; delete_link is annotated destructiveHint: true. Every tool returns its JSON result inside the MCP content envelope as a text block.

List your links, newest first, with cursor pagination.

FieldTypeDescription
limitintegerResults per page. Default 20, maximum 100.
cursorstringOpaque pagination token. Pass next_cursor from a previous response to fetch the next page.
created_afterstringISO 8601 timestamp — return only links created strictly after this time.
created_beforestringISO 8601 timestamp — return only links created strictly before this time.

result

{
  "links": [
    {
      "slug": "a1b2c3d4e5f6g7h8",
      "title": "Q3 Proposal",
      "created_at": "2026-06-10T18:22:04.511Z",
      "expires_at": null,
      "view_count": 42,
      "is_public": true,
      "url": "https://htmlvault.io/a1b2c3d4e5f6g7h8"
    }
  ],
  "next_cursor": "MjAyNi0wNi0xMFQxODoyMjowNC41MTFafGExYjJjM2Q0",
  "has_more": true
}

When has_more is true, call again with cursor set to next_cursor. When it is false, next_cursor is null and you have the last page.

Fetch one link’s metadata and full stored HTML.

FieldTypeDescription
slugrequiredstringThe link slug.

result

{
  "slug": "a1b2c3d4e5f6g7h8",
  "title": "Q3 Proposal",
  "html_content": "<!doctype html>…",
  "created_at": "2026-06-10T18:22:04.511Z",
  "expires_at": null,
  "view_count": 42,
  "is_public": true,
  "url": "https://htmlvault.io/a1b2c3d4e5f6g7h8"
}

Returns { "error": "Link not found" } if the slug is not owned by the caller.

get_analytics

View metrics for one link — totals, engagement, and breakdowns.

FieldTypeDescription
slugrequiredstringThe link slug.
period"7d" | "30d" | "90d" | "all"Time window for the metrics. Default 30d.

result

{
  "slug": "a1b2c3d4e5f6g7h8",
  "period": "30d",
  "totals": { "views": 318, "unique_views": 204, "repeat_views": 114, "bot_views": 27 },
  "engagement": { "avg_time_on_page_seconds": 47, "avg_scroll_depth_pct": 68 },
  "breakdowns": {
    "countries": [{ "value": "United States", "views": 190 }],
    "devices":   [{ "value": "desktop", "views": 240 }],
    "channels":  [{ "value": "clay-icp-ceos", "views": 35 }],
    "referrers": [{ "value": "https://mail.google.com/", "views": 60 }]
  }
}

Automated traffic is excluded from views, engagement, and every breakdown; bot hits are counted separately as bot_views so the number is visible without polluting the human totals. Metric definitions match the dashboard exactly. Returns { "error": "Link not found" } if the slug is not owned by the caller.

Publish HTML as a secure, shareable link. Content is checked against Google Safe Browsing and your account’s PII scan policy before it goes live.

FieldTypeDescription
htmlrequiredstringComplete HTML to publish. 5 MB limit.
titlerequiredstringShort descriptive title shown in your dashboard.
expiresIn"1h" | "24h" | "3d" | "7d" | "30d" | "never"Expiry window. Omit to use your account default.
tracking"none"Set to none to publish with no tracking pixels or default tracking code. Omit to apply your tracking defaults.
domainstringHostname of one of your active custom domains to serve from. Omit to inherit your account default domain.

result

{
  "slug": "k9m2p4q7r1s8t3v6",
  "url": "https://htmlvault.io/k9m2p4q7r1s8t3v6",
  "title": "Q3 Proposal"
}

If your account’s PII policy is set to warn (rather than block), the link still publishes and the response includes a warnings array describing the categories found. See Errors for the block cases. A successful MCP create_link fires the link.created webhook.

Replace the HTML of an existing link in place — the slug and public URL are unchanged, so a link you have already shared updates live. Re-runs the same Safe Browsing and PII policy gates as publishing.

FieldTypeDescription
slugrequiredstringThe link slug to update.
htmlrequiredstringNew HTML content.

result

{
  "slug": "k9m2p4q7r1s8t3v6",
  "url": "https://htmlvault.io/k9m2p4q7r1s8t3v6",
  "updated_at": "2026-06-13T15:04:11.882Z"
}

Returns { "error": "Link not found or not owned" } if the slug is not yours. Fires the link.updated webhook on success.

Delete one of your links.

FieldTypeDescription
slugrequiredstringThe link slug to delete.

Deletion is immediate and irreversible. The link row is hard-deleted and all of its view records are removed with it (cascade). It does not pass through the retention window — there is no recovery period. A link under a legal hold cannot be deleted.

result

{ "deleted": "k9m2p4q7r1s8t3v6" }

A link under legal hold returns { "error": "legal_hold", "message": "This link is under a legal hold and cannot be deleted." }. A slug you don’t own (or already deleted) returns { "error": "Link not found or already deleted" }.

scan_html

Run the regex PII & secret scanner against HTML without publishing — useful for an agent to check content before calling create_link.

FieldTypeDescription
htmlrequiredstringHTML content to scan.

result

{
  "hasSensitiveData": true,
  "findings": [
    { "type": "api_key", "label": "API Key", "count": 1 },
    { "type": "email",   "label": "Email Address", "count": 3 }
  ]
}

This is the zero-token regex scanner only — it reports findings but never blocks. The publish gates on create_link / update_link are what enforce your policy.

Workflow recipes

Publish straight from a chat

Ask the agent to generate an HTML artifact (a proposal, dashboard, or one-pager), then “vault this.” The agent calls create_link with the HTML and a short title and returns the URL. Because the publish gates run server-side, anything that trips your PII policy or Safe Browsing is caught before the link exists.

Living dashboards

Publish a status page once with create_link, share the URL, then have the agent refresh it on a schedule with update_link. The slug never changes, so recipients always see current data at the same link — and get_analyticstells you who’s actually opening it.

Agent pipelines with notifications

When an automated pipeline creates links via MCP, subscribe your stack to the matching webhook events link.created to log the new URL, link.viewed to react to engagement. The agent publishes; your systems are notified out-of-band without polling.

Limits & errors

Rate limit

Requests are rate-limited per API key to 100 requests per minute (fixed one-minute window). Over the limit returns 429 with a Retry-After header:

429 Too Many Requests

{ "error": "rate_limited", "message": "Too many requests. Please slow down and retry after the window resets." }

The exact ceiling may be tuned over time; clients should honor Retry-After rather than hard-code the value.

Error catalog

Tool-level errors are returned as a JSON object in the content envelope (transport status 200); auth and rate-limit errors are returned at the HTTP layer with the status shown.

FieldTypeDescription
pii_detectedtool resultPII policy is set to block and the content matched. Shape: { "error": "pii_detected", "findings": [...] }. From create_link / update_link.
unsafe_contenttool resultContent links to a known-malicious site (Safe Browsing). Shape: { "error": "unsafe_content", "message": "...", "threatTypes": [...] }.
unknown_domaintool resultThe domain passed to create_link isn’t an active domain on your account.
legal_holdtool resultFrom delete_link on a held link. Shape: { "error": "legal_hold", "message": "..." }.
rate_limited429Per-key rate limit exceeded; see above.
missing_key / invalid_key401No bearer header / unrecognized or revoked key.
plan_required403Valid key but Free plan.

Subscribing to events instead of polling? See the Webhooks reference.