DocsUploads

Uploads

Send files up to 95 MB in one request, or up to 2 GB in resumable chunks.

Upload audio or video files for AI speech-to-text. For uploads, source can only be auto or stt. See Sources and options for language and diarize.

Supported files

The file name must end in one of these extensions: mp3, wav, m4a, flac, ogg, opus, mp4, mov, webm or mkv. Anything else returns 422 unsupported_media. The file must contain an audio track; a file without one fails with unsupported_media.

LimitValue
One-shot upload (multipart/form-data)95 MB
Chunked upload2 GB (2,000,000,000 bytes). Your plan may lower this.
Open chunked uploads per team20

A file over the limit returns 413 media_too_large with max_bytes in the error.

One-shot upload

For files up to 95 MB, send one multipart/form-data request to POST /v1/transcriptions. The file goes in a part named file. These optional form fields are also accepted: language, diarize (true or false), source (auto or stt) and webhook_url.

curl -X POST "https://api.transcript1.com/v1/transcriptions?wait=60" \
  -H "Authorization: Bearer $TRANSCRIPT1_API_KEY" \
  -F [email protected] \
  -F language=en \
  -F diarize=true

The response contains a transcription object with source.platform set to upload. The status code is 202 while processing and 200 at a final status, as for a YouTube URL. An empty file returns 422 invalid_request.

Chunked upload

Send large files in separate chunks so you can resume an interrupted upload. Follow these four steps.

1. Create the upload

Send the file's name and size as JSON. mime_type is optional. Add an Idempotency-Key header so you can safely retry the create:

curl -X POST "https://api.transcript1.com/v1/transcriptions" \
  -H "Authorization: Bearer $TRANSCRIPT1_API_KEY" \
  -H "Idempotency-Key: interview-2026-10-13" \
  -H "Content-Type: application/json" \
  -d '{"upload":{"filename":"interview.m4a","size":48210334,"mime_type":"audio/mp4"},"language":"auto","diarize":true}'

The response is 201 with the status awaiting_upload and an upload block:

{
  "id": "tr_8fK2Lm0PqRsTuVwXyZaBcD",
  "object": "transcription",
  "status": "awaiting_upload",
  "upload": {
    "chunk_url_template": "https://api.transcript1.com/v1/transcriptions/tr_8fK2Lm0PqRsTuVwXyZaBcD/upload/chunks/{index}",
    "headers": { "X-Upload-Token": "t1u_..." },
    "expires_at": "2026-10-13T10:30:00.000Z",
    "chunks": [
      { "index": 0, "offset": 0, "size": 8388608 },
      { "index": 1, "offset": 8388608, "size": 8388608 }
    ]
  }
}

The server plans the chunks, about 8 MiB each (8,388,608 bytes), and returns the list in chunks (shortened above). The X-Upload-Token is a secret for this one upload and is valid for 1 hour (expires_at).

2. Send each chunk

Send every chunk with PUT to chunk_url_template, replacing {index} with the chunk's index. The body is the bytes from offset for size bytes, sent as application/octet-stream. The body must be exactly the planned size. Authenticate with the X-Upload-Token header; no Authorization header is needed.

curl -X PUT "https://api.transcript1.com/v1/transcriptions/tr_8fK2Lm0PqRsTuVwXyZaBcD/upload/chunks/0" \
  -H "X-Upload-Token: t1u_..." \
  -H "Content-Type: application/octet-stream" \
  --data-binary @chunk-0.bin

Each chunk returns { "object": "upload_chunk", "index": 0, "size": 8388608 }. Sending a chunk again replaces it. A wrong size or an index outside the plan returns 422 invalid_request. A missing or expired token returns 401 unauthorized.

3. Resume if interrupted

GET /v1/transcriptions/:id/upload lists what arrived:

{
  "object": "upload",
  "status": "awaiting_upload",
  "total_bytes": 48210334,
  "uploaded_bytes": 16777216,
  "received_chunks": [0, 1],
  "chunks": [ { "index": 0, "offset": 0, "size": 8388608 } ],
  "expires_at": "2026-10-13T10:30:00.000Z"
}

Send only the chunks missing from received_chunks. If the token expired, repeat the create request from step 1 with the same Idempotency-Key and parameters. The response returns the same transcription with a new upload token.

4. Complete the upload

curl -X POST "https://api.transcript1.com/v1/transcriptions/tr_8fK2Lm0PqRsTuVwXyZaBcD/upload/complete" \
  -H "Authorization: Bearer $TRANSCRIPT1_API_KEY"

POST /v1/transcriptions/:id/upload/complete checks that every planned chunk arrived with its planned size, assembles the file and queues the transcription. It returns 202 with the transcription object. If chunks are missing it returns 409 upload_incomplete with missing_chunks, a list of the chunk indexes to send again. If the upload was already completed or canceled it returns 409 invalid_state. From here, poll with GET /v1/transcriptions/:id?wait=60 or use a webhook.

Complete Python example

import os
import requests
 
API = "https://api.transcript1.com"
auth = {"Authorization": f"Bearer {os.environ['TRANSCRIPT1_API_KEY']}"}
path = "interview.m4a"
 
created = requests.post(
    f"{API}/v1/transcriptions",
    headers={**auth, "Idempotency-Key": "interview-2026-10-13"},
    json={"upload": {"filename": os.path.basename(path), "size": os.path.getsize(path)}},
)
created.raise_for_status()
transcription = created.json()
upload = transcription["upload"]
 
with open(path, "rb") as file:
    for chunk in upload["chunks"]:
        file.seek(chunk["offset"])
        url = upload["chunk_url_template"].replace("{index}", str(chunk["index"]))
        response = requests.put(
            url,
            data=file.read(chunk["size"]),
            headers={**upload["headers"], "Content-Type": "application/octet-stream"},
        )
        response.raise_for_status()
 
done = requests.post(f"{API}/v1/transcriptions/{transcription['id']}/upload/complete", headers=auth)
done.raise_for_status()
print(done.json()["status"])

Limits and retention

  • A team can have at most 20 uploads open (status awaiting_upload) at once. Creating more returns 429 too_many_active_jobs with a Retry-After header.
  • An upload that is not completed within 24 hours of being created is canceled, and its chunks are deleted.
  • Your media is deleted 24 hours after the transcription reaches a final status (completed, failed or canceled). The transcript itself is kept until you delete it with DELETE /v1/transcriptions/:id.
  • Failed and canceled transcriptions are never charged. Minutes are charged per media minute, rounded up, once the file's duration is known. See Billing and limits.

Updated October 14, 2026