# `SfVoiceMedia`
[🔗](https://github.com/sf-voice/sf-voice-core/blob/main/lib/sf_voice_media.ex#L1)

Elixir SDK for the sf-voice media API.

build a client with `new/2`, then call the public functions to ingest,
query, and search media.

## quick start

    client = SfVoiceMedia.new("sk-...")

    # 1. ingest a media file — returns immediately with a task id
    {:ok, %{task_id: tid}} =
      SfVoiceMedia.ingest(client, %{source: :url, url: "https://example.com/clip.mp4"})

    # 2. wait for indexing to complete — raises on failure or timeout
    task = SfVoiceMedia.poll_task!(client, tid)

    # 3. search across your indexed media with natural language
    {:ok, %{results: results}} =
      SfVoiceMedia.search(client, %{query: "product roadmap discussion"})

    # results carry timestamps so you can jump to the exact moment
    Enum.each(results, fn r ->
      IO.puts("#{r.asset_id} at #{r.start_ms}ms — #{r.match_type}")
    end)

all functions return `{:ok, result}` or `{:error, %SfVoiceMedia.Error{}}`.
`poll_task!/3` raises `SfVoiceMedia.Error` on timeout or task failure.

# `delete_asset`

```elixir
@spec delete_asset(SfVoiceMedia.Client.t(), String.t()) ::
  :ok | {:error, SfVoiceMedia.Error.t()}
```

Soft-deletes an asset so it is excluded from list results while the backend retains the record.

Returns `:ok` if the deletion was successful (HTTP 204), `{:error, %SfVoiceMedia.Error{}}` otherwise.

## Examples

    :ok = SfVoiceMedia.delete_asset(client, "ast_abc123")

# `get_asset`

```elixir
@spec get_asset(SfVoiceMedia.Client.t(), String.t()) ::
  {:ok, SfVoiceMedia.Types.asset()} | {:error, SfVoiceMedia.Error.t()}
```

Retrieve a library asset by its ID.

Returns `{:ok, asset}` when the asset is found, or `{:error, %SfVoiceMedia.Error{}}` on failure.

# `get_task`

```elixir
@spec get_task(SfVoiceMedia.Client.t(), String.t()) ::
  {:ok, SfVoiceMedia.Types.task()} | {:error, SfVoiceMedia.Error.t()}
```

Fetches the current state of an ingestion task.

Returns `{:ok, task}` where `task` is a map describing the task (includes a `"status"` field), or `{:error, %SfVoiceMedia.Error{}}` on failure.

## Examples

    {:ok, %{status: "ready", asset_id: aid}} = SfVoiceMedia.get_task(client, "task_abc123")

# `ingest`

```elixir
@spec ingest(SfVoiceMedia.Client.t(), SfVoiceMedia.Types.ingest_request()) ::
  {:ok, SfVoiceMedia.Types.ingest_response()} | {:error, SfVoiceMedia.Error.t()}
```

Submit a media file for ingestion from a URL or an S3 key.

Returns immediately with a task identifier that can be inspected with `get_task/2` or awaited with `poll_task/3`.

## Returns

  - `{:ok, response}` — successful response containing at least `task_id` and optionally `asset_id` and other task metadata.
  - `{:error, %SfVoiceMedia.Error{}}` — request failed; contains error details.

# `list_assets`

```elixir
@spec list_assets(SfVoiceMedia.Client.t(), SfVoiceMedia.Types.list_assets_params()) ::
  {:ok, SfVoiceMedia.Types.asset_list_response()}
  | {:error, SfVoiceMedia.Error.t()}
```

lists assets in the library, paginated.

## examples

    {:ok, %{items: items, page_info: info}} =
      SfVoiceMedia.list_assets(client, %{page: 1, limit: 20})

    # no params — uses server defaults
    {:ok, %{items: items}} = SfVoiceMedia.list_assets(client)

# `new`

```elixir
@spec new(
  String.t(),
  keyword()
) :: SfVoiceMedia.Client.t()
```

Builds a SfVoiceMedia.Client preconfigured with the given API key and optional settings.

## Options

  - `:base_url` — API base URL; defaults to "https://api.sf-voice.com". A trailing `/` is removed.
  - `:http_opts` — keyword list forwarded to Req for every request; defaults to `[]`.

## Examples

    client = SfVoiceMedia.new("sk-my-api-key")

    client = SfVoiceMedia.new("sk-my-api-key",
      base_url: "https://staging.api.sf-voice.com",
      http_opts: [receive_timeout: 10_000]
    )

# `poll_task!`

Polls an ingestion task until its status becomes "ready" or "failed".

polls `get_task/2` at a fixed interval. returns the final task map when
the task reaches "ready". raises `SfVoiceMedia.Error` if the task fails
or the timeout is exceeded.

## options

  - `:interval_ms` — milliseconds to wait between polls (default: 1_500)
  - `:timeout_ms`  — maximum total wait in milliseconds (default: 120_000)

## examples

    task = SfVoiceMedia.poll_task!(client, tid)
    task = SfVoiceMedia.poll_task!(client, tid, interval_ms: 2_000, timeout_ms: 60_000)

# `search`

```elixir
@spec search(SfVoiceMedia.Client.t(), SfVoiceMedia.Types.search_request()) ::
  {:ok, SfVoiceMedia.Types.search_response()} | {:error, SfVoiceMedia.Error.t()}
```

Run a semantic search over indexed media.

The `request` map must include a `:query` string and may include optional parameters such as `:types` (list of asset types), `:threshold` (similarity threshold), and `:limit` (maximum results). Returns the API response wrapped in `{:ok, result}` or `{:error, %SfVoiceMedia.Error{}}`.

## Examples

    {:ok, %{results: results}} =
      SfVoiceMedia.search(client, %{query: "product roadmap discussion"})

    {:ok, %{results: results}} =
      SfVoiceMedia.search(client, %{
        query: "quarterly targets",
        types: [:conversation],
        threshold: 0.7,
        limit: 10
      })

---

*Consult [api-reference.md](api-reference.md) for complete listing*
