> ## Documentation Index
> Fetch the complete documentation index at: https://docs.raidzero.icu/llms.txt
> Use this file to discover all available pages before exploring further.

# TypeScript

> @sf-voice/media TypeScript SDK reference: create a client, ingest assets, poll indexing tasks, and search video, audio, and transcripts.

`@sf-voice/media` — requires a runtime with `fetch`, `FormData`, and `Blob`.

Current version: `0.1.2`. Pin it while the API is moving.

## Install

```sh pnpm theme={null}
pnpm add @sf-voice/media@0.1.2
```

```sh npm theme={null}
npm install @sf-voice/media@0.1.2
```

```sh bun theme={null}
bun add @sf-voice/media@0.1.2
```

## Create a client

```ts theme={null}
import { SfVoiceMedia } from "@sf-voice/media";

const client = new SfVoiceMedia({
  baseUrl: "https://api.sf-voice.com",
  apiKey: process.env.SF_VOICE_API_KEY!,
});
```

| Option | Type | Default | |
| - | - | - | - |
| `baseUrl` | `string` | — | Required. |
| `apiKey` | `string` | — | Required. Your `sk_live_...` key, sent as `Authorization: Bearer`. |
| `timeoutMs` | `number` | `30_000` | Per-request abort timeout. |

## Ingest

`ingest(request)` submits a recording and returns immediately with a `task_id`.

### From a URL

```ts theme={null}
const { task_id } = await client.ingest({
  source: "url",
  asset_id: "call_001",
  asset_class: "customer_acme",
  url: "https://storage.example.com/calls/001.mp3",
  media_type: "audio",
  types: ["audio", "transcript"],
  metadata: { caller: "+14155550199" },
});
```

### From S3

```ts theme={null}
const { task_id } = await client.ingest({
  source: "s3",
  asset_id: "call_001",
  asset_class: "customer_acme",
  s3_key: "calls/customer_acme/001.mp3",
  media_type: "audio",
  types: ["audio", "transcript"],
});
```

### From a file

```ts theme={null}
const buffer = await file.arrayBuffer();

const { task_id } = await client.ingest({
  source: "file",
  asset_id: "call_001",
  asset_class: "customer_acme",
  file: buffer,
  filename: "001.mp3",
  content_type: "audio/mpeg",
  media_type: "audio",
  types: ["audio", "transcript"],
});
```

`file` accepts `Blob`, `ArrayBuffer`, or `Uint8Array`.

### Common fields

| Field | Required | |
| - | - | - |
| `asset_id` | Yes | Your ID. Returned on search results. |
| `asset_class` | No | Logical group. Scopes search so results don't leak across boundaries. |
| `media_type` | No | `"audio"` or `"video"`. Auto-detected if omitted. |
| `types` | No | Surfaces to index: `"audio"`, `"video"`, `"transcript"`. Defaults to all. |
| `metadata` | No | Flat string/number/boolean pairs for your own use. |

### Response

```ts theme={null}
type IngestResponse = {
  asset_id: string;
  task_id: string;
  status: "pending";
};
```

## Tasks

### Poll until ready

```ts theme={null}
const task = await client.pollTask(task_id, {
  intervalMs: 2_000,   // how often to check (default 1_500)
  timeoutMs: 120_000,  // give up after this long (default 120_000)
});

if (task.status === "failed") {
  throw new Error(task.error ?? "indexing failed");
}
```

`pollTask` throws `SfVoiceMediaPollTimeoutError` if the task doesn't reach `ready` or `failed` within the timeout.

### Check once

```ts theme={null}
const task = await client.getTask(task_id);
// task.status: "pending" | "indexing" | "ready" | "failed"
```

### Task shape

```ts theme={null}
type Task = {
  task_id: string;
  asset_id: string;
  asset_class?: string;
  types: Array<"audio" | "video" | "transcript">;
  status: "pending" | "indexing" | "ready" | "failed";
  error?: string;
  created_at: string;
  completed_at?: string;
};
```

## Search

`search(request)` returns timestamped matches across indexed recordings.

### By asset class

```ts theme={null}
const { results } = await client.search({
  query: "customer asks about cancelling their subscription",
  asset_class: "customer_acme",
  types: ["transcript"],
  threshold: 0.7,
  limit: 10,
});
```

### By specific assets

```ts theme={null}
const { results } = await client.search({
  query: "pricing question",
  asset_ids: ["call_001", "call_002"],
});
```

### Search all indexed assets

```ts theme={null}
const { results } = await client.search({
  query: "AI interrupted the caller",
  scope: "all",
});
```

> [!WARNING]
>
> `scope: "all"` searches across your indexed assets. Use `asset_class` or `asset_ids` for anything customer-facing.

### Search fields

| Field | Required | |
| - | - | - |
| `query` | Yes | Natural language. |
| `types` | No | `"audio"`, `"video"`, `"transcript"`. Searches all if omitted. |
| `asset_class` | No | Restrict to one logical group. |
| `asset_ids` | No | Restrict to a specific set of assets. |
| `scope` | No | Set to `"all"` to search across indexed assets. |
| `threshold` | No | `0.0`–`1.0`. Default `0.5`. Higher = fewer, more confident results. |
| `page` | No | Page number. |
| `limit` | No | Results per page. Max 50. |

### Result shape

```ts theme={null}
type SearchResult = {
  asset_id: string;
  score: number;      // 0.0–1.0
  start_ms: number;   // offset into the recording
  end_ms: number;
  match_type: "audio" | "video" | "transcript";
  thumbnail_url?: string;
};
```

`start_ms` and `end_ms` are milliseconds into the source recording. Use them to seek a player to the matching segment.

## Assets

```ts theme={null}
// list (paginated)
const { items, page_info } = await client.listAssets({ page: 1, limit: 20 });

// single
const asset = await client.getAsset("call_001");

// delete
await client.deleteAsset("call_001");
```

```ts theme={null}
type Asset = {
  asset_id: string;
  asset_class?: string;
  media_type: "audio" | "video";
  source_type: "url" | "s3" | "file";
  types: Array<"audio" | "video" | "transcript">;
  status: "pending" | "indexing" | "ready" | "failed";
  metadata?: Record<string, string | number | boolean>;
  duration_ms?: number;
  created_at: string;
  updated_at: string;
};
```

## Errors

```ts theme={null}
import { SfVoiceMediaError } from "@sf-voice/media";

try {
  await client.search({ query: "...", asset_class: "customer_acme" });
} catch (err) {
  if (err instanceof SfVoiceMediaError) {
    console.error(err.code);    // machine-readable code
    console.error(err.status);  // HTTP status
    console.error(err.message); // human-readable
  }
}
```

| Class | When |
| - | - |
| `SfVoiceMediaError` | Non-2xx API response. |
| `SfVoiceMediaRequestTimeoutError` | Single request exceeded `timeoutMs`. |
| `SfVoiceMediaPollTimeoutError` | `pollTask` timed out before terminal state. Carries `taskId`. |


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.