# SDK Reference

The BugTape JavaScript/TypeScript SDK records browser activity and lets users submit bug reports with full context.

## Installation

The public npm package is not published. Use the hosted browser module after applying your app's consent policy:

```html
<script type="module">
  import { init } from 'https://app.bugtape.ai/bugtape.mjs';
  init({ apiKey: 'bt_live_your_key_here' });
</script>
```

## 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

```javascript
import { init, identify } from 'https://app.bugtape.ai/bugtape.mjs';
```

This import belongs in a browser module. For Next.js use the [hosted module example](./examples/nextjs/README.md); do not ask a bundler to install the unavailable npm package.

## Initialization

```typescript
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 |
| `position` | `string` | No | `'bottom-right'` | Widget position: `bottom-right`, `bottom-left`, `top-right`, `top-left` |
| `theme` | `string` | No | `'auto'` | Widget theme: `dark`, `light`, `auto` |
| `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**:

```javascript
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](./agents.md).

### `BugTapeInstance`

| Method | Returns | Description |
|--------|---------|-------------|
| `open()` | `void` | Open the bug report widget programmatically |
| `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) |

## Auto-captured events

Once initialized, the SDK automatically captures these event types:

### DOM events (`dom`)

Recorded via [rrweb](https://github.com/rrweb-io/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.

### 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 |

### 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`)

```typescript
{ 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:

```typescript
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`:

```typescript
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:

```typescript
{
  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](./ingest-schema.md) for the current storage boundary.

## Ingest response

```typescript
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
  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.

2. **Retry**: Retryable transport/server failures get one retry after a 1-second delay. Permanent validation/auth failures are not queued.
3. **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.
4. **Custom submit**: If `onSubmit` is provided, it replaces the default submission. You can call the API yourself or route reports elsewhere.

## Encryption limitation

End-to-end report encryption is not supported. Setting `encryptionKey` causes submission to fail explicitly. 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](./privacy-proof-checklist.md) and run the [privacy fixture](./examples/privacy-fixture/). Default regex scrub is not enough for named clients in table text — mark containers with `data-bugtape-ignore` / `.bugtape-ignore` / `.rr-block`, 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.
