> ## 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.

# Uploading while recording

> Start the request before the speaker stops, so most of the utterance is uploaded by the time they finish.

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.

## Rules for a chunked upload

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.

## With the Python SDK

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

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.

## Related pages

* [Quickstart](/docs/dictation) — the single-call flow this builds on
* [Audio requirements](/docs/dictation/audio-requirements) — why raw PCM is the easiest format to stream
* [Connection pre-warming](/docs/dictation/connection-pre-warming) — take the handshake off the critical path too
