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

typespecs for all request and response shapes in the sf-voice media API.

these are documentation-only types — Elixir maps are used at runtime.

- **keys** are always atoms in both directions. req decodes JSON with
  `keys: :atoms`; the SDK stringifies outgoing atom keys before encoding.
- **enum values** (status, media_type, source, match_type) are atoms in
  request maps (e.g. `%{source: :url}`) but arrive as strings in response
  maps (e.g. `%{status: "ready"}`), because JSON values are not converted.

# `asset`

```elixir
@type asset() :: %{
  id: String.t(),
  media_type: String.t(),
  source_type: String.t(),
  status: String.t(),
  metadata: map() | nil,
  duration_ms: non_neg_integer() | nil,
  created_at: String.t(),
  updated_at: String.t()
}
```

a single asset stored in the library

# `asset_list_response`

```elixir
@type asset_list_response() :: %{items: [asset()], page_info: page_info()}
```

response from `GET /v1/assets`

# `ingest_request`

```elixir
@type ingest_request() :: %{
  :source =&gt; source(),
  optional(:url) =&gt; String.t(),
  optional(:s3_key) =&gt; String.t(),
  optional(:media_type) =&gt; media_type(),
  optional(:metadata) =&gt; map()
}
```

request body for `POST /v1/ingest`.

either `:url` (with `:url` key) or `:s3` (with `:s3_key` key).
`:media_type` and `:metadata` are optional for both.

# `ingest_response`

```elixir
@type ingest_response() :: %{
  asset_id: String.t(),
  task_id: String.t(),
  status: String.t()
}
```

202 response body from `POST /v1/ingest`

# `list_assets_params`

```elixir
@type list_assets_params() :: %{
  optional(:page) =&gt; pos_integer(),
  optional(:limit) =&gt; pos_integer()
}
```

optional query params for `GET /v1/assets`

# `match_type`

```elixir
@type match_type() :: :visual | :conversation | :text_in_video
```

what kind of match a search result represents

# `media_type`

```elixir
@type media_type() :: :video | :audio
```

media kind

# `page_info`

```elixir
@type page_info() :: %{
  total: non_neg_integer(),
  page: pos_integer(),
  limit: pos_integer(),
  next_page_token: String.t() | nil
}
```

pagination envelope returned by list and search endpoints.

    %{total: integer, page: integer, limit: integer, next_page_token: String.t() | nil}

# `poll_opts`

```elixir
@type poll_opts() :: %{
  optional(:interval_ms) =&gt; pos_integer(),
  optional(:timeout_ms) =&gt; pos_integer()
}
```

options for `SfVoiceMedia.poll_task!/3`.

- `:interval_ms` — milliseconds between polls (default 1_500)
- `:timeout_ms`  — max total wait time in ms before raising (default 120_000)

# `search_request`

```elixir
@type search_request() :: %{
  :query =&gt; String.t(),
  optional(:types) =&gt; [match_type()],
  optional(:asset_ids) =&gt; [String.t()],
  optional(:threshold) =&gt; float(),
  optional(:page) =&gt; pos_integer(),
  optional(:limit) =&gt; pos_integer()
}
```

request body for `POST /v1/search`

# `search_response`

```elixir
@type search_response() :: %{results: [search_result()], page_info: page_info()}
```

response from `POST /v1/search`

# `search_result`

```elixir
@type search_result() :: %{
  asset_id: String.t(),
  score: float(),
  start_ms: non_neg_integer(),
  end_ms: non_neg_integer(),
  match_type: String.t(),
  thumbnail_url: String.t() | nil
}
```

a single match returned by the search endpoint

# `source`

```elixir
@type source() :: :url | :s3
```

ingestion source — where the media file comes from

# `task`

```elixir
@type task() :: %{
  task_id: String.t(),
  asset_id: String.t(),
  status: String.t(),
  error: String.t() | nil,
  created_at: String.t(),
  completed_at: String.t() | nil
}
```

response from `GET /v1/tasks/:task_id`

# `task_status`

```elixir
@type task_status() :: :pending | :indexing | :ready | :failed
```

lifecycle state of a task or asset

---

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