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

# Audio

> Speech to text, text to speech and speech to speech — transcribe a recording into a timestamped transcript, turn text into a stored audio file, or answer one spoken turn with another.

Three directions, three routes. **Transcription** takes a recording — a stored [file](/docs/api/files) or a public URL — and answers a transcript with per-segment timestamps. **Speech** takes up to 4 096 characters of text and answers an audio file, already saved to [Files](/docs/api/files) so the same `fil_` can be downloaded, published or handed to an agent. **Conversation** takes a stored recording of one spoken turn and answers the spoken reply, saved the same way — one model listens and speaks, with no transcript or text model in between.

All three bill on the `media` component, what the finished call actually cost, exactly like [image generation](/docs/platform/pricing#media-generation--per-finished-job-media-component). Inside a session, transcription and speech are the [`transcribe_audio` and `generate_speech`](/docs/capabilities/tools#audio-tools) tools; there is no conversation tool.

## Managed aliases and pinned models

Every route takes an optional `model`. Omit it and the request runs on the **managed alias** for that direction — `stt/auto`, `tts/auto`, `s2s/auto` — which picks the best eligible model for each request and falls back automatically when one is unavailable. Name a pinned `owner/model` from the [catalogue](#the-catalogues) to run on exactly that model instead; a pinned model that is unavailable fails rather than falling back.

Transcription and speech run in a **data-residency floor** by default: recordings and text are processed in the United States or on globally-hosted endpoints, never in a region-restricted deployment. Speech-to-speech models are hosted by their own maker, so a conversation has no such floor — the reply comes from wherever the model you chose runs.

## The transcription job

A transcription is a [`media_job`](/docs/api/media) of `kind: "transcription"`. Most recordings are transcribed inside the request, so the job comes back `completed` on the `202` itself. A long recording may be **queued** upstream: the job then comes back `processing`, with the same shape, and the platform keeps collecting it in the background — re-read it with `GET /v1/media/audio/transcriptions/{id}` until it is `completed` or `failed`. `cost_micro_usd` is `null` until the transcript lands; the debit is booked once, when it does.

The audio routes emit no `media.job.*` [events](/docs/api/events).

<ResponseField name="id" type="string">`med_…`</ResponseField>
<ResponseField name="object" type="string">Always `media_job`.</ResponseField>
<ResponseField name="kind" type="string">Always `transcription`.</ResponseField>
<ResponseField name="status" type="string">`queued` · `processing` · `completed` · `failed`.</ResponseField>
<ResponseField name="model" type="string">The transcription model or alias that ran.</ResponseField>
<ResponseField name="input" type="object">What you sent, minus `model`.</ResponseField>

<ResponseField name="result" type="object | null">
  Set once `completed`:

  <Expandable title="fields">
    <ResponseField name="text" type="string">The whole transcript.</ResponseField>
    <ResponseField name="language" type="string | null">The language detected, ISO 639-1, when the model reports one.</ResponseField>
    <ResponseField name="duration_seconds" type="number | null">The recording's length, when the model reports it.</ResponseField>
    <ResponseField name="segments" type="array">`{ start_seconds, end_seconds, text }` per utterance. Empty when `timestamps: false`.</ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="error" type="object | null">`{ code, message }` when `failed`.</ResponseField>
<ResponseField name="cost_micro_usd" type="integer | null">What was booked, in micro-USD; `null` until the job completes.</ResponseField>
<ResponseField name="created_at" type="string">RFC 3339.</ResponseField>
<ResponseField name="completed_at" type="string | null">RFC 3339.</ResponseField>

## Transcribe

`POST /v1/media/audio/transcriptions` — scope `agents:write`, [`Idempotency-Key`](/docs/api/overview#idempotency) required

<ParamField body="file_id" type="string">A stored audio file, `fil_…`. **Exactly one** of `file_id` or `url`.</ParamField>
<ParamField body="url" type="string">A public `http(s)` URL of an audio file, at most **100 MiB**. **Exactly one** of `file_id` or `url`. The same limits as a [file import](/docs/api/files#import-a-file-from-a-url): loopback, private-network, link-local and cloud-metadata hosts are refused, every redirect is checked the same way, and a larger declared or actual body is `validation_failed` on `url`.</ParamField>
<ParamField body="model" type="string" default="stt/auto">A model from [`GET /v1/media/models?kind=stt`](#the-catalogues).</ParamField>
<ParamField body="language" type="string">ISO 639-1 hint, for example `en`. Omitted, the model detects it.</ParamField>
<ParamField body="timestamps" type="boolean" default="true">Return per-segment timestamps.</ParamField>

<CodeGroup>
  ```bash cURL theme={"system"}
  curl -fsSL https://api.vetta.sh/v1/media/audio/transcriptions \
    -H "authorization: Bearer sk_live_..." \
    -H "idempotency-key: 4a1f…" \
    -H "content-type: application/json" \
    -d '{ "url": "https://cdn.example.com/earnings-call.mp3", "language": "en" }'
  ```

  ```ts SDK theme={"system"}
  const job = await vetta.media.audio.transcribe({
    url: "https://cdn.example.com/earnings-call.mp3",
    language: "en",
  });
  ```

  ```bash CLI theme={"system"}
  vetta media transcribe https://cdn.example.com/earnings-call.mp3 --language en
  ```
</CodeGroup>

<ResponseExample>
  ```json 202 Accepted theme={"system"}
  {
    "id": "med_01j9x3c2g8h4k5m6n7p8q9r0s1",
    "object": "media_job",
    "kind": "transcription",
    "status": "completed",
    "model": "stt/auto",
    "input": { "url": "https://cdn.example.com/earnings-call.mp3", "language": "en", "timestamps": true },
    "result": {
      "text": "Good morning, and thank you for joining us.",
      "language": "en",
      "duration_seconds": 3.4,
      "segments": [{ "start_seconds": 0, "end_seconds": 3.4, "text": "Good morning, and thank you for joining us." }]
    },
    "error": null,
    "cost_micro_usd": 184,
    "session_id": null,
    "created_at": "2026-09-06T10:00:00Z",
    "completed_at": "2026-09-06T10:00:00Z"
  }
  ```
</ResponseExample>

## Retrieve a transcription

`GET /v1/media/audio/transcriptions/{id}` — scope `agents:read`

The same object, by id. A `med_` from another organization, or one that is not a transcription, is `404 not_found`.

## Speak

`POST /v1/media/audio/speech` — scope `agents:write`, [`Idempotency-Key`](/docs/api/overview#idempotency) required

Synchronous: the reply carries the stored file. Download the bytes with [`GET /v1/files/{id}?download=true`](/docs/api/files#retrieve-a-file).

<ParamField body="text" type="string" required>What to say, 1–4 096 characters.</ParamField>
<ParamField body="model" type="string" default="tts/auto">A model from [`GET /v1/media/models?kind=tts`](#the-catalogues).</ParamField>
<ParamField body="voice" type="string">One of the voices the model offers. Omitted, the model's default. A voice the model does not have is `validation_failed` on `voice`.</ParamField>
<ParamField body="format" type="string" default="mp3">`mp3` · `wav` · `ogg`.</ParamField>
<ParamField body="speed" type="number">0.5–2. Omitted, the model's own pace.</ParamField>

<CodeGroup>
  ```bash cURL theme={"system"}
  curl -fsSL https://api.vetta.sh/v1/media/audio/speech \
    -H "authorization: Bearer sk_live_..." \
    -H "idempotency-key: 9c7e…" \
    -H "content-type: application/json" \
    -d '{ "text": "Your order has shipped and arrives Thursday." }'
  ```

  ```ts SDK theme={"system"}
  const speech = await vetta.media.audio.speak({ text: "Your order has shipped and arrives Thursday." });
  const mp3 = await vetta.files.download(speech.file.id);
  ```

  ```bash CLI theme={"system"}
  vetta media speak "Your order has shipped and arrives Thursday." | jq -r .file.id | xargs vetta file download > shipped.mp3
  ```
</CodeGroup>

<ResponseExample>
  ```json 200 OK theme={"system"}
  {
    "object": "speech_generation",
    "model": "tts/auto",
    "file": {
      "id": "fil_01j9x3d7k2m4n6p8q0r2s4t6u8",
      "object": "file",
      "name": "fil_01j9x3d7k2m4n6p8q0r2s4t6u8.mp3",
      "content_type": "audio/mpeg",
      "size_bytes": 41872,
      "scope": "published",
      "session_id": null,
      "sha256": "3f…",
      "created_at": "2026-09-06T10:00:01Z"
    },
    "duration_seconds": null,
    "cost_micro_usd": 36
  }
  ```
</ResponseExample>

The cost of a speech call is settled from the provider's usage record after the audio is returned. When that record is not yet available, the call is billed at the `media` component's floor rather than held.

## Converse

`POST /v1/media/audio/conversations` — scope `agents:write`, [`Idempotency-Key`](/docs/api/overview#idempotency) required

Synchronous, like speech: one spoken turn in, the spoken reply out, as a stored file. The input is a stored file only — [upload](/docs/api/files#upload-a-file) or [import](/docs/api/files#import-a-file-from-a-url) a recording first; the CLI does both for you.

<ParamField body="file_id" type="string" required>A stored audio file, `fil_…`, holding the turn to answer.</ParamField>
<ParamField body="model" type="string" default="s2s/auto">A model from [`GET /v1/media/models?kind=s2s`](#the-catalogues).</ParamField>
<ParamField body="voice" type="string">One of the voices the model offers. Omitted, the model's default.</ParamField>
<ParamField body="format" type="string" default="mp3">`mp3` · `wav` · `ogg`, the format of the reply.</ParamField>

<CodeGroup>
  ```bash cURL theme={"system"}
  curl -fsSL https://api.vetta.sh/v1/media/audio/conversations \
    -H "authorization: Bearer sk_live_..." \
    -H "idempotency-key: b81d…" \
    -H "content-type: application/json" \
    -d '{ "file_id": "fil_01j9x3f0a1b2c3d4e5f6g7h8j9", "format": "wav" }'
  ```

  ```ts SDK theme={"system"}
  const reply = await vetta.media.audio.converse({ file_id: turn.id, format: "wav" });
  const wav = await vetta.files.download(reply.file.id);
  ```

  ```bash CLI theme={"system"}
  vetta media converse ./question.wav | jq -r .file.id | xargs vetta file download > reply.mp3
  ```
</CodeGroup>

The reply is a `speech_generation`, the same object [Speak](#speak) answers, with `model` naming the conversation model or alias that ran.

## The catalogues

[`GET /v1/media/models`](/docs/api/media) answers the audio models too: `kind=stt`, `kind=tts` and `kind=s2s` each list the managed alias for that direction first, then the pinned models cheapest-first where a published price exists. Each row's `description` carries the published price per second or per request where there is one. Voices are the model's own: a speech or conversation model accepts the voice names its maker publishes, and the managed aliases pick a voice when none is named.

## Errors

| Status | Code                     | When                                                                                                                                                                                                                                                                      |
| ------ | ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `402`  | `insufficient_credits`   | The prepaid balance is empty. Nothing is sent to the provider.                                                                                                                                                                                                            |
| `404`  | `not_found`              | `file_id` names no file of yours; `GET` names no transcription of yours.                                                                                                                                                                                                  |
| `400`  | `validation_failed`      | Both or neither of `file_id`/`url`; a `url` at a private host, over 100 MiB or that does not answer `2xx`; `text` over 4 096 characters; a `voice` the model does not offer; a `model` the catalogue does not have; audio the model cannot read. `param` names the field. |
| `429`  | `rate_limited`           | The provider is throttling. Retry after a moment with the same `Idempotency-Key`.                                                                                                                                                                                         |
| `501`  | `feature_not_configured` | This deploy has no audio provider configured, or its account can no longer be billed.                                                                                                                                                                                     |
| `502`  | `provider_error`         | The provider failed. Retry with the same `Idempotency-Key`.                                                                                                                                                                                                               |
