Docs

1. Install the widget

Create a site in the dashboard with the exact origin your pages are served from. You get a publishable site key (pk_…) and this snippet; paste it before </body> on every page you want reviewed:

<script src="{API_BASE}/widget/v1/widget.js" data-site-key="pk_…" defer></script>

The site page in the dashboard always shows the snippet with your real key filled in.

Attributes

AttributeRequiredMeaning
data-site-keyyes The site's publishable key. It is public by design: the API only accepts it from the site's origin.
data-api-baseno Where the widget talks to. Defaults to the origin the script was loaded from, which is the API, so you normally leave it out.
data-page-slugno The identifier threads are grouped under. Default: index for a path ending in /, otherwise the last path segment without .html. Set it when several URLs render the same content, or when a page's path changes.
data-target-scopeno CSS selector for the containers whose headings, paragraphs and list items become commentable. Default: article, main.
data-versionno A deploy identifier (a commit SHA works well). It is stored on every comment and on a thread when it is closed, so you can tell which version a comment was about.

The origin must match

The widget's first call is GET /widget/config with the page's Origin. The API answers only when that origin is the site's origin or one of its extra allowed origins; any other page gets 403 origin does not match site and the widget stays silent. An origin is scheme, host and port with no path: https://www.example.com and https://example.com are two different sites. Add a staging host or a local dev server (http://localhost:3000) under Allowed origins on the site page.

Reviewers do not have accounts. They join a site through a review link you mint and share.

Minting one

  1. Open the site page in the dashboard and go to Review links.
  2. Give it a label that names the cohort ("design partners", "internal", "client round 2") and, optionally, an expiry.
  3. The share URL and the raw code (rv_…) are shown once. Copy the share URL and send it on.

Use Review link for me to mint a link labelled owner for yourself: owners comment through the same path reviewers use, so there is exactly one identity path in the widget. An MCP client can also mint links with create_review_link (section 4).

Sharing it

The share URL is the site's origin with the code as a query parameter:

https://your-site/?review=rv_…

The code works on any page of the site, as ?review=… or #review=…, so you can send reviewers straight to the page you want looked at.

What reviewers see

Revoking

A locked-out reviewer sees a quiet "ask the site owner for a review link" note in the widget; a fresh link from you lets them join again.

3. Turning the widget off

Every site has an on/off switch at the top of its page in the dashboard. When it is off, GET /widget/config reports enabled: false, the widget attaches nothing and exits. Pages honour the change on their next load, with no deploy on your side.

4. MCP setup

The API hosts an MCP server at {API_BASE}/mcp (streamable HTTP). It authenticates with an API token from Tokens & billing, sent as Authorization: Bearer ak_…. The token acts as you: every site in your account, and the same authorization the dashboard has.

Claude Code

claude mcp add --transport http comments {API_BASE}/mcp --header "Authorization: Bearer ak_…"

Then, in a session: /mcp lists the server, and /comments:triage runs the triage prompt for a site.

Cursor

Add to .cursor/mcp.json in the project (or the global one in your home directory):

{
  "mcpServers": {
    "comments": {
      "url": "{API_BASE}/mcp",
      "headers": {
        "Authorization": "Bearer ak_…"
      }
    }
  }
}

Claude Desktop

The server needs a client that speaks HTTP MCP and can send a bearer header. Claude Desktop's connector settings take a URL but do not let you set an Authorization header, so connect it through a local stdio-to-HTTP bridge such as the mcp-remote package (a third-party tool: install it yourself and check it before trusting it with a token). In claude_desktop_config.json:

{
  "mcpServers": {
    "comments": {
      "command": "npx",
      "args": [
        "-y", "mcp-remote",
        "{API_BASE}/mcp",
        "--header", "Authorization: Bearer ak_…"
      ]
    }
  }
}

If you would rather not run a bridge, use Claude Code or Cursor, which connect directly.

Tools

ToolWhat it does
list_sites()Your sites with their enabled state and open-thread counts. Start here to get a site_id.
list_threads(site_id, page?, include_closed?)Threads on a site, optionally for one page slug; open threads only unless include_closed is true. Each carries its comments and the text it was anchored to.
get_thread(site_id, thread_id)One thread in full.
reply(site_id, thread_id, body)Add a comment to a thread as the owner.
set_status(site_id, thread_id, status, site_version?)Move a thread to new, in_review or closed; pass the deployed commit as site_version when closing.
create_review_link(site_id, label)Mint a share link for reviewers. The share URL is returned once.

The triage prompt

triage(site_id) asks the agent to list the open threads of a site and, for each one, find the element in the site's source using page_slug, anchor and context_snapshot (the exact text the reviewer saw), make the fix, reply with a one-line summary, and set_status to closed with the deployed commit as site_version. Threads it cannot resolve are left in_review with a reply explaining why.

5. API overview

Base URL {API_BASE}. Owner routes accept the dashboard's session cookie or Authorization: Bearer ak_…. Reviewer routes take Authorization: Bearer rt_… plus X-Site-Key and a matching Origin. Thread routes accept either; an owner names the site with the X-Site-Id header. Raw keys, codes and tokens are returned once and never again. The interactive OpenAPI reference is at the API's /docs.

MethodPathPrincipalPurpose
POST/auth/magic-linknone, rate limitedsend the sign-in link; always 202
POST/auth/magic-link/verifynoneconsume the link, create a session, set the cookie
POST/auth/logoutownerrevoke the session
GET/accountowneruser, account, plan
GET / POST/account/tokensownerlist, mint (ak_ raw once)
DELETE/account/tokens/{id}ownerrevoke
GET / POST/sitesownerlist, create (pk_ raw once and the snippet)
GET / PATCH / DELETE/sites/{id}ownerdetail, {name, enabled, allowed_origins}, delete
POST/sites/{id}/keys/rotateownernew pk_, old revoked
GET / POST/sites/{id}/linksownerlist, mint (rv_ raw once and the share URL)
DELETE/sites/{id}/links/{lid}ownerrevoke the link and its reviewers' access
GET/sites/{id}/reviewersownerwho joined through which link
DELETE/sites/{id}/reviewers/{rid}ownerrevoke one reviewer
GET/widget/config?site_key=Origin + key{site_id, name, enabled}; the widget's first call
POST/widget/joinOrigin + X-Site-Key, rate limited{code, display_name}{token, reviewer_id, display_name, site_id}
GET/api/threads?page=reviewer or ownerthreads with comments for a page slug
POST/api/threadsreviewer or ownernew thread with its first comment
PATCH/api/threads/{id}reviewer or ownerre-anchor, status, resolved
POST/api/threads/{id}/commentsreviewer or ownerreply
PATCH / DELETE/api/threads/{id}/comments/{cid}same authoredit, soft delete
GET/api/unread?pages=reviewer or owneropen-thread counts per page slug (comma separated)
POST/billing/checkoutownerStripe Checkout URL; 503 when billing is not configured
POST/billing/portalownerStripe customer portal URL; 503 when billing is not configured
POST/billing/webhookStripe signatureplan state
GET/healthz, /readyznoneliveness, database ping
GET/widget/v1/widget.js, /widget/v1/widget.cssnonethe versioned widget, cached immutably
*/mcpak_ bearerthe MCP endpoint (streamable HTTP)

Limits: 4,000 characters per comment, 500 threads per page, 200 comments per thread, 10 extra origins per site. Rate limits answer 429 with a Retry-After header.