Documentation

Install the widget in one paste, then use the API and MCP server — available on every plan, including Free.

Install the widget

One script tag. Paste it just before the closing </body> tag on every page where people should be able to report changes:

<script async src="https://cdn.siteupdate.io/widget.js" integrity="sha384-…" crossorigin="anonymous" data-site-key="swk_YOUR_WIDGET_KEY"></script>

Your widget key (swk_…) lives in Settings → Sites— it's shown once when the site is created, and that screen gives you this exact snippet with the key and the current integrity hash filled in. You can rotate the key any time. The script is async and error-isolated: it never blocks or breaks your site.

Options

Everything is configured with data-* attributes on the same script tag:

AttributeValuesWhat it does
data-positionbottom-right (default) · bottom-leftWhich corner the button sits in.
data-labelany textButton text. Default: “Request a change”.
data-showeveryone (default) · link · manualWho sees the button — see below.
data-require-identitytrue · falseAsk for name + email before reporting. Defaults to true in link mode, false otherwise.
data-apiURLPoint at a self-hosted or dev API instead of api.siteupdate.io.

WordPress

No code needed: install the SiteUpdate plugin from the plugin directory, then go to Settings → SiteUpdate and paste your widget key. The plugin injects the snippet on every page and stays current as we update the widget.

From your own code (SDK)

The snippet exposes a tiny SiteUpdate global (safe to call before the widget finishes loading):

<script>
  // Open the widget from your own button (pairs with data-show="manual")
  document.querySelector('#report-issue').addEventListener('click', () => {
    SiteUpdate.open();
  });

  // Your site knows who's logged in? Tell the widget once —
  // it pre-fills who to notify, so reporters never type their email.
  SiteUpdate.identify({ email: 'rita@client.com', name: 'Rita' });

  // Runs after a request is sent (analytics, a thank-you toast, …)
  SiteUpdate.on('submit', (request) => {
    /* your code */
  });
</script>

What the button looks like

The launcher sits in the corner you choose (default bottom-right). These previews are the real widget styles rendered live — the default, and two examples restyled with the CSS variables below:

Default
Squared, brand red, serif
Navy, compact label

Want PNGs for your own docs or a client deck? pnpm shots captures real screenshots (button closed, widget open, customized variant) from the demo site into assets/marketing/.

Customize the button (CSS)

The widget lives in a shadow DOM, so your site's CSS can't leak in and break it — but CSS custom properties pass through on purpose. Set them on #siteupdate-widgetto match the button to your site's brand:

/* Anywhere in your site's CSS — the variables pass through the shadow DOM */
#siteupdate-widget {
  --su-launcher-bg: #7c2d12;        /* your brand color */
  --su-launcher-bg-hover: #631f0b;
  --su-launcher-color: #fff7ed;     /* text + icon on the button */
  --su-launcher-radius: 6px;        /* squared instead of the default pill */
  --su-launcher-font-size: 15px;
  --su-font: Georgia, 'Times New Roman', serif;

  /* Optional: primary buttons inside the open widget panel */
  --su-accent: #7c2d12;
  --su-accent-contrast: #fff7ed;
}
VariableDefaultAffects
--su-launcher-bg#0F766EButton background.
--su-launcher-bg-hoverdarker bgButton background on hover.
--su-launcher-color#FFFFFFButton text + pencil icon color.
--su-launcher-radius999px (pill)Corner shape — 0 for square, 6px for soft corners.
--su-launcher-font-size16pxButton label size (keep ≥14px for tap targets).
--su-fontsystem stackFont family for the button label.
--su-accent#0F766EPrimary buttons inside the open panel (Send, Continue).
--su-accent-contrast#FFFFFFText on those primary buttons.

Two rules. Keep it readable:the button must pass WCAG AA contrast (4.5:1) against your page — if you set --su-launcher-bg, check --su-launcher-color against it. Looks only:the variables restyle color, shape and type. The three-step flow, its wording, and plan-level branding aren't customizable — that's what keeps the reporter experience working for everyone.

Report from any site — the browser extension

The Chrome extension is for your team, not your clients: report on any site with no snippet installed— staging environments, a prospect's site during pre-sales, a locked-down CMS where adding a script tag takes a change request. (Clients still get the widget or a share link — they never install anything.)

