# API Reference

Complete reference for every method in `@capturesweet/sdk`.

## Initialization

```typescript
import CaptureSweet from '@capturesweet/sdk';

const cs = new CaptureSweet(apiKey: string, options?: {
  baseUrl?: string;   // Default: "https://capturesweet.com"
  fetch?: typeof fetch; // Default: globalThis.fetch
});
```

---

## cs.captures

### `captures.create(params?)`

Create a new capture and receive a presigned upload URL.

**Parameters:**
| Field | Type | Default | Description |
|-------|------|---------|-------------|
| `title` | `string` | `"Untitled Recording"` | Capture title |
| `description` | `string` | `null` | Description text |
| `privacy` | `CapturePrivacy` | `"link_only"` | Privacy level |

**Returns:** `CreateCaptureResponse`
```typescript
{ id: string; slug: string; upload_url: string; storage_path: string }
```

**Errors:** `unauthorized`, `bad_request` (free tier limit reached)

**Example:**
```typescript
const { id, slug, upload_url } = await cs.captures.create({
  title: "Onboarding Flow",
  privacy: "public",
});
```

---

### `captures.list(params?)`

List the authenticated user's captures.

**Parameters:**
| Field | Type | Default | Description |
|-------|------|---------|-------------|
| `page` | `number` | `1` | Page number |
| `limit` | `number` | `20` | Items per page (max 100) |
| `sort` | `"created_at" \| "views" \| "title"` | `"created_at"` | Sort field |
| `privacy` | `CapturePrivacy \| "all"` | all | Filter by privacy |
| `folder_id` | `string` | - | Filter by folder |
| `search` | `string` | - | Search title and transcript |

**Returns:** `ListCapturesResponse`
```typescript
{ captures: Capture[]; total: number; page: number; limit: number }
```

**Errors:** `unauthorized`

**Example:**
```typescript
const { captures, total } = await cs.captures.list({
  search: "demo",
  limit: 10,
  sort: "views",
});
```

---

### `captures.get(id)`

Get a single capture by ID, including its screenshots.

**Parameters:**
| Field | Type | Description |
|-------|------|-------------|
| `id` | `string` | Capture UUID |

**Returns:** `CaptureWithScreenshots` (Capture object with `screenshots: Screenshot[]`)

**Errors:** `unauthorized`, `not_found`

**Example:**
```typescript
const capture = await cs.captures.get("550e8400-e29b-41d4-a716-446655440000");
console.log(capture.title, capture.screenshots.length);
```

---

### `captures.update(id, params)`

Update a capture's metadata, privacy, page configuration, or status.

**Parameters:**
| Field | Type | Description |
|-------|------|-------------|
| `id` | `string` | Capture UUID |
| `title` | `string` | Updated title |
| `description` | `string \| null` | Updated description |
| `privacy` | `CapturePrivacy` | Privacy level |
| `page_config` | `PageConfig` | Module configuration, branding, etc. |
| `status` | `CaptureStatus` | `"processing" \| "ready" \| "failed" \| "deleted"` |
| `published` | `boolean` | Whether the capture is published |
| `thumbnail_path` | `string` | Path to thumbnail image |
| `duration_seconds` | `number` | Video duration |
| `transcript` | `object \| null` | Structured transcript data |
| `transcript_text` | `string \| null` | Plain text transcript |
| `navigation_log` | `object \| null` | Browser navigation log |

**Returns:** `Capture`

**Errors:** `unauthorized`, `not_found`, `forbidden`

**Example:**
```typescript
const updated = await cs.captures.update("capture-id", {
  title: "Updated Demo",
  privacy: "public",
  page_config: {
    branding: { primary_color: "#4F46E5" },
    modules: [
      { type: "transcript", enabled: true, position: "sidebar", searchable: true },
    ],
  },
});
```

---

### `captures.delete(id)`

Soft-delete a capture (sets status to `"deleted"`).

**Parameters:**
| Field | Type | Description |
|-------|------|-------------|
| `id` | `string` | Capture UUID |

**Returns:** `{ success: boolean }`

**Errors:** `unauthorized`

**Example:**
```typescript
await cs.captures.delete("capture-id");
```

---

### `captures.getUploadUrl(id)`

Generate a new presigned upload URL for an existing capture.

