npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@sf-voice/media

v0.1.1

Published

TypeScript SDK for the sf-voice media API

Readme

@sf-voice/media

TypeScript SDK for the sf-voice media API.

Version: 0.1.1

Installation

pnpm add @sf-voice/media@latest
npm install @sf-voice/media@latest
bun install @sf-voice/media@latest

Requirements

  • An sf-voice API key.
  • The media API base URL.
  • A runtime with fetch, FormData, and Blob support.

Client Setup

import { SfVoiceMedia } from "@sf-voice/media";

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

| Option | Required | Description | |---|---:|---| | baseUrl | yes | Base URL for the sf-voice media API. | | apiKey | yes | API key sent as the X-API-Key header. | | timeoutMs | no | Per-request timeout in milliseconds. Defaults to 30_000. |

Core Concepts

| Field | Description | |---|---| | asset_id | Customer-provided unique id for the media asset. Use your own stable id so you can correlate results with your system. Required on ingest. | | asset_class | Optional logical group for assets, for example one customer, workspace, repository, or project. Use this to keep search scoped to the right group. | | types | Optional media surfaces to index or search. Allowed values: "video", "audio", "transcript". | | metadata | Optional flat key/value metadata. Values must be strings, numbers, or booleans. | | threshold | Optional minimum match score from 0.0 to 1.0. Higher values return fewer, more confident search results. |

Quickstart

import { SfVoiceMedia, SfVoiceMediaError } from "@sf-voice/media";

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

try {
  const ingest = await client.ingest({
    source: "url",
    asset_id: "video_123",
    asset_class: "customer_acme",
    url: "https://example.com/recording.mp4",
    media_type: "video",
    types: ["video", "audio", "transcript"],
    metadata: {
      title: "product demo",
      customer_id: "acme",
    },
  });

  const task = await client.pollTask(ingest.task_id, {
    intervalMs: 2_000,
    timeoutMs: 120_000,
  });

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

  const search = await client.search({
    query: "where does the customer mention pricing?",
    asset_class: "customer_acme",
    types: ["transcript"],
    threshold: 0.7,
    limit: 10,
  });

  console.log(search.results);
} catch (error) {
  if (error instanceof SfVoiceMediaError) {
    console.error(error.code, error.status, error.message);
  }
  throw error;
}

Ingest

ingest(request) submits media for indexing and returns immediately with a task id.

const ingest = await client.ingest({
  source: "url",
  asset_id: "video_123",
  asset_class: "customer_acme",
  url: "https://example.com/recording.mp4",
  media_type: "video",
  types: ["video", "transcript"],
  metadata: { title: "product demo" },
});

Ingest Input

Common fields:

| Field | Required | Description | |---|---:|---| | asset_id | yes | Your unique id for the asset. | | asset_class | no | Logical group for the asset. Search can later use this as a scope. | | media_type | no | "video" or "audio". | | types | no | Surfaces to index: "video", "audio", "transcript". | | metadata | no | Flat key/value metadata for your own correlation. |

Source-specific fields:

| Source | Required Fields | Description | |---|---|---| | url | url | Ingest from a public or backend-accessible URL. | | s3 | s3_key | Ingest from an S3 key already connected to sf-voice. | | file | file, filename | Upload a Blob, ArrayBuffer, or Uint8Array directly. |

URL Ingest

const ingest = await client.ingest({
  source: "url",
  asset_id: "video_123",
  asset_class: "customer_acme",
  url: "https://example.com/recording.mp4",
  media_type: "video",
  types: ["video", "audio", "transcript"],
});

S3 Ingest

const ingest = await client.ingest({
  source: "s3",
  asset_id: "support_call_456",
  asset_class: "customer_acme",
  s3_key: "uploads/customer_acme/support_call_456.mp3",
  media_type: "audio",
  types: ["audio", "transcript"],
});

File Ingest

const file = await input.files?.[0]?.arrayBuffer();
if (!file) throw new Error("missing file");

const ingest = await client.ingest({
  source: "file",
  asset_id: "browser_upload_789",
  asset_class: "customer_acme",
  file,
  filename: "demo.mp4",
  content_type: "video/mp4",
  media_type: "video",
  types: ["video", "audio", "transcript"],
});

Ingest Output

