BugTape docs
SDK Reference
Every browser init option and method.
The BugTape JavaScript/TypeScript SDK records browser activity and lets users submit bug reports with full context.
Installation
Use the hosted browser module, or npm install @bugtape/sdk with a bundler. Apply your app's consent policy first.
<script type="module">
import { init } from 'https://app.bugtape.ai/bugtape.mjs';
init({ apiKey: 'bt_live_your_key_here' });
</script>With a bundler, install the same SDK from npm instead:
npm install @bugtape/sdkimport BugTape from '@bugtape/sdk';
BugTape.init({ apiKey: 'bt_live_your_key_here' });Supported runtimes
| Runtime | Supported | Notes |
|---|---|---|
| Browser (modern) | Yes | Chrome, Firefox, Safari, Edge — ES2020+ |
| Node.js | No | SDK uses browser APIs (DOM, fetch, rrweb) |
| Next.js (browser) | Yes | Use the hosted module example; no SSR initialization |
| Next.js (server) | No | Use the HTTP API for server-side ingestion |
| React Native | No | Browser-only |
Browser module exports
import { init, identify, token } from 'https://app.bugtape.ai/bugtape.mjs';This import belongs in a browser module. For Next.js use the hosted module example, or install @bugtape/sdk with your bundler and initialize it in a client component.
Initialization
const instance = init({ apiKey: 'bt_live_your_key_here' });BugTapeConfig
| Property | Type | Required | Default | Description |
|---|---|---|---|---|
apiKey | string | Yes | — | Project API key (bt_live_* or bt_test_*) |
endpoint | string | No | https://app.bugtape.ai/v1/ingest | Ingest endpoint URL |
registeredIdentity | { applicationId: string; buildId?: string; installationId?: string } | No | omitted | Management-enrolled UUIDs. applicationId is required inside this object; optional build and installation must belong to that application and the capture key's project. Copy the populated snippet from Setup. |
release | string | No | inferred | Deploy/release id on every occurrence. Prefer init({ release }). If omitted: BUGTAPE_RELEASE, then common CI SHA envs (GITHUB_SHA, VERCEL_GIT_COMMIT_SHA, …). |
summarize | boolean | No | true | Set to false to disable the AI summary entirely |
recordingWindow | number | No | 10 | Buffer duration in minutes |
maxEvents | number | No | 50000 | Maximum events in ring buffer |
autoReport | boolean | { enabled?, maxPerMinute?, interactionWindowMs?, reportUncaughtErrors?, reportHungRequests?, hangTimeoutMs?, reportDeadRoutes?, emptyExpectRules? } | No | on | Sends a report with no user action for uncaught errors and unhandled rejections, requests that fail within interactionWindowMs (8000) of a click, requests that hang longer than hangTimeoutMs (15000, at least 1000), page loads that return 400 or more, and in-app not-found screens. At most maxPerMinute (6) automatic reports a minute. false turns all of it off; empty 2xx responses are reported only for emptyExpectRules you set. See Capture semantics. |
showButton | boolean | No | true | Show the floating bug-report button. false keeps automatic capture and open() (open the form from your own link) but shows no button. |
badge | boolean | { siteName?: string; ref?: string } | No | false | Show the button as BugTape's orange b badge. Hover or focus shows Report a bug, What is BugTape? and Go to BugTape. See Corner badge. |
majorBugAlert | boolean | MajorBugAlertOptions | No | true | Signal a newly accepted high or critical automatic issue on the report button. See Report button and signal. |
position | string | No | 'bottom-right' | Widget position: bottom-right, bottom-left, top-right, top-left |
theme | string | No | 'auto' | Widget theme: dark, light, auto |
tokenize | { publicKey: string } | No | omitted | Seal client details: text in [data-bugtape-token] elements and values passed to token() leave as placeholders; the values travel sealed to this public key. See Client-detail tokens. |
privacy | 'standard' | 'strict' | No | 'standard' | 'strict' masks every character of page text in the replay as solid blocks ("Anna Smith" → "▇▇▇▇ ▇▇▇▇▇") and text-bearing attributes, strips query strings from URLs, drops request/response bodies and console objects — all in the browser before anything is sent. Errors and stacks are still sent after the PII scrub. |
disableDomRecording | boolean | No | false | Disable DOM mutation recording |
disableNetworkRecording | boolean | No | false | Disable network request recording |
disableConsoleRecording | boolean | No | false | Disable console output recording |
rageClickSensitivity | string | No | 'medium' | Rage click detection: very-sensitive, medium, low |
context | object | No | — | Occurrence metadata: reporterEmail, reporterId, sessionId, userId, release, environment, platform, metadata. Prefer top-level release. |
context.platform | string | No | 'web' | Capture platform: web, ios, android, react-native, flutter, server, other. Sent on every ingest; the server infers it from the user agent when a client omits it. On the v1 web/server path, platform is not part of the fingerprint. Native v2 hashes a canonical JSON blob that includes platform. |
pii | Partial<PiiConfig> | No | { enabled: true } | PII scrubbing configuration |
onSubmit | (report: BugReport) => void | Promise<void> | No | — | Custom submit handler (replaces default) |
onSuccess | (response: IngestResponse) => void | No | — | Called after successful submission |
onError | (error: Error) => void | No | — | Called on submission failure |
Do not pass encryptionKey: the SDK rejects it because storage/read/decryption is not supported end to end. Do not put an AI provider key in browser code. onSubmit replaces default delivery; an empty handler discards the submission path. Use onSuccess for acknowledgement callbacks.
Registered application, build and installation
Use Setup's enrolled snippet to associate a real capture with its installation. The SDK takes the IDs inside registeredIdentity; they are not top-level SDK options:
init({
apiKey: captureKey,
registeredIdentity: { applicationId, installationId, buildId },
});The variables above are your enrolled IDs from Setup or authorized deployment configuration. Omit buildId until a build has been registered. Basic capture can omit the entire object, but it cannot confirm a particular installation. A build or installation ID requires its application's ID. Do not invent IDs or label a repaired build with a failed build's ID.
The SDK copies and validates this object on first initialization. Misplaced top-level applicationId, buildId or installationId causes an early error before recording or transport starts. Repeated init calls return the existing instance without applying new configuration. The HTTP ingest payload flattens the validated values to those three top-level fields; that wire format is different from the SDK configuration.
An ingest acknowledgement proves report acceptance in the stated project. Setup's installation receipt separately confirms a real capture from that enrolled installation. Neither receipt attests a binary, physical device or verified repair. Agent work grants and trusted runner verification have separate authority; see Agents.
BugTapeInstance
| Method | Returns | Description |
|---|---|---|
open() | void | Open the bug report widget programmatically |
setMajorBugAlertEnabled(enabled) | void | Enable or disable the button signal; remember the choice in this browser |
isMajorBugAlertEnabled() | boolean | Read the effective signal preference |
isMajorAlertActive() | boolean | Check whether the button is signaling an issue |
clearMajorBugAlert() | void | Clear the current signal without changing the preference |
signalMajorBug(reason?) | void | Request a signal for your own confirmed failure; respects the enabled preference and does not send a report |
getEvents() | BugTapeEvent[] | Get all buffered events within the recording window |
destroy() | void | Tear down the SDK, remove event listeners, remove widget |
buffer | RingBuffer | Direct access to the ring buffer (advanced use) |
Floating report button and serious-issue signal
The floating button opens the report form at any time. A person can describe what they tried to do and send the captured context with their report. Automatic capture works even when nobody opens the form. Apply your app's consent policy and choose its privacy settings before initialization.
With default delivery, the SDK signals only after the server accepts a new automatic issue with high or critical severity. Failed submissions, duplicate reports, known regressions and silenced issues do not trigger a new signal. Browser errors, failed requests and hangs can be classified as high; this is a capture severity, not a claim of lost sales or verified business impact.
The button gives three soft shadow glows, then keeps a steady signal. It does not make a sound. Reduced motion removes the glow animation. Opening the report form clears the signal; otherwise it clears after 90 seconds by default. It never opens the form on its own.
const instance = init({
apiKey: 'bt_live_your_key_here',
showButton: true,
majorBugAlert: {
enabled: true,
severities: ['critical', 'high'],
autoClearMs: 90_000,
},
});Use majorBugAlert: false to set a disabled default, or choose severities: ['critical'] for a narrower signal. autoClearMs: 0 keeps the steady signal until the form opens or you clear it. A preference saved by setMajorBugAlertEnabled() overrides the initialization default. Use instance.setMajorBugAlertEnabled(false) to disable and clear it at runtime.
Use showButton: false to hide the floating button while keeping automatic capture. Call instance.open() from your own report link. A custom onSubmit replaces default delivery and has no ingest acknowledgement, so it does not trigger this automatic signal; signalMajorBug() is available for your own confirmed failure and does not verify or send evidence.
Corner badge
badge: true (or badge: { siteName: 'Acme Shop' }) turns the report button into BugTape's orange b in the position corner. It is off by default, so existing installs keep the bug button.
init({ apiKey: 'bt_live_your_key_here', badge: { siteName: 'Acme Shop' } });- Hover or keyboard focus shows three options: Report a bug (the same report form), What is BugTape? (a short note that names
siteName, or "this site") and Go to BugTape (https://bugtape.ai/?ref=<your hostname>in a new tab; setrefto change the value). - With a mouse, a click on the badge opens the report form. On a touch screen the first tap shows the options.
- Escape closes the form, then the options. With reduced motion the options appear without the stagger.
- The badge does not drag.
showButton: falsehides it;instance.open()still works.
Auto-captured events
Once initialized, the SDK automatically captures these event types:
DOM events (dom)
Recorded via rrweb. Captures full visual replay data including:
- Full DOM snapshots
- Incremental DOM mutations
- Mouse movement and clicks
- Scroll events (throttled to 150ms)
- Input changes (last value only)
- Focus/blur events
All text inputs are masked by default for privacy. With privacy: 'strict' all page text is masked as well (layout, clicks and scrolls are kept).
Network events
| Event type | Data captured |
|---|---|
network | Resource timing: URL, duration, transfer size, initiator type |
network:fetch | Fetch requests: URL, method, status code, duration |
network:fetch:error | Failed fetch: URL, method, error message, duration |
network:xhr | XHR requests: URL, method, status code, duration |
The SDK does not record its own requests: report delivery to the capture endpoint, presence beacons (…/v1/ingest/presence, or your presence.endpoint) and evidence uploads (…/v1/evidence/…). Other requests your app makes to the same host are recorded.
Console events
| Event type | Data |
|---|---|
console:log | { level: "log", args: string[] } |
console:warn | { level: "warn", args: string[] } |
console:error | { level: "error", args: string[] } |
console:info | { level: "info", args: string[] } |
console:debug | { level: "debug", args: string[] } |
Arguments are JSON-serialized with a 2,000-character limit per argument.
Error events
| Event type | Data |
|---|---|
error | { message, filename, lineno, colno, stack } |
error:unhandledrejection | { message, stack } |
Rage click events (rage-click)
{ x: number, y: number, clicks: number, target: string, url: string, innerText: string }Sensitivity presets:
| Preset | Clicks | Window | Radius | Debounce |
|---|---|---|---|---|
very-sensitive | 2+ | 1500ms | 50px | 1s |
medium | 3+ | 1000ms | 30px | 2s |
low | 4+ | 800ms | 20px | 3s |
Event structure
All events share this shape:
interface BugTapeEvent {
type: string; // Event type identifier
timestamp: number; // Unix timestamp in milliseconds
data: unknown; // Event-specific payload
}Ring buffer behavior
- Events are stored in a time-windowed buffer (default: 10 minutes).
- When the event count exceeds
maxEvents, the oldest 20% of events are evicted. - PII scrubbing is applied at capture time (before events enter the buffer).
- Calling
getEvents()automatically prunes expired events.
Bug report structure
When a user submits via the widget, the SDK builds a BugReport:
interface BugReport {
title: string;
description: string;
severity: 'critical' | 'high' | 'medium' | 'low';
screenshot: ScreenshotResult | null;
audio: AudioResult | null;
audioTranscript: string | null;
aiSummary: BugSummary | null;
events: BugTapeEvent[];
environment: {
url: string; // window.location.href
userAgent: string; // navigator.userAgent
viewport: string; // e.g., "1920x1080"
timestamp: string; // ISO 8601
language: string; // navigator.language
};
}Ingest payload
The SDK transforms the BugReport into the API payload:
{
title: string;
description: string;
severity: string;
url: string;
userAgent: string;
viewport: string;
events: BugTapeEvent[];
screenshot: ScreenshotResult | null;
audio: AudioResult | null;
aiSummary: BugSummary | null;
platform: string; // context.platform ?? 'web'
// plus context fields: reporterEmail, reporterId, sessionId, userId, release, environment, metadata
}Submitted via POST to the configured endpoint with headers:
Content-Type: application/jsonX-BugTape-Key: <apiKey>
Legacy screenshot/audio payload fields are not proof of retrievable media storage. See the ingest contract for the current storage boundary.
When the page already runs Datadog RUM, metadata.datadog carries the RUM ids from DD_RUM.getInternalContext(): rum_session_id, rum_view_id and rum_application_id. The view URL and name are not read, and BugTape never loads Datadog. If your app sets metadata.datadog itself, the SDK leaves it as is. See Use BugTape with Datadog.
Ingest response
interface IngestResponse {
id: string; // UUID — stable report ID
fingerprint: string; // 16-char hex dedup fingerprint
deduplicated: boolean; // true if matched an existing report
regression: boolean; // true if a resolved bug reoccurred
is_regression: boolean; // alias for regression
warnings?: Array<'release_missing'>; // no release: BugTape cannot tell a regression from old noise
schemaVersion?: 1;
captureId?: string; // stable capture acknowledgement when schema v1 is used
occurrenceId?: string; // required UUID in a schema v1 acknowledgement
receivedAt?: string;
}Submission behavior
- Default flow: SDK POSTs the report to the ingest endpoint.
Schema v1 submissions require a matching captureId and valid occurrenceId before the SDK invokes onSuccess or removes a queued report. Missing, mismatched, malformed or oversized receipts remain retryable. Response bodies are capped at64KiB, with the existing10-second deadline and stream cancellation. Self-hosted servers must support the v1 receipt before upgrading this SDK.
- Retry: Retryable transport/server failures get one retry after a 1-second delay. Permanent validation/auth failures are not queued.
- Offline fallback: Eligible failed captures can be stored in a bounded local retry queue when storage is available. Retry retains the original destination, credential scope and capture ID.
onErrorreports whether the capture was saved; do not assume every failure was persisted. - Custom submit: If
onSubmitis provided, it replaces the default submission. You can call the API yourself or route reports elsewhere.
Client-detail tokens
Seal client details that your team needs to read later, so BugTape stores them but cannot read them.
<td data-bugtape-token>Anna Smith</td>import { init, token } from 'https://app.bugtape.ai/bugtape.mjs';
init({ apiKey: 'bt_live_…', tokenize: { publicKey: 'btpk1_…' } });
const placeholder = token('Anna Smith'); // '⟦bt:1⟧'- Text inside
[data-bugtape-token]elements is replaced in the replay, click breadcrumbs and page headings by a placeholder (⟦bt:N⟧). The same value keeps the same placeholder on one page. token(value)returns the placeholder for a value you pass yourself (context, metadata,identify({ name })). Do not tokenizeuserId,reporterEmailoremail: they count who a bug reached and drive notifications. Keep placeholders out of error messages and titles: they change between page loads, so they would split one issue into many.- On submit, the values a report uses are sealed into
metadata.bugtapeTokenswith the public key: ECDH P-256, HKDF-SHA256 and AES-256-GCM, with a fresh key per report. The server keeps the sealed vault and returns it ascapture.clientTokensfromGET /v1/bugs/:id/events?occurrenceId=. - The console opens the vault in the browser with the private key (
btsk1_…), held insessionStoragefor that tab. Make a key pair in Settings → Client details. - Without
tokenize.publicKey, marked text is masked (*in the replay,▇fromtoken()), never sent as it is. - A lost private key cannot be recovered. Placeholders in reports sealed with it stay closed.
- Strict mode still masks all other page text and keeps placeholders, so the two work together.
Encryption limitation
End-to-end encryption of whole reports is not supported. Setting encryptionKey causes submission to fail explicitly; use client-detail tokens to seal specific values. The API does not provide a retrievable encrypted-artifact/decryption path. Do not use this option or claim private ciphertext storage.
PII scrubbing
Enabled by default. Scrubs the following patterns from all event data at capture time:
- Email addresses
- Credit card numbers (validated with Luhn algorithm)
- US Social Security numbers
- Phone numbers
- JWTs
- API keys / bearer tokens
- IPv4 addresses
Configure with pii: { enabled: false } to disable.
Before a capture key on client-data UI: complete the privacy proof checklist and run the privacy fixture. Default regex scrub is not enough for named clients in table text — mark containers with data-bugtape-ignore / .bugtape-ignore / .rr-block, set privacy: 'strict' (all page text masked in the browser), or set disableDomRecording: true / use server-only ingest.
AI summaries
The SDK fetches a short AI-generated summary when the bug report panel opens. The summary contains:
- A suggested title
- A severity assessment (
critical/high/medium/low) - A 1–3 sentence description of what happened
The summary is included in the submitted report alongside the user-provided title and description.
AI Summary (server-side)
This is the default path. The SDK sends the last ~300 events to BugTape's /v1/ingest/ai-summary endpoint, which calls the workspace's configured AI provider (OpenAI, Anthropic, or Google) server-side and returns the summary.
No additional client configuration is required — it uses the same apiKey as ingest. If the workspace has no AI provider configured, the server responds with 503 and the SDK silently falls back to submitting without a summary; the bug report is still filed.
Set summarize: false to disable the AI summary request entirely.
Provider credentials
Omit the deprecated openaiKey option. Keep provider credentials on the server; do not place them in browser code or NEXT_PUBLIC_* variables. Use summarize: false if you do not want the summary request.
Error boundaries
All SDK recorders are wrapped in error boundaries. If a recorder fails (e.g., rrweb throws on an unusual DOM), the SDK logs a warning and continues. Test the integration against your own app; this does not guarantee that every host interaction is harmless.