**Parameters:**
| Field | Type | Description |
|-------|------|-------------|
| `id` | `string` | Capture UUID |

**Returns:** `{ upload_url: string; storage_path: string }`

**Errors:** `unauthorized`, `not_found`

**Example:**
```typescript
const { upload_url } = await cs.captures.getUploadUrl("capture-id");
```

---

### `captures.addScreenshot(captureId, params)`

Upload a screenshot image for a capture (multipart form data).

**Parameters:**
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `captureId` | `string` | yes | Capture UUID |
| `file` | `Blob` | yes | Image file blob |
| `filename` | `string` | no | Filename (default: `"screenshot.png"`) |
| `timestamp` | `number` | no | Timestamp in the video (seconds) |
| `xpath` | `string` | no | XPath of the captured element |
| `url` | `string` | no | URL of the page when captured |
| `metadata` | `object` | no | Arbitrary metadata |

**Returns:** `Screenshot`

**Errors:** `unauthorized`, `not_found`, `bad_request`

**Example:**
```typescript
const screenshot = await cs.captures.addScreenshot("capture-id", {
  file: imageBlob,
  timestamp: 12.5,
  url: "https://example.com/dashboard",
  metadata: { element: "hero-section" },
});
```

---

## cs.folders

### `folders.list()`

List all folders for the authenticated user, including capture counts.

**Returns:** `FolderWithCount[]`
```typescript
Array<Folder & { capture_count: number }>
```

**Errors:** `unauthorized`

**Example:**
```typescript
const folders = await cs.folders.list();
```

---

### `folders.create(params)`

Create a new folder.

**Parameters:**
| Field | Type | Required | Default | Description |
|-------|------|----------|---------|-------------|
| `name` | `string` | yes | - | Folder name |
| `color` | `string` | no | `"#F23755"` | Hex color |

**Returns:** `Folder`

**Errors:** `unauthorized`, `bad_request` (empty name)

**Example:**
```typescript
const folder = await cs.folders.create({ name: "Q1 Demos", color: "#10B981" });
```

---

### `folders.update(id, params)`

Update a folder's name, color, or sort order.

**Parameters:**
| Field | Type | Description |
|-------|------|-------------|
| `name` | `string` | New name |
| `color` | `string` | New hex color |
| `sort_order` | `number` | Display order |

**Returns:** `Folder`

**Errors:** `unauthorized`, `not_found`

**Example:**
```typescript
await cs.folders.update("folder-id", { name: "Renamed Folder" });
```

---

### `folders.delete(id)`

Delete a folder. Captures inside are NOT deleted.

**Returns:** `{ success: boolean }`

**Errors:** `unauthorized`

**Example:**
```typescript
await cs.folders.delete("folder-id");
```

---

### `folders.addCaptures(folderId, params)`

Add one or more captures to a folder.

**Parameters:**
| Field | Type | Description |
|-------|------|-------------|
| `capture_id` | `string` | Single capture ID |
| `capture_ids` | `string[]` | Multiple capture IDs |

**Returns:** `{ success: boolean }`

**Errors:** `unauthorized`, `bad_request`

**Example:**
```typescript
await cs.folders.addCaptures("folder-id", {
  capture_ids: ["capture-1", "capture-2"],
});
```

---

### `folders.removeCapture(folderId, params)`

Remove a capture from a folder.

**Parameters:**
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `capture_id` | `string` | yes | Capture to remove |

**Returns:** `{ success: boolean }`

**Errors:** `unauthorized`, `bad_request`

**Example:**
```typescript
await cs.folders.removeCapture("folder-id", { capture_id: "capture-1" });
```

---

## cs.analytics

### `analytics.record(params)`

Record a viewer analytics event. This endpoint does NOT require authentication.

**Parameters:**
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `capture_id` | `string` | yes | Capture UUID |
| `event_type` | `AnalyticsEventType` | yes | Event type (see below) |
| `session_id` | `string` | no | Viewer session ID |
| `viewer_id` | `string` | no | Viewer user ID |
| `viewer_email` | `string` | no | Viewer email |
| `watch_percentage` | `number` | no | 0-100, how far they watched |
| `watch_duration_seconds` | `number` | no | Seconds watched |
| `timestamp_in_video` | `number` | no | Video timestamp of event |
| `referrer` | `string` | no | Referrer URL |