type IngestResponse = {
  asset_id: string;
  task_id: string;
  status: "pending";
};

Example:

{
  "asset_id": "video_123",
  "task_id": "task_abc123",
  "status": "pending"
}

Tasks And Polling

Use getTask(taskId) to fetch the current task state once. Use pollTask(taskId, options) to wait until the task reaches "ready" or "failed".

const task = await client.pollTask("task_abc123", {
  intervalMs: 2_000,
  timeoutMs: 120_000,
});

Task Output

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

Example:

{
  "task_id": "task_abc123",
  "asset_id": "video_123",
  "asset_class": "customer_acme",
  "types": ["video", "audio", "transcript"],
  "status": "ready",
  "created_at": "2026-05-27T12:00:00Z",
  "completed_at": "2026-05-27T12:01:42Z"
}

Search

search(request) searches indexed media with natural language.

Search should usually be scoped with either asset_ids or asset_class.

const search = await client.search({
  query: "where does the customer mention pricing?",
  asset_class: "customer_acme",
  types: ["transcript"],
  threshold: 0.7,
  page: 1,
  limit: 10,
});

Search Input

| Field | Required | Description | |---|---:|---| | query | yes | Natural-language search query. | | types | no | Which surfaces to search: "video", "audio", "transcript". | | asset_ids | no | Restrict search to specific customer asset ids. | | asset_class | no | Restrict search to one logical group. Recommended for customer-scoped search. | | scope | no | Set to "all" only when intentionally searching across every asset. | | threshold | no | Minimum match score from 0.0 to 1.0. Defaults to the API default. | | page | no | Page number. | | limit | no | Max results per page. |

Search By Asset Class

const results = await client.search({
  query: "refund policy",
  asset_class: "customer_acme",
  types: ["transcript"],
});

Search Specific Assets

const results = await client.search({
  query: "installation steps",
  asset_ids: ["video_123", "video_456"],
  types: ["video", "transcript"],
});

Search All Assets

const results = await client.search({
  query: "security review",
  scope: "all",
  types: ["transcript"],
});

Search Output

type SearchResponse = {
  results: Array<{
    asset_id: string;
    score: number;
    start_ms: number;
    end_ms: number;
    match_type: "video" | "audio" | "transcript";
    thumbnail_url?: string;
  }>;
  page_info: {
    total: number;
    page: number;
    limit: number;
    next_page_token?: string;
  };
};

Example:

{
  "results": [
    {
      "asset_id": "video_123",
      "score": 0.84,
      "start_ms": 42000,
      "end_ms": 58000,
      "match_type": "transcript",
      "thumbnail_url": "https://api.sf-voice.com/assets/video_123/thumb.jpg"
    }
  ],
  "page_info": {
    "total": 1,
    "page": 1,
    "limit": 10
  }
}

Assets

List Assets

const assets = await client.listAssets({
  page: 1,
  limit: 20,
});

Get Asset

const asset = await client.getAsset("video_123");

Delete Asset

await client.deleteAsset("video_123");

Asset Output

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

Errors

Every non-2xx API response throws SfVoiceMediaError.

import { SfVoiceMediaError } from "@sf-voice/media";

try {
  await client.search({
    query: "pricing",
    asset_class: "customer_acme",
  });
} catch (error) {
  if (error instanceof SfVoiceMediaError) {
    console.error(error.code);
    console.error(error.status);
    console.error(error.message);
  }
  throw error;
}

Known API error codes include:

  • bucket_not_connected
  • s3_access_denied
  • s3_key_not_found
  • unsupported_format
  • file_too_large
  • provider_unavailable
  • unauthorized
  • not_found
  • rate_limited

SfVoiceMediaRequestTimeoutError is thrown when a single HTTP request exceeds the client timeout.

SfVoiceMediaPollTimeoutError is thrown when pollTask exceeds its polling timeout before the task reaches "ready" or "failed".

API Surface

client.ingest(request): Promise<IngestResponse>
client.getTask(taskId): Promise<Task>
client.pollTask(taskId, options?): Promise<Task>
client.listAssets(params?): Promise<AssetListResponse>
client.getAsset(assetId): Promise<Asset>
client.deleteAsset(assetId): Promise<void>
client.search(request): Promise<SearchResponse>

Examples