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

# Dictation API

> Transcribe short audio clips and get an LLM-rewritten transcript alongside the verbatim one in a single HTTP call.

## Overview

Send an audio clip in a single HTTP call and get back the verbatim transcript together with an LLM-rewritten version of it.

The rewrite is on by default: with no configuration, it removes disfluencies and leaves every other word exactly as spoken. Set `llm_instruction` to reshape the transcript into something else instead — a chart note, a booking confirmation, or whatever shape you describe. The verbatim transcript always comes back alongside the rewrite.

Dictation accepts up to 120 seconds of audio per call. You can send the audio
after the recording finishes, or upload it as it is captured so most of the
transfer happens while the user is still speaking — see [Uploading while
recording](#uploading-while-recording).

<Tabs groupId="language">
  <Tab language="python-sdk" title="Python SDK" default>
    ```python theme={null}
    import assemblyai as aai

    aai.settings.api_key = "<YOUR_API_KEY>"

    result = aai.DictationTranscriber().transcribe_live("clip.wav")

    print(result.text)  # verbatim transcript
    print(result.llm_response)  # default cleanup rewrite
    print(result.final_text)  # the rewrite, falling back to the transcript
    ```
  </Tab>

  <Tab language="python" title="Python">
    ```python theme={null}
    import requests

    with open("clip.wav", "rb") as f:
        audio = f.read()

    response = requests.post(
        "https://dictation.assemblyai.com/v1/transcribe/live",
        headers={"Authorization": "<YOUR_API_KEY>"},
        files={
            # `config` first, and always present — `{}` means "no settings".
            "config": (None, "{}", "application/json"),
            "audio": ("clip.wav", audio, "audio/wav"),
        },
        timeout=90,
    )
    response.raise_for_status()
    result = response.json()

    print(result["text"])  # verbatim transcript
    print(result["llm_response"])  # default cleanup rewrite
    ```
  </Tab>

  <Tab language="javascript" title="JavaScript">
    ```javascript theme={null}
    import { readFileSync } from "fs";

    const audio = readFileSync("clip.wav");
    const form = new FormData();
    // `config` first, and always present — `{}` means "no settings".
    form.append("config", new Blob(["{}"], { type: "application/json" }));
    form.append("audio", new Blob([audio], { type: "audio/wav" }), "clip.wav");

    const response = await fetch("https://dictation.assemblyai.com/v1/transcribe/live", {
      method: "POST",
      headers: { Authorization: "<YOUR_API_KEY>" },
      body: form,
    });

    if (!response.ok) {
      const error = await response.json();
      throw new Error(error.error || error.detail);
    }

    const result = await response.json();
    console.log(result.text); // verbatim transcript
    console.log(result.llm_response); // default cleanup rewrite
    ```
  </Tab>

  <Tab language="curl" title="cURL">
    ```bash theme={null}
    curl -X POST https://dictation.assemblyai.com/v1/transcribe/live \
      -H 'Authorization: <YOUR_API_KEY>' \
      -F 'config={};type=application/json' \
      -F 'audio=@clip.wav;type=audio/wav'
    ```
  </Tab>
</Tabs>

<Note>
  Dictation is a separate service from Sync, Pre-recorded, and Streaming STT.
  It has its own hostname (`dictation.assemblyai.com`) and its own request
  shape. The [Python SDK](https://github.com/AssemblyAI/assemblyai-python-sdk)
  wraps it as `DictationTranscriber` from version 1.5.1; in every other
  language, call it over HTTP.
</Note>

## Before you begin

To call the Dictation API, you need:

* **An API key** — browse to [API Keys](https://www.assemblyai.com/dashboard/home) in your dashboard and copy your key.
* **An audio clip** in WAV format, or raw 16-bit PCM. Maximum 120 seconds.
* **A client** — either the AssemblyAI Python SDK (`pip install "assemblyai>=1.5.1"`), or any HTTP client: cURL, Python 3.8+ with `requests` or `httpx`, or Node.js 18+ with `fetch`.

## Endpoint

```
POST https://dictation.assemblyai.com/v1/transcribe/live
```

Send the parts in order — `config` first, then `audio`. The endpoint reads the
body as it arrives, so you can start the request while the user is still
speaking and upload the audio as it is captured; see [Uploading while
recording](#uploading-while-recording).

`/v1/transcribe/stream` is the path this endpoint shipped under and still
reaches the same handler. There is no unversioned alias.

## Authentication

Pass your AssemblyAI API key in the `Authorization` header as the raw key, with no `Bearer` prefix:

```
Authorization: <YOUR_API_KEY>
```

<Note>
  An invalid API key returns `404 Not Found` with `{"detail": "Invalid API
      key"}`, not `401`. Treat any `404` from this endpoint as an auth failure.
</Note>

## Request

The body is `multipart/form-data` with two parts, in this order:

| Part                | Content                                                                                                                                                             |
| ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `config` (required) | A JSON object (Content-Type `application/json`). See [Config parameters](#config-parameters). Send `{}` to transcribe with defaults, including the default rewrite. |
| `audio` (required)  | The audio bytes. Set the part's Content-Type to `audio/wav` for WAV or `audio/pcm` for raw 16-bit PCM. Maximum 120 seconds of audio.                                |

Config comes first, and is required, because the server starts transcribing
the audio as it arrives and cannot begin without it. An `audio` part that
arrives before `config`, or a request with no `config` part at all, is
rejected with `400`.

**WAV and raw PCM only.** Compressed formats — MP3, M4A, FLAC, OGG, WebM —
are rejected with `415`.

### Config parameters

Every field except `llm_instruction` controls transcription. `llm_instruction` customizes the transcript rewrite, which is applied by default — see [Rewriting the transcript](#rewriting-the-transcript).

| Field             | Type             | Meaning                                                                                                                                                                                                                                                                                                                                                                               |
| ----------------- | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `sample_rate`     | integer          | Sample rate of the audio. Required for raw PCM (for example `16000`). Ignored for WAV — the sample rate is read from the file header.                                                                                                                                                                                                                                                 |
| `channels`        | integer          | Channel count. Required for raw PCM.                                                                                                                                                                                                                                                                                                                                                  |
| `language_codes`  | array of strings | Language(s) of the audio as ISO codes. Supported: `en`, `es`, `de`, `fr`, `it`, `pt`, `tr`, `nl`, `sv`, `no`, `da`, `fi`, `hi`, `vi`, `ar`, `he`, `ja`, `ur`, `zh`. Defaults to `["en"]`.                                                                                                                                                                                             |
| `stt_prompt`      | string           | Context for the transcription: a description of what the audio is about, such as `"A doctor dictating a patient visit note."` (max 6000 characters). It describes the situation rather than instructing the model, and is prepended to the base transcription prompt, which always applies. Also accepted as `prompt` — send one or the other, or the request is rejected with `400`. |
| `keyterms_prompt` | array of strings | Terms to bias transcription toward, such as names or jargon (max 100 terms / 8000 characters total). Same parameter name as the Streaming and Pre-recorded APIs. The legacy names `keyterms` and `word_boost` are also accepted — send only one of the three, or the request is rejected with `400`.                                                                                  |
| `llm_instruction` | string           | Plain-English description of the rewrite you want (max 2048 characters). Replaces the default cleanup task; omit it to keep the default. See [Rewriting the transcript](#rewriting-the-transcript).                                                                                                                                                                                   |

Over HTTP, unknown fields are forwarded to the transcription engine as-is, so transcription parameters added in the future work without dictation-side changes.

<Note>
  `DictationConfig` in the Python SDK is stricter: it accepts exactly the
  fields in the table above and rejects anything else with a validation error,
  rather than passing it through. That turns a typo, or a Sync-only option
  used by mistake, into an error instead of a setting that quietly does
  nothing. To send a field the SDK does not know about yet, build the `config`
  part over HTTP.
</Note>

### Rewriting the transcript

The rewrite is applied by default. Omitting `llm_instruction` — or the whole `config` part — runs the default cleanup task, which removes disfluencies only (filler sounds and phrases, false starts, stammered repeats). Every kept word, its spelling, and its punctuation stay exactly as spoken.

To customize the rewrite, set `llm_instruction` to a plain-English description of the task you want (max 2048 characters). It replaces the default cleanup task:

```json theme={null}
{ "llm_instruction": "Remove filler words and fix punctuation." }
```

The rewrite never replaces the transcription. `text` is always the verbatim transcript, and the rewritten version arrives separately in `llm_response`.

See [Transcript rewriting](/docs/dictation/transcript-rewriting) for how to write a good instruction, what the service enforces for you, and why dictated commands are never carried out.

## Uploading while recording

The endpoint transcribes the audio it has while the rest is still arriving, so
a client that uploads during the recording gets its transcript sooner. What the
user waits for after they stop speaking is the last stretch of audio rather
than the whole clip. The saving grows with clip length; on a clip of a few
seconds there is little uploaded-but-unprocessed audio to save.

Three rules apply to a chunked upload:

* The `config` part must arrive **before** the first audio byte.
* Don't let the connection go silent for long stretches mid-body — an
  abandoned upload is timed out rather than held open.
* A chunked body can't be replayed. Keep the audio in memory if you want to
  retry a failed request.

Raw PCM is the easiest format to stream, since it needs no container header —
declare `sample_rate` and `channels` in `config` and send frames as they come
off the microphone.

The Python SDK does the framing for you. `transcribe_live()` takes an iterator
of audio chunks, and `open_live()` takes audio pushed in from a callback — a
microphone library, a WebRTC track, a telephony media stream:

<Tabs groupId="dictation-live">
  <Tab title="Pull — an iterator of chunks" default>
    ```python theme={null}
    import assemblyai as aai

    aai.settings.api_key = "<YOUR_API_KEY>"

    config = aai.DictationConfig(
        sample_rate=16000,
        channels=1,
        llm_instruction="Remove filler words and tidy the punctuation.",
    )


    def record():
        """Your microphone loop, yielding 16-bit PCM bytes."""
        while recording:
            yield stream.read(4096)


    result = aai.DictationTranscriber().transcribe_live(record(), config)

    print(result.text)
    print(result.llm_response)
    ```
  </Tab>

  <Tab title="Push — audio from a callback">
    ```python theme={null}
    import assemblyai as aai
    import sounddevice as sd

    aai.settings.api_key = "<YOUR_API_KEY>"

    config = aai.DictationConfig(sample_rate=16000, channels=1)

    with aai.DictationTranscriber() as transcriber:
        with transcriber.open_live(config) as session:
            stream = sd.RawInputStream(
                samplerate=16000,
                channels=1,
                dtype="int16",
                callback=lambda data, *_: session.write(bytes(data)),
            )
            with stream:
                input("Dictating, press Enter to stop... ")

        print(session.result().final_text)
    ```
  </Tab>
</Tabs>

`AsyncDictationTranscriber` is the asyncio counterpart, with the same two
methods. Call `transcriber.warm()` when you know audio is coming — as the user
reaches for the record button — to pay the DNS, TCP and TLS setup before the
first byte rather than in front of it.

Without the SDK, frame the multipart body yourself:

```python expandable theme={null}
import json
import requests

BOUNDARY = "----dictation-example"
CONFIG = {
    "sample_rate": 16000,
    "channels": 1,
    "llm_instruction": "Remove filler words and tidy the punctuation.",
}


def multipart_body(frames):
    """Yield the config part, then each audio frame as it is captured."""
    yield (
        f"--{BOUNDARY}\r\n"
        'Content-Disposition: form-data; name="config"\r\n'
        "Content-Type: application/json\r\n\r\n"
        f"{json.dumps(CONFIG)}\r\n"
        f"--{BOUNDARY}\r\n"
        'Content-Disposition: form-data; name="audio"; filename="audio"\r\n'
        "Content-Type: audio/pcm\r\n\r\n"
    ).encode()
    for frame in frames:
        yield frame
    yield f"\r\n--{BOUNDARY}--\r\n".encode()


# `record()` is your microphone loop, yielding 16-bit PCM bytes.
response = requests.post(
    "https://dictation.assemblyai.com/v1/transcribe/live",
    headers={
        "Authorization": "<YOUR_API_KEY>",
        "Content-Type": f"multipart/form-data; boundary={BOUNDARY}",
    },
    data=multipart_body(record()),
    timeout=90,
)
response.raise_for_status()
result = response.json()

print(result["text"])
print(result["llm_response"])
```

Passing a generator to `data=` makes `requests` send the body with chunked
transfer encoding, which is what lets the upload start before the audio is
complete. A body with a `Content-Length` is also accepted, and still streams —
the server does not wait for the full body before it begins.

## Response

A successful call returns `200` with JSON:

| Field               | Type             | Meaning                                                 |
| ------------------- | ---------------- | ------------------------------------------------------- |
| `text`              | string           | The verbatim transcript. Never altered by the LLM.      |
| `words`             | array            | Per-word objects `{ text, confidence }`.                |
| `confidence`        | number           | Overall transcription confidence, 0–1.                  |
| `llm_response`      | string or `null` | The rewritten text. `null` when the rewrite failed.     |
| `llm_error`         | string or `null` | `"timeout"` or `"error"` when the rewrite failed.       |
| `audio_duration_ms` | number           | Duration of the submitted audio.                        |
| `session_id`        | string           | Request identifier. Include it when reporting problems. |
| `request_time_ms`   | number           | Total server-side processing time.                      |
| `sync_time_ms`      | number           | Transcription portion of `request_time_ms`.             |

<Note>
  Rewrites are best-effort. A rewrite failure still returns `200` with the
  transcription. If `llm_response` is `null` and `text` is present, use
  `text`. Never treat a non-`null` `llm_error` as a failed request.
</Note>

## Errors

Most errors return `{"error": "...", "error_code": "..."}`. The ones relayed
from the transcription service, an invalid API key (`404`) and an unsupported
audio format (`415`), use a `{"status", "title", "detail"}` body instead, so
read both shapes. Note that an invalid API key returns `404` rather than `401`.

Set the HTTP client timeout to 90 seconds. Typical short clips respond in under
one second. The rewrite has a 5-second internal deadline, after which the
response returns with `llm_error: "timeout"` and the transcription intact.

See [Error handling](/docs/dictation/error-handling) for the full status code table
and retry guidance.

## Examples

The knobs do different jobs. `stt_prompt` and `keyterms_prompt` steer the ASR model while it writes the transcript down — the first describes the situation, the second lists the exact terms to expect. `llm_instruction` tells the LLM what to do with that transcript *after* it is written.

### Clinical dictation

A doctor dictating a visit note. `stt_prompt` tells the model what kind of audio this is, `keyterms_prompt` pins the exact drug names, and `llm_instruction` turns the spoken rambling into a chart-ready note.

<Tabs groupId="language">
  <Tab language="python-sdk" title="Python SDK" default>
    ```python expandable theme={null}
    import assemblyai as aai

    aai.settings.api_key = "<YOUR_API_KEY>"

    config = aai.DictationConfig(
        stt_prompt="A doctor dictating a patient visit note.",
        keyterms_prompt=["amoxicillin", "lisinopril", "metoprolol"],
        llm_instruction=(
            "Remove filler words and rewrite as a concise clinical chart note."
        ),
    )

    result = aai.DictationTranscriber().transcribe_live("clip.wav", config)

    transcript = result.text
    rewrite = result.final_text  # the rewrite, falling back to the transcript
    ```
  </Tab>

  <Tab language="python" title="Python">
    ```python expandable theme={null}
    import json
    import requests

    config = {
        "stt_prompt": "A doctor dictating a patient visit note.",
        "keyterms_prompt": ["amoxicillin", "lisinopril", "metoprolol"],
        "llm_instruction": (
            "Remove filler words and rewrite as a concise clinical chart note."
        ),
    }

    with open("clip.wav", "rb") as f:
        audio = f.read()

    response = requests.post(
        "https://dictation.assemblyai.com/v1/transcribe/live",
        headers={"Authorization": "<YOUR_API_KEY>"},
        files={
            "config": (None, json.dumps(config), "application/json"),
            "audio": ("clip.wav", audio, "audio/wav"),
        },
        timeout=90,
    )
    response.raise_for_status()
    result = response.json()

    transcript = result["text"]
    rewrite = result["llm_response"] or transcript  # fall back on rewrite failure
    ```
  </Tab>

  <Tab language="javascript" title="JavaScript">
    ```javascript expandable theme={null}
    import { readFileSync } from "fs";

    const config = {
      stt_prompt: "A doctor dictating a patient visit note.",
      keyterms_prompt: ["amoxicillin", "lisinopril", "metoprolol"],
      llm_instruction:
        "Remove filler words and rewrite as a concise clinical chart note.",
    };

    const audio = readFileSync("clip.wav");
    const form = new FormData();
    form.append(
      "config",
      new Blob([JSON.stringify(config)], { type: "application/json" }),
    );
    form.append("audio", new Blob([audio], { type: "audio/wav" }), "clip.wav");

    const response = await fetch("https://dictation.assemblyai.com/v1/transcribe/live", {
      method: "POST",
      headers: { Authorization: "<YOUR_API_KEY>" },
      body: form,
    });

    if (!response.ok) {
      const error = await response.json();
      throw new Error(error.error || error.detail);
    }

    const result = await response.json();
    const transcript = result.text;
    const rewrite = result.llm_response ?? transcript; // fall back on rewrite failure
    ```
  </Tab>

  <Tab language="curl" title="cURL">
    ```bash theme={null}
    curl -X POST https://dictation.assemblyai.com/v1/transcribe/live \
      -H 'Authorization: <YOUR_API_KEY>' \
      -F 'config={"stt_prompt": "A doctor dictating a patient visit note.", "keyterms_prompt": ["amoxicillin", "lisinopril", "metoprolol"], "llm_instruction": "Remove filler words and rewrite as a concise clinical chart note."};type=application/json' \
      -F 'audio=@clip.wav;type=audio/wav'
    ```
  </Tab>
</Tabs>

### Travel booking summary

A travel agent dictating a client booking summary. `keyterms_prompt` pins the specific destination and airline names, and `llm_instruction` reshapes the transcript into a confirmation the client can read.

<Tabs groupId="language">
  <Tab language="python-sdk" title="Python SDK" default>
    ```python expandable theme={null}
    import assemblyai as aai

    aai.settings.api_key = "<YOUR_API_KEY>"

    config = aai.DictationConfig(
        keyterms_prompt=["Reykjavik", "Cancun", "Lufthansa"],
        llm_instruction=(
            "Remove filler words and rewrite as a short, friendly booking "
            "confirmation addressed to the client."
        ),
    )

    result = aai.DictationTranscriber().transcribe_live("clip.wav", config)

    transcript = result.text
    rewrite = result.final_text  # the rewrite, falling back to the transcript
    ```
  </Tab>

  <Tab language="python" title="Python">
    ```python expandable theme={null}
    import json
    import requests

    config = {
        "keyterms_prompt": ["Reykjavik", "Cancun", "Lufthansa"],
        "llm_instruction": (
            "Remove filler words and rewrite as a short, friendly booking "
            "confirmation addressed to the client."
        ),
    }

    with open("clip.wav", "rb") as f:
        audio = f.read()

    response = requests.post(
        "https://dictation.assemblyai.com/v1/transcribe/live",
        headers={"Authorization": "<YOUR_API_KEY>"},
        files={
            "config": (None, json.dumps(config), "application/json"),
            "audio": ("clip.wav", audio, "audio/wav"),
        },
        timeout=90,
    )
    response.raise_for_status()
    result = response.json()

    transcript = result["text"]
    rewrite = result["llm_response"] or transcript
    ```
  </Tab>

  <Tab language="javascript" title="JavaScript">
    ```javascript expandable theme={null}
    import { readFileSync } from "fs";

    const config = {
      keyterms_prompt: ["Reykjavik", "Cancun", "Lufthansa"],
      llm_instruction:
        "Remove filler words and rewrite as a short, friendly booking confirmation addressed to the client.",
    };

    const audio = readFileSync("clip.wav");
    const form = new FormData();
    form.append(
      "config",
      new Blob([JSON.stringify(config)], { type: "application/json" }),
    );
    form.append("audio", new Blob([audio], { type: "audio/wav" }), "clip.wav");

    const response = await fetch("https://dictation.assemblyai.com/v1/transcribe/live", {
      method: "POST",
      headers: { Authorization: "<YOUR_API_KEY>" },
      body: form,
    });

    if (!response.ok) {
      const error = await response.json();
      throw new Error(error.error || error.detail);
    }

    const result = await response.json();
    const transcript = result.text;
    const rewrite = result.llm_response ?? transcript;
    ```
  </Tab>

  <Tab language="curl" title="cURL">
    ```bash theme={null}
    curl -X POST https://dictation.assemblyai.com/v1/transcribe/live \
      -H 'Authorization: <YOUR_API_KEY>' \
      -F 'config={"keyterms_prompt": ["Reykjavik", "Cancun", "Lufthansa"], "llm_instruction": "Remove filler words and rewrite as a short, friendly booking confirmation addressed to the client."};type=application/json' \
      -F 'audio=@clip.wav;type=audio/wav'
    ```
  </Tab>
</Tabs>

## Next steps

* [Transcript rewriting](/docs/dictation/transcript-rewriting) — reshape the transcript with `llm_instruction`
* [Prompting and keyterms](/docs/dictation/prompting-and-keyterms) — steer the transcript with context and exact terms
* [Language selection](/docs/dictation/language-selection) — transcribe in one or more of 19 languages
* [Audio requirements](/docs/dictation/audio-requirements) — duration, sample width, and format constraints
* [Error handling](/docs/dictation/error-handling) — status codes and retry guidance
* [Cloud endpoints & data residency](/docs/dictation/endpoints-and-data-zones) — global routing and the US/EU data zones
* [Connection pre-warming](/docs/dictation/connection-pre-warming) — take the TLS handshake off the critical path
* [API reference](/docs/api-reference/dictation-api/transcribe-live) — the full request and response schema
