Install
- Sign in and create a project. You get a project key.
- Paste the snippet before the closing
</body>tag on every page. - Press Send a test report on the project page, or open your site and tap the ladybug.
<script src="https://bugshot-cloud.thatfellowinc.workers.dev/bugshot.js"
data-endpoint="https://bugshot-cloud.thatfellowinc.workers.dev/i/YOUR_PROJECT_KEY"
defer></script>
Frameworks
The script tag works everywhere. If you would rather start it from your own code:
React or Next.js
// app/layout.tsx (Next.js App Router)
import Script from 'next/script'
export default function RootLayout({ children }) {
return (
<html lang="en">
<body>
{children}
<Script src="https://bugshot-cloud.thatfellowinc.workers.dev/bugshot.js"
data-endpoint="https://bugshot-cloud.thatfellowinc.workers.dev/i/YOUR_PROJECT_KEY"
strategy="afterInteractive" />
</body>
</html>
)
}
Vue or Nuxt
// nuxt.config.ts
export default defineNuxtConfig({
app: {
head: {
script: [{
src: 'https://bugshot-cloud.thatfellowinc.workers.dev/bugshot.js',
'data-endpoint': 'https://bugshot-cloud.thatfellowinc.workers.dev/i/YOUR_PROJECT_KEY',
defer: true,
}],
},
},
})
Any bundler
import { init, identify } from 'bugshot'
init({ endpoint: 'https://bugshot-cloud.thatfellowinc.workers.dev/i/YOUR_PROJECT_KEY' })
identify({ id: user.id, plan: user.plan })
Options
Set them as attributes on the script tag, or as camelCase keys passed to init().
| Attribute | Default | What it does |
|---|---|---|
data-endpoint | required | Where reports are sent. Your project page shows the exact value. |
data-project | default | A label sent with each report. |
data-launcher | bottom-right | bottom-right, bottom-left, top-right, top-left, or none to use your own button. |
data-label | Report a bug | Text on the floating button. |
data-accent | ladybug red | Your brand colour for the send button and annotations, as #rgb or #rrggbb. |
data-theme | auto | light, dark or auto (follows the visitor’s system). |
data-hotkey | ctrl+shift+b | Keyboard shortcut. Set false to turn it off. |
data-title | Report a problem | Heading of the dialog. |
data-placeholder | Placeholder in the description box. | |
data-require-email | false | Ask the reporter for an email address. |
data-mask-inputs | true | Blur every field’s contents in the screenshot. |
data-capture-console | true | Record console errors and warnings. |
data-capture-network | true | Record failed requests. |
data-capture-breadcrumbs | true | Record clicks and page changes. |
JavaScript API
BugShot.open() // open the reporter yourself
BugShot.close()
BugShot.identify({ id: 'usr_88', plan: 'pro' }) // who is reporting
BugShot.setContext({ flags: ['new-checkout'] }) // app state for every report
BugShot.destroy() // remove it and restore patched globals
Use data-launcher="none" and call BugShot.open() from your own "Report a problem" menu item.
Privacy markup
<div data-bugshot-mask>Blurred in the screenshot</div> <div data-bugshot-ignore>Left out of the screenshot entirely</div>
The classes bugshot-mask and bugshot-ignore work the same way.
Destinations
Every report is saved to your inbox. On a project's page you can also forward them:
- Slack: an incoming webhook URL. Each new report posts with the screenshot and the technical details.
- GitHub Issues: a repository and a fine-grained token with Issues read and write. A new bug opens one GitHub issue; if a resolved bug comes back, BugShot comments on it.
- Linear: a personal API key and a team id. A new bug opens one Linear issue.
- Webhook: any URL that accepts JSON, optionally signed.
Secrets are encrypted before they are stored and are never shown again.
Webhook payload
POST https://your-app.example/bugshot
content-type: application/json
x-bugshot-signature: sha256=<hex HMAC of the raw body, using your signing secret>
{
"project": "Pantry",
"issue": { "url": "...", "title": "...", "reportCount": 3 },
"screenshotUrl": "https://.../s/...",
"report": { "message": "...", "page": {...}, "env": {...},
"console": [...], "network": [...], "breadcrumbs": [...] }
}
Verify the signature by computing an HMAC SHA-256 of the raw request body with your secret and comparing it to the header in constant time.
Agents and API
Let an AI agent work through your inbox. It reads a bug the way you do: the message, the page, the console errors, the failed requests, the click trail, the device and the screenshot. Then it can leave a note and mark the bug resolved. Agents connect over MCP, or over a plain REST API with a key.
1. Add BugShot to your agent
The MCP server uses Streamable HTTP at https://bugshot-cloud.thatfellowinc.workers.dev/mcp. Add that address and nothing else: there is no key to paste.
Run this in your terminal, then type /mcp in Claude Code, pick bugshot and sign in.
claude mcp add --transport http bugshot https://bugshot-cloud.thatfellowinc.workers.dev/mcp
Add this to ~/.cursor/mcp.json. Cursor asks you to sign in the first time it connects.
{
"mcpServers": {
"bugshot": { "url": "https://bugshot-cloud.thatfellowinc.workers.dev/mcp" }
}
}
Add this to .vscode/mcp.json, then start the server and allow the sign in.
{
"servers": {
"bugshot": { "type": "http", "url": "https://bugshot-cloud.thatfellowinc.workers.dev/mcp" }
}
}
In Claude, open Settings, then Connectors, then Add custom connector, and sign in when asked.
Name: BugShot URL: https://bugshot-cloud.thatfellowinc.workers.dev/mcp
Turn on developer mode in ChatGPT settings, add a custom MCP server with this URL, and choose OAuth.
Name: BugShot URL: https://bugshot-cloud.thatfellowinc.workers.dev/mcp Authentication: OAuth
Run both lines in your terminal. The second one opens the sign in.
codex mcp add bugshot --url https://bugshot-cloud.thatfellowinc.workers.dev/mcp codex mcp login bugshot
Add this to ~/.gemini/settings.json, then type /mcp auth bugshot in Gemini CLI.
{
"mcpServers": {
"bugshot": { "httpUrl": "https://bugshot-cloud.thatfellowinc.workers.dev/mcp" }
}
}
Add this to opencode.json. OpenCode signs in on first use, or run opencode mcp auth bugshot.
{
"mcp": {
"bugshot": { "type": "remote", "url": "https://bugshot-cloud.thatfellowinc.workers.dev/mcp", "enabled": true }
}
}
Add this to ~/.codeium/windsurf/mcp_config.json and sign in when Windsurf asks.
{
"mcpServers": {
"bugshot": { "serverUrl": "https://bugshot-cloud.thatfellowinc.workers.dev/mcp" }
}
}
Add this to ~/.hermes/config.yaml, then run hermes mcp login bugshot.
mcp_servers:
bugshot:
url: "https://bugshot-cloud.thatfellowinc.workers.dev/mcp"
auth: oauth
Run both lines. The second one opens the sign in.
openclaw mcp add bugshot --url https://bugshot-cloud.thatfellowinc.workers.dev/mcp --transport streamable-http openclaw mcp login bugshot
In Grok, open Settings, then Connectors, then New Connector, choose Custom and sign in when asked.
URL: https://bugshot-cloud.thatfellowinc.workers.dev/mcp
Using something else? Any MCP client works with https://bugshot-cloud.thatfellowinc.workers.dev/mcp. Clients that cannot sign in use an API key from the Agents page.
2. Sign in when it asks
The first time the agent connects, your browser opens BugShot. A workspace owner picks the access and allows it:
- Read only: the agent can list and read issues.
- Read and write: the agent can also change an issue's status and add notes.
The connection shows under Connected apps on the Agents page, where you can disconnect it at any time. Sign in uses OAuth 2.1 with PKCE and dynamic client registration, which is what MCP clients expect.
Scripts, CI jobs and agents that cannot sign in use an API key instead. Owners create keys on the same page. A key is bsk_ followed by 40 hex characters, it is shown once, and it goes in the Authorization header as a Bearer token. Treat it like a password and keep it out of your repo.
3. Give it a first job
Paste this into your agent, from inside the repo of the app that sent the report:
Use BugShot to fix the newest open bug in this repo. Read the issue, find the cause, make the fix, then add a note with what you changed and mark it resolved.
Notes an agent writes show up on the issue page in your dashboard, so you can see what it found and what it changed.
What the agent can do
| Tool | What it does | Needs |
|---|---|---|
list_projects | List the projects in your account. | Read only |
list_issues | List issues. Arguments: status (open, resolved, ignored or all), project_id and limit. | Read only |
get_issue | Read one issue as a Markdown brief: the message, the page, console errors, failed requests, the click trail and the device, plus the screenshot as an image (see the note below). Arguments: issue_id and include_screenshot. | Read only |
update_issue_status | Set an issue to open, resolved or ignored, with an optional note. Arguments: issue_id, status and note. | Read and write |
add_issue_note | Add a note to an issue. Arguments: issue_id and body. | Read and write |
The server also offers a prompt, fix_bug. It takes an optional issue_id and defaults to the newest open issue.
The screenshot comes back as an image unless the reporter uploaded it themselves. Screenshots people upload are blurred in your dashboard until you open them, so they are not sent to the agent as an image. The rest of the brief still comes through.
REST API
Send an API key, or a token from signing in, as a Bearer token. Every response is JSON.
# list your open issues
curl -H "Authorization: Bearer bsk_your_key" \
"https://bugshot-cloud.thatfellowinc.workers.dev/api/v1/issues?status=open&limit=20"
# mark one resolved (needs a read and write key)
curl -X POST -H "Authorization: Bearer bsk_your_key" \
-H "content-type: application/json" \
-d '{ "status": "resolved", "note": "Fixed in #482" }' \
https://bugshot-cloud.thatfellowinc.workers.dev/api/v1/issues/ISSUE_ID/status
| Endpoint | What it does | Needs |
|---|---|---|
GET /api/v1/projects | List your projects. | Read only |
GET /api/v1/issues?status=open&project=prj_...&limit=20 | List issues. Filter by status, project and limit. | Read only |
GET /api/v1/issues/{id} | One issue with its latest reports, the Markdown brief and a screenshot URL. The screenshot comes with a sensitive flag. | Read only |
POST /api/v1/issues/{id}/status | Body: { "status": "resolved", "note": "Fixed in #482" } | Read and write |
POST /api/v1/issues/{id}/notes | Body: { "body": "Investigated: ..." } | Read and write |
Errors are JSON like { "error": "..." }, with one of these status codes:
401: the key or token is missing, wrong or expired.403: the key or connection is read only.404: the issue or project is not in your account.429: you are rate limited. Each key or connection gets 120 requests a minute.