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

# Async transcription

> Submit recordings as durable jobs, poll each transcript, and cancel unfinished work.

Use [`POST /v1/asr/batches`](https://docs.chariot.in/api-reference/endpoint/asr-batches-create) when a recording needs background processing or you want to submit several recordings together. A batch can contain a single file. Each input becomes an independent transcription with its own ID, status, result, and billing.

> [!NOTE]
>
> **Beta.** File acceptance constraints can change. Handle submission errors and per-file failures instead of assuming every recording can be accepted. CSV uploads and intermediate-result streaming are not supported.

For a complete transcript in the same HTTP request, use [File transcription](https://docs.chariot.in/guides/speech-to-text/file-transcription.md). For live audio, use the [WebSocket](https://docs.chariot.in/guides/speech-to-text/websocket.md).

## Batch limits

These are the current default limits for the beta async API. Deployment configuration can change the duration, byte-size, and processing-time limits. The synchronous [file API](https://docs.chariot.in/guides/speech-to-text/file-transcription.md) has separate, smaller limits.

| Limit | Value |
| - | - |
| Directly uploaded files per request | **1** |
| Total inputs per batch | **100**, including the optional upload and URL inputs |
| Audio duration per recording | **3 hours** |
| Original file size per recording | **1 GiB** (1,073,741,824 bytes), for uploads and URL downloads |
| Complete multipart request size | **2 GiB** (2,147,483,648 bytes) |
| JSON body or multipart `manifest` size | **512 KiB** (524,288 bytes) |
| `audio_url` length | **4,096 characters**, including signed URL parameters |
| URL download timeout per attempt | **5 minutes** |

Duration is checked per recording, not across the batch. The request-size allowance includes form fields and framing; it does not increase the per-file limit. Acceptance does not guarantee that processing will complete within the job deadline.

## Upload a recording

Send `multipart/form-data` with a JSON string in `manifest` and one `files` attachment. Use `file_index: 0` to reference that attachment exactly once. The field retains the name `files`, but accepts at most one file per request. Give each input a `custom_id` that is unique within the batch so you can match results to your own records.

```bash theme={null}
curl --fail-with-body https://api.chariot.in/v1/asr/batches \
  -H "chariotai-api-key: $CHARIOT_API_KEY" \
  -H "Idempotency-Key: recordings-2026-10-26-001" \
  --form-string 'manifest={"inputs":[{"custom_id":"interview-1","file_index":0,"include_timestamps":true}]}' \
  -F "files=@interview-1.wav"
```

Let your HTTP client set the multipart boundary. Send encoded media files, such as WAV, MP3, FLAC, or an MP4 containing an audio track. You do not need to pace uploads in real time or convert them into WebSocket audio frames.

The API receives the uploaded file and stores the original before accepting the batch. The async worker then retrieves it and processes it in the background. A `202` response confirms acceptance, not successful transcription. Once accepted, a job continues if you close the tab or disconnect. Save the batch ID and poll from a later request.

## Submit media URLs

For multiple recordings, send JSON with an `audio_url` for each input, up to 100 inputs:

```bash theme={null}
curl --fail-with-body https://api.chariot.in/v1/asr/batches \
  -H "chariotai-api-key: $CHARIOT_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: remote-recording-001" \
  --data '{"inputs":[{"custom_id":"meeting-1","audio_url":"https://media.example.com/meeting.flac","language":"en","include_timestamps":true}]}'
```

Use direct public HTTP or HTTPS media URLs. Signed URLs are supported. Keep each URL valid while the job waits and downloads, including retries. The async worker downloads the media after acceptance. A URL submission does not download the media before returning `202`. Private-network URLs and URLs requiring custom authentication headers cannot be fetched.

Each input must have exactly one of `audio_url` or `file_index`. You can mix up to 99 URL inputs with one upload in a multipart manifest. The `files` field is optional when every input uses `audio_url`. JSON-only submissions use URLs.

## Choose settings per file

| Field | Default | Meaning |
| - | - | - |
| `custom_id` | Required | Your identifier, unique within this batch. |
| `language` | `none` | Requested transcription language, or automatic language detection. |
| `model_type` | `chariot-1-asr-realtime` | Public ASR model, shared with the file and WebSocket APIs. |
| `mode` | `high_accuracy` | Async files support only `high_accuracy`. An explicit `balanced` value rejects the submission with `422`. |
| `style` | `codemix` | `codemix` or `native`. |
| `include_timestamps` | `false` | Request estimated word timestamps in the final result. |

See [Languages, modes, and styles](https://docs.chariot.in/guides/speech-to-text/modes-and-languages.md). Settings belong to each input, so files in the same batch can use different settings.

## Retry a submission safely

Supply an `Idempotency-Key` and save it with your request. If the submission times out before you receive its ID, retry with the same key, manifest, and file bytes. A matching retry returns the existing batch and the same input-to-transcription ID mapping. Reusing that key for different inputs or settings returns `409`.

A new key creates a new batch and can lead to another charge. Idempotency recovers submission results; it does not restart a failed file. To retry a terminal failed file, submit it as a new batch.

## Poll the batch and its files

An accepted batch looks like this:

```json theme={null}
{
  "id": "1f4b3eed-2fdf-4495-b988-2b7145127058",
  "status": "queued",
  "created_at": "2026-10-26T14:47:48Z",
  "started_at": null,
  "finished_at": null,
  "cancel_requested_at": null,
  "counts": { "total": 1, "queued": 1 },
  "items_url": "/v1/asr/history?batch_id=1f4b3eed-2fdf-4495-b988-2b7145127058",
  "items": [{ "id": "9cb6f4c5-b2bf-4d75-96e3-716f6f87d994", "custom_id": "meeting-1" }],
  "webhook_signing_secret": null
}
```

Work can start immediately, so the initial status may already be `processing`.

**1. Save the batch ID**

Poll [`GET /v1/asr/batches/{batch_id}`](https://docs.chariot.in/api-reference/endpoint/asr-batch) for aggregate status and counts. Missing status keys in `counts` mean zero files have that status.

**2. Find each transcription ID**

Save the `items` mapping returned by submission. Each entry contains your `custom_id` and its transcription `id`, in submission order. You can also fetch `items_url` on the same API host with your API key, following `next_cursor` until null. History is ordered newest first, so correlate by `custom_id`, not position.

**3. Read each result**

Poll [`GET /v1/asr/transcriptions/{transcription_id}`](https://docs.chariot.in/api-reference/endpoint/asr-transcription) until `completed`, `failed`, or `cancelled`. Poll every few seconds with backoff on transient errors. A successful polling HTTP response does not itself mean the transcription succeeded.

```bash theme={null}
curl --fail-with-body \
  "https://api.chariot.in/v1/asr/history?batch_id=$BATCH_ID" \
  -H "chariotai-api-key: $CHARIOT_API_KEY"

curl --fail-with-body \
  "https://api.chariot.in/v1/asr/transcriptions/$TRANSCRIPTION_ID" \
  -H "chariotai-api-key: $CHARIOT_API_KEY"
```

A completed transcription includes these fields (other metadata omitted):

```json theme={null}
{
  "id": "9cb6f4c5-b2bf-4d75-96e3-716f6f87d994",
  "batch_id": "1f4b3eed-2fdf-4495-b988-2b7145127058",
  "custom_id": "meeting-1",
  "source": "file",
  "execution": "async",
  "input_type": "url",
  "status": "completed",
  "language": "none",
  "include_timestamps": true,
  "input_audio": { "format": "wav", "sample_rate_hz": 48000 },
  "duration_seconds": 2.4,
  "billing": { "credits": 4, "status": "settled" },
  "error": null,
  "result": {
    "text": "Hello world",
    "words": [
      { "text": "Hello", "start": 0.25, "end": 0.75 },
      { "text": "world", "start": 0.8, "end": 1.3 }
    ],
    "is_final": true
  }
}
```

Word times are estimates in seconds from the beginning of the recording. `words` is empty when timestamps were not requested; it can be `null` when timing data is unavailable. Confidence scores and speaker labels are not returned.

### Status and progress

| File status | Meaning |
| - | - |
| `queued` | Waiting to run or retry. Acceptance does not reserve a concurrency slot. |
| `processing` | Preparing or transcribing the recording. |
| `cancelling` | Cancellation was requested and is being applied. |
| `completed` | Final result is available. |
| `failed` | Processing ended unsuccessfully. Inspect `error` and `billing`. |
| `cancelled` | Cancellation finished. Any saved partial text is not final. |

Batches use `queued`, `processing`, and `cancelling` while active. Their terminal statuses are:

| Batch status | Meaning |
| - | - |
| `completed` | Every file completed. |
| `partially_completed` | Some files completed; others failed or were cancelled. |
| `cancelled` | Every file was cancelled. |
| `failed` | No file completed, and at least one failed. |

`progress.stage` describes the current activity, such as `downloading`, `decoding`, `sending_audio`, or `transcribing`. `progress.stage_percent` describes that stage only and can be `null`. For example, sending 100% of the audio does not mean transcription has finished. It is not a processed-duration counter or a completion ETA.

Polling may return a partial `result.text` with `is_final: false`, or `result: null` before text is available. Treat each partial result as a snapshot, not a delta to append. Partial snapshots are not guaranteed and can change. Use the final result after `completed` for downstream work.

## Cancel unfinished work

Cancel one transcription or the unfinished files in a batch:

```bash theme={null}
curl --fail-with-body -X POST \
  "https://api.chariot.in/v1/asr/transcriptions/$TRANSCRIPTION_ID/cancel" \
  -H "chariotai-api-key: $CHARIOT_API_KEY"

curl --fail-with-body -X POST \
  "https://api.chariot.in/v1/asr/batches/$BATCH_ID/cancel" \
  -H "chariotai-api-key: $CHARIOT_API_KEY"
```

Cancellation returns `202` with current resource details. An already terminal batch or transcription stays unchanged, without an extra refund or terminal event. Continue polling until terminal. A running file can complete before cancellation takes effect. Finished files remain unchanged. These routes do not cancel synchronous file requests or live WebSocket sessions.

Async transcription shares workspace ASR concurrency with live and synchronous file requests. Queued files wait for capacity. A slot is held while a file is being prepared and transcribed.

Completed files are billed by audio duration. A file cancelled or failed after charging receives a full refund; a file stopped before charging has no charge. Saved partial text is not currently billed separately. Check `billing.status` for the settled outcome. `billing.credits` is the current net debit, so a full refund leaves zero credits. Cancellation does not refund files that already completed.

## Handle submission and processing errors

| Status | Action |
| - | - |
| `401`, `403` | Check your API key and ASR access. |
| `409` | Check whether an idempotency key was reused for a different request. |
| `400` | Check multipart formatting and form-part size limits. |
| `413` | Attach at most one file and reduce the upload or manifest size. |
| `415` | Use JSON or multipart form data. |
| `422` | Correct the manifest, input references, URL, or transcription settings. |
| `429` | Pending work is at capacity. Retry with backoff. |
| `503` | The requested service, mode, or webhook delivery is temporarily unavailable. |

### Submission rejection versus per-file failure

| Problem | Outcome |
| - | - |
| More than one attached file | The entire submission is rejected with `413`; no jobs are created. |
| A URL string exceeds 4,096 characters, an invalid input setting, or an invalid file reference | The entire submission is rejected with `422`; no jobs are created. |
| An uploaded file exceeds the byte-size limit | The entire submission is rejected with `413`; no jobs are created. |
| Accepted URL points to audio longer than 3 hours, exceeds the download byte limit, expires, or cannot be downloaded | Only that transcription fails. Other inputs continue processing. |
| An accepted upload exceeds the audio-duration limit | Only that transcription fails during worker validation. |

For example, nine successful recordings and one over-duration recording produce a `partially_completed` batch. Duration validation happens before charging that recording. The failed transcription currently reports `error.code: "invalid_input"`.

After acceptance, download, decoding, credit, and transcription failures are reported on the individual transcription. Inspect its `status`, `error`, and `billing`; do not infer success from `202`. Application error responses follow [Errors & troubleshooting](https://docs.chariot.in/guides/resources/errors.md).

You can replace frequent polling with [completion webhooks](https://docs.chariot.in/guides/speech-to-text/webhooks.md), and use [unified history](https://docs.chariot.in/guides/speech-to-text/history.md) to find jobs later.

### Processing error codes

| Code | Meaning |
| - | - |
| `invalid_input` | The input could not be downloaded, decoded, or accepted. |
| `access_unavailable` | ASR is unavailable for the workspace. |
| `insufficient_credits` | Available credits could not cover the file. |
| `processing_unavailable` | Processing failed after the available retries. |
| `deadline_exceeded` | The job deadline expired. |
| `attempts_exhausted` | The maximum processing attempts were exhausted. |
| `transcription_failed` | A synchronous or historical file failure without a more specific public code. |

Error messages are safe descriptions, not stable identifiers. Handle unknown future codes as failures and retain the transcription ID for support. See [Transcription history](https://docs.chariot.in/guides/speech-to-text/history.md) for the complete resource field semantics.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.