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:
| Attribute | Values | What it does |
|---|---|---|
| data-position | bottom-right (default) · bottom-left | Which corner the button sits in. |
| data-label | any text | Button text. Default: “Request a change”. |
| data-show | everyone (default) · link · manual | Who sees the button — see below. |
| data-require-identity | true · false | Ask for name + email before reporting. Defaults to true in link mode, false otherwise. |
| data-api | URL | Point 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:
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;
}| Variable | Default | Affects |
|---|---|---|
| --su-launcher-bg | #0F766E | Button background. |
| --su-launcher-bg-hover | darker bg | Button background on hover. |
| --su-launcher-color | #FFFFFF | Button text + pencil icon color. |
| --su-launcher-radius | 999px (pill) | Corner shape — 0 for square, 6px for soft corners. |
| --su-launcher-font-size | 16px | Button label size (keep ≥14px for tap targets). |
| --su-font | system stack | Font family for the button label. |
| --su-accent | #0F766E | Primary buttons inside the open panel (Send, Continue). |
| --su-accent-contrast | #FFFFFF | Text 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.)
- Works on any page— the current tab's domain is auto-matched to the right site in your workspace.
- Pixel-perfect screenshots (browser capture, not a DOM reconstruction) plus the same console/click-trail context as the widget.
- Cross-page recording: start recording, click through the broken flow across pages, stop & report — the trail rides along.
- Enterprise-friendly: IT can force-install it fleet-wide via managed Chrome policy — company-wide reporting on internal tools without touching any page's source.
Setup (2 minutes):
- Install the extension (Chrome Web Store listing coming — until then: load the built extension from
packages/extension/distvia chrome://extensions → Developer mode → Load unpacked). - Create an API key in Settings → API keys (scopes: read + write).
- Open the extension's Options page, paste the key, hit “Save & test connection”.
- 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
| Mode | Header | Used by | Scope |
|---|---|---|---|
| Session cookie | (cookie) | dashboard | user's memberships |
| API key | Authorization: Bearer suk_… | integrations, customers | workspace, scoped read/write/admin |
| Widget key | X-Widget-Key: swk_… + Origin | widget/SDK | single site, ingest-only — never grants reads |
| Magic token | ?t=… (signed, expiring) | reporter status page | single reporter's requests |
| OAuth 2.1 | Authorization: Bearer | MCP server | per-grant workspace/site (planned) |
Endpoints
| GET/POST | /v1/sites | Site CRUD; POST returns the widget key once |
| GET/PATCH/DELETE | /v1/sites/:id | Read, update, delete a site |
| POST | /v1/sites/:id/rotate-widget-key | New widget key, returned once |
| GET | /v1/sites/:id/requests | List requests — filters: status, type, assignee, tag, q, since |
| POST | /v1/sites/:id/requests | Create a request via API |
| GET | /v1/requests/:id | Full request incl. spec + capture bundle (signed URLs) |
| PATCH | /v1/requests/:id | Update status, assignee, priority, tags, due_at |
| GET/POST | /v1/requests/:id/comments | Comments — visibility: "client" | "internal" |
| POST | /v1/requests/:id/merge | Merge duplicates — unions watchers |
| GET/POST | /v1/requests/:id/links | External links (GitHub PR, Jira issue, …) |
| GET | /v1/requests/:id/bundle/:artifact | screenshot | replay | video | console | network | click_trail |
| GET/POST | /v1/workspaces/:id/api-keys | API keys — create returns the key once |
| GET/POST/DELETE | /v1/workspaces/:id/integrations | Provider config (GitHub, Slack, …) |
| GET/POST | /v1/sites/:id/webhooks | Outbound webhooks |
| GET | /v1/status/:token | Reporter status (magic-token auth) |
| POST | /v1/status/:token/comments | Reporter 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
- API per key: 300/min
- Ingest per widget key: 60/min (burst 20)
- Interview: 20 turns per session
- Auth endpoints: 10/min per IP
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_requests | List requests (by site_id, status, free-text q) — one summary line each |
| get_request | Full context: AI spec, reporter text, interview Q&A, env, signed artifact URLs |
| get_screenshot | Signed URL + dimensions for the annotated screenshot |
| get_console_logs | Console output, errors/warnings first, ±30s around the last error |
| get_network_log | Network table, failed requests first |
| get_click_trail | The reporter's journey as numbered steps across pages |
| get_replay_summary | Replay duration / pages / error markers + signed download URL |
| add_comment | Comment on a request (defaults to internal visibility) |
| update_status | Move a request through the workflow (new → … → live) |
| create_request | Create a follow-up request on a site |
| link_pr | Attach 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.