Setup (2 minutes):

  1. Install the extension (Chrome Web Store listing coming — until then: load the built extension from packages/extension/dist via chrome://extensions → Developer mode → Load unpacked).
  2. Create an API key in Settings → API keys (scopes: read + write).
  3. Open the extension's Options page, paste the key, hit “Save & test connection”.
  4. Visit any of your sites and click the SiteUpdate icon → Report on this page.

Current limitation: extension reports include the page, selector, description and a captured context summary; full screenshot/replay attachments for extension reports are a small planned API addition. Firefox support is on the roadmap.

API reference

One base URL, one response envelope, boring on purpose.

Conventions

Base URL https://api.siteupdate.io/v1 (local dev: http://localhost:4000/v1). Every response uses the same envelope; meta appears on list endpoints:

{ "success": true, "data": { }, "error": null, "meta": { "total": 0, "page": 1, "limit": 50 } }

On failure, error is { "code", "message" } and data is null. IDs are prefixed ULIDs (ws_…, site_…, req_…); requests also carry a human number (#142). Pagination: ?page=&limit= (max 100). Timestamps: ISO 8601 UTC.

Auth modes

ModeHeaderUsed byScope
Session cookie(cookie)dashboarduser's memberships
API keyAuthorization: Bearer suk_…integrations, customersworkspace, scoped read/write/admin
Widget keyX-Widget-Key: swk_… + Originwidget/SDKsingle site, ingest-only — never grants reads
Magic token?t=… (signed, expiring)reporter status pagesingle reporter's requests
OAuth 2.1Authorization: BearerMCP serverper-grant workspace/site (planned)

Endpoints

GET/POST/v1/sitesSite CRUD; POST returns the widget key once
GET/PATCH/DELETE/v1/sites/:idRead, update, delete a site
POST/v1/sites/:id/rotate-widget-keyNew widget key, returned once
GET/v1/sites/:id/requestsList requests — filters: status, type, assignee, tag, q, since
POST/v1/sites/:id/requestsCreate a request via API
GET/v1/requests/:idFull request incl. spec + capture bundle (signed URLs)
PATCH/v1/requests/:idUpdate status, assignee, priority, tags, due_at
GET/POST/v1/requests/:id/commentsComments — visibility: "client" | "internal"
POST/v1/requests/:id/mergeMerge duplicates — unions watchers
GET/POST/v1/requests/:id/linksExternal links (GitHub PR, Jira issue, …)
GET/v1/requests/:id/bundle/:artifactscreenshot | replay | video | console | network | click_trail
GET/POST/v1/workspaces/:id/api-keysAPI keys — create returns the key once
GET/POST/DELETE/v1/workspaces/:id/integrationsProvider config (GitHub, Slack, …)
GET/POST/v1/sites/:id/webhooksOutbound webhooks
GET/v1/status/:tokenReporter status (magic-token auth)
POST/v1/status/:token/commentsReporter reply / added info

Copy-paste examples

List open requests for a site:

curl -s "https://api.siteupdate.io/v1/sites/site_01ABC/requests?status=new&limit=20" \
  -H "Authorization: Bearer suk_YOUR_KEY"

Pull one request with its full spec and capture bundle:

curl -s "https://api.siteupdate.io/v1/requests/req_01XYZ" \
  -H "Authorization: Bearer suk_YOUR_KEY"

Move it to In Review and leave an internal note:

curl -s -X PATCH "https://api.siteupdate.io/v1/requests/req_01XYZ" \
  -H "Authorization: Bearer suk_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"status": "in_review"}'

curl -s -X POST "https://api.siteupdate.io/v1/requests/req_01XYZ/comments" \
  -H "Authorization: Bearer suk_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"body": "Fixed in PR #87", "visibility": "internal"}'

Rate limits

429 responses include Retry-After.

MCP quickstart — give your coding agent the whole bundle

The MCP server gives Claude Code, Cursor, and friends direct access to requests: full dev-ready specs, screenshots, console/network logs, click trails, replays — and lets them comment, update status, and link PRs back. Free on every plan.

1. Create an API key

Dashboard → Settings → API keys → Create key (scope write if the agent should update requests). Keys look like suk_… and are shown once.

2. Add the server — Claude Code, one command:

claude mcp add --transport http siteupdate https://mcp.siteupdate.io/mcp \
  --header "Authorization: Bearer suk_YOUR_KEY_HERE"

Or paste into your MCP config (.mcp.json, Cursor mcp.json, …):

{
  "mcpServers": {
    "siteupdate": {
      "type": "http",
      "url": "https://mcp.siteupdate.io/mcp",
      "headers": { "Authorization": "Bearer suk_YOUR_KEY_HERE" }
    }
  }
}

3. Use it

Try: “List open SiteUpdate requests and fix the highest-priority bug. When you're done, link the PR.”

list_requestsList requests (by site_id, status, free-text q) — one summary line each
get_requestFull context: AI spec, reporter text, interview Q&A, env, signed artifact URLs
get_screenshotSigned URL + dimensions for the annotated screenshot
get_console_logsConsole output, errors/warnings first, ±30s around the last error
get_network_logNetwork table, failed requests first
get_click_trailThe reporter's journey as numbered steps across pages
get_replay_summaryReplay duration / pages / error markers + signed download URL
add_commentComment on a request (defaults to internal visibility)
update_statusMove a request through the workflow (new → … → live)
create_requestCreate a follow-up request on a site
link_prAttach a PR: link + internal comment + status → in_review

MCP tools return summarized, agent-shaped output plus signed artifact URLs — never raw megabyte dumps inline. Local dev: http://localhost:4010/mcp.