**Event types:** `view_start`, `view_end`, `play`, `pause`, `seek`, `cta_click`, `comment`, `download`, `share`

**Returns:** `{ success: boolean }`

**Errors:** `bad_request` (missing capture_id/event_type, invalid event_type)

**Example:**
```typescript
await cs.analytics.record({
  capture_id: "capture-id",
  event_type: "view_start",
  session_id: "sess-abc",
});
```

---

### `analytics.get(captureId, params?)`

Get analytics summary and raw events for a capture (owner only).

**Parameters:**
| Field | Type | Default | Description |
|-------|------|---------|-------------|
| `captureId` | `string` | - | Capture UUID |
| `days` | `number` | `7` | Lookback period in days |

**Returns:** `GetAnalyticsResponse`
```typescript
{
  summary: {
    total_views: number;
    views_in_period: number;
    avg_watch_percentage: number;
    cta_clicks: number;
  };
  events: AnalyticsEvent[];
}
```

**Errors:** `unauthorized`

**Example:**
```typescript
const { summary, events } = await cs.analytics.get("capture-id", { days: 30 });
console.log(`${summary.cta_clicks} CTA clicks`);
```

---

## cs.comments

### `comments.list(captureId)`

List approved comments for a capture. Public endpoint, no auth required.

**Returns:** `Comment[]`

**Example:**
```typescript
const comments = await cs.comments.list("capture-id");
```

---

### `comments.create(captureId, params)`

Create a comment on a capture.

**Parameters:**
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `body` | `string` | yes | Comment text |
| `timestamp_seconds` | `number` | no | Video timestamp for the comment |
| `author_name` | `string` | no | Name (auto-filled if authenticated) |
| `author_email` | `string` | no | Email (auto-filled if authenticated) |
| `parent_id` | `string` | no | Parent comment ID for threading |

**Returns:** `Comment`

**Errors:** `bad_request` (empty body)

**Example:**
```typescript
const comment = await cs.comments.create("capture-id", {
  body: "Nice walkthrough! The dashboard section at 2:30 was especially helpful.",
  timestamp_seconds: 150,
});
```

---

## Error Classes

All errors extend `CaptureSweetError`:

| Class | Code | HTTP Status |
|-------|------|-------------|
| `UnauthorizedError` | `"unauthorized"` | 401 |
| `ForbiddenError` | `"forbidden"` | 403 |
| `NotFoundError` | `"not_found"` | 404 |
| `BadRequestError` | `"bad_request"` | 400 |
| `RateLimitedError` | `"rate_limited"` | 429 |
| `ServerError` | `"server_error"` | 5xx |
| `NetworkError` | `"network_error"` | 0 |

Every error has: `.code` (string), `.status` (number), `.message` (string), `.body` (raw response).

```typescript
import { CaptureSweetError, NotFoundError } from '@capturesweet/sdk';

try {
  await cs.captures.get(id);
} catch (err) {
  if (err instanceof NotFoundError) { /* 404 */ }
  if (err instanceof CaptureSweetError) {
    switch (err.code) {
      case "unauthorized": /* re-auth */ break;
      case "rate_limited": /* back off */ break;
    }
  }
}
```

---

## Types

### CapturePrivacy
`"private" | "link_only" | "password" | "email_gate" | "public"`

### CaptureStatus
`"processing" | "ready" | "failed" | "deleted"`

### PageConfig
```typescript
{
  branding?: { logo_url?, primary_color?, background?, custom_css?, show_capture_sweet_badge? };
  modules?: Module[];
  personalization?: { enabled: boolean; params: { key: string; default_value: string }[] };
  sharing?: { custom_slug?, expiry_date?, embed_enabled?, notifications?: { on_view, on_comment, on_cta_click } };
}
```

### Module Types
`transcript | comments | cta | stripe | embed | document | contact_form | download | navigation_log | screenshots`

> Legacy aliases `claude_artifact`, `custom_embed`, and `flow_output` were renamed in June 2026 (`claude_artifact`/`custom_embed` → `embed`, `flow_output` → `document`). The API accepts the new names; existing data was migrated.

See `packages/sdk/src/types.ts` for complete type definitions.
