Home

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/sdk

import BugTape from '@bugtape/sdk';

BugTape.init({ apiKey: 'bt_live_your_key_here' });

Supported runtimes

RuntimeSupportedNotes
Browser (modern)YesChrome, Firefox, Safari, Edge — ES2020+
Node.jsNoSDK uses browser APIs (DOM, fetch, rrweb)
Next.js (browser)YesUse the hosted module example; no SSR initialization
Next.js (server)NoUse the HTTP API for server-side ingestion
React NativeNoBrowser-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

PropertyTypeRequiredDefaultDescription
apiKeystringYes—Project API key (bt_live_* or bt_test_*)
endpointstringNohttps://app.bugtape.ai/v1/ingestIngest endpoint URL
registeredIdentity{ applicationId: string; buildId?: string; installationId?: string }NoomittedManagement-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.
releasestringNoinferredDeploy/release id on every occurrence. Prefer init({ release }). If omitted: BUGTAPE_RELEASE, then common CI SHA envs (GITHUB_SHA, VERCEL_GIT_COMMIT_SHA, …).
summarizebooleanNotrueSet to false to disable the AI summary entirely
recordingWindownumberNo10Buffer duration in minutes
maxEventsnumberNo50000Maximum events in ring buffer
autoReportboolean | { enabled?, maxPerMinute?, interactionWindowMs?, reportUncaughtErrors?, reportHungRequests?, hangTimeoutMs?, reportDeadRoutes?, emptyExpectRules? }NoonSends 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.
showButtonbooleanNotrueShow the floating bug-report button. false keeps automatic capture and open() (open the form from your own link) but shows no button.
badgeboolean | { siteName?: string; ref?: string }NofalseShow 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.
majorBugAlertboolean | MajorBugAlertOptionsNotrueSignal a newly accepted high or critical automatic issue on the report button. See Report button and signal.
positionstringNo'bottom-right'Widget position: bottom-right, bottom-left, top-right, top-left
themestringNo'auto'Widget theme: dark, light, auto
tokenize{ publicKey: string }NoomittedSeal 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.
disableDomRecordingbooleanNofalseDisable DOM mutation recording
disableNetworkRecordingbooleanNofalseDisable network request recording
disableConsoleRecordingbooleanNofalseDisable console output recording
rageClickSensitivitystringNo'medium'Rage click detection: very-sensitive, medium, low
contextobjectNo—Occurrence metadata: reporterEmail, reporterId, sessionId, userId, release, environment, platform, metadata. Prefer top-level release.
context.platformstringNo'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.
piiPartial<PiiConfig>No{ enabled: true }PII scrubbing configuration
onSubmit(report: BugReport) => void | Promise<void>No—Custom submit handler (replaces default)
onSuccess(response: IngestResponse) => voidNo—Called after successful submission
onError(error: Error) => voidNo—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

MethodReturnsDescription
open()voidOpen the bug report widget programmatically
setMajorBugAlertEnabled(enabled)voidEnable or disable the button signal; remember the choice in this browser
isMajorBugAlertEnabled()booleanRead the effective signal preference
isMajorAlertActive()booleanCheck whether the button is signaling an issue
clearMajorBugAlert()voidClear the current signal without changing the preference
signalMajorBug(reason?)voidRequest 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()voidTear down the SDK, remove event listeners, remove widget
bufferRingBufferDirect 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; set ref to 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: false hides 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 typeData captured
networkResource timing: URL, duration, transfer size, initiator type
network:fetchFetch requests: URL, method, status code, duration
network:fetch:errorFailed fetch: URL, method, error message, duration
network:xhrXHR 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 typeData
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 typeData
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:

PresetClicksWindowRadiusDebounce
very-sensitive2+1500ms50px1s
medium3+1000ms30px2s
low4+800ms20px3s

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/json
  • X-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

  1. 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.

  1. Retry: Retryable transport/server failures get one retry after a 1-second delay. Permanent validation/auth failures are not queued.
  2. 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. onError reports whether the capture was saved; do not assume every failure was persisted.
  3. Custom submit: If onSubmit is 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 tokenize userId, reporterEmail or email: 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.bugtapeTokens with 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 as capture.clientTokens from GET /v1/bugs/:id/events?occurrenceId=.
  • The console opens the vault in the browser with the private key (btsk1_…), held in sessionStorage for that tab. Make a key pair in Settings → Client details.
  • Without tokenize.publicKey, marked text is masked (* in the replay, ▇ from token()), 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.