# Tải file lên

> File tối đa 95 MB gửi bằng một request. File lớn hơn, đến 2 GB, chia thành các chunk và tải tiếp được nếu kết nối bị ngắt.

Canonical: https://transcript1.com/vi/docs/uploads
Language: vi
Last updated: 2026-10-14

File audio và video tải lên luôn dùng AI để chuyển giọng nói thành văn bản. Chọn `source` là `auto` hoặc `stt`. Nếu cần đặt ngôn ngữ hay nhận diện người nói, xem cách dùng `language` và `diarize` trong [Link video và tùy chọn](https://transcript1.com/vi/docs/sources-and-options).

## Định dạng và dung lượng file

Kiểm tra đuôi file trước khi gửi: `mp3`, `wav`, `m4a`, `flac`, `ogg`, `opus`, `mp4`, `mov`, `webm` hoặc `mkv`. Đuôi khác sẽ nhận `422 unsupported_media`. File cũng phải có track audio; thiếu track này, job báo `unsupported_media`.

| Cách gửi và giới hạn | Mức tối đa |
|---|---|
| Một request `multipart/form-data` | 95 MB |
| Chia file thành các chunk | 2 GB (2.000.000.000 byte); gói của bạn có thể đặt giới hạn thấp hơn. |
| Phiên tải lên theo chunk chưa hoàn tất trong mỗi team | 20 |

Nếu file quá lớn, API trả `413 media_too_large`. Trường `max_bytes` trong lỗi cho biết dung lượng tối đa bạn được gửi.

## Gửi bằng một request

Gọi `POST /v1/transcriptions` với body `multipart/form-data` cho file tối đa 95 MB. Đặt phần chứa file là `file`. Bạn có thể thêm các trường form `language`, `diarize` (`true` hoặc `false`), `source` (`auto` hoặc `stt`) và `webhook_url`.

```bash
curl -X POST "https://api.transcript1.com/v1/transcriptions?wait=60" \
  -H "Authorization: Bearer $TRANSCRIPT1_API_KEY" \
  -F file=@interview.m4a \
  -F language=en \
  -F diarize=true
```

API trả object transcription có `source.platform` là `upload`. Giống với link video, mã HTTP là `202` nếu job đang xử lý và `200` nếu đã kết thúc. Gửi file rỗng sẽ nhận `422 invalid_request`.

## Chia file thành các chunk

Với file lớn, gửi từng chunk riêng biệt. Cách này cho phép tải tiếp phần còn thiếu nếu kết nối bị ngắt. Quy trình có bốn bước.

### 1. Tạo phiên tải lên

Gửi JSON chứa tên và dung lượng file; `mime_type` có thể bỏ qua. Kèm header `Idempotency-Key` để gửi lại request tạo phiên mà không tạo thêm job:

```bash
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}'
```

API trả `201`, trạng thái `awaiting_upload` và object `upload` chứa thông tin để gửi chunk:

```json
{
  "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 }
    ]
  }
}
```

Server quyết định cách chia file và trả danh sách `chunks`. Mỗi chunk khoảng 8 MiB (8.388.608 byte); ví dụ trên chỉ hiển thị một phần danh sách. Giữ kín `X-Upload-Token`: token chỉ dùng cho phiên tải lên này và hết hạn sau 1 giờ, tại `expires_at`.

### 2. Gửi từng chunk

Với từng chunk, thay `{index}` trong `chunk_url_template` bằng giá trị `index`, rồi gửi `PUT`. Đọc đúng `size` byte từ vị trí `offset` của file và dùng chúng làm body `application/octet-stream`. Không gửi thừa hoặc thiếu byte so với danh sách server trả về. Request này xác thực bằng `X-Upload-Token`, không cần `Authorization`.

```bash
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
```

Gửi thành công sẽ nhận `{ "object": "upload_chunk", "index": 0, "size": 8388608 }`. Bạn có thể gửi lại để ghi đè chunk đó. Sai dung lượng hoặc dùng index ngoài danh sách sẽ nhận `422 invalid_request`. Thiếu token hay token hết hạn sẽ nhận `401 unauthorized`.

### 3. Tải tiếp sau khi mất kết nối

Gọi `GET /v1/transcriptions/:id/upload` để kiểm tra server đã nhận những chunk nào:

```json
{
  "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"
}
```

Đối chiếu `received_chunks` và chỉ gửi những chunk chưa có trong danh sách. Nếu token hết hạn, gửi lại request ở bước 1, giữ nguyên `Idempotency-Key` và các tham số. API trả lại cùng job nhưng cấp token tải lên mới.

### 4. Ghép file và bắt đầu xử lý

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

Gọi `POST /v1/transcriptions/:id/upload/complete` sau khi gửi đủ chunk. Server kiểm tra số chunk và dung lượng từng chunk, ghép file rồi đưa job vào hàng đợi. Response là `202` kèm object transcription.

Nếu nhận `409 upload_incomplete`, đọc `missing_chunks` để biết các index cần gửi lại. Nếu phiên tải lên đã hoàn tất hoặc đã hủy, API trả `409 invalid_state`. Khi job đã vào hàng đợi, dùng `GET /v1/transcriptions/:id?wait=60` để chờ hoặc nhận thông báo qua [webhook](https://transcript1.com/vi/docs/webhooks).

### Toàn bộ quy trình bằng Python

```python
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"])
```

## Giới hạn và thời hạn lưu file

- Mỗi team có tối đa 20 phiên tải lên ở trạng thái `awaiting_upload` cùng lúc. Tạo thêm sẽ nhận `429 too_many_active_jobs` kèm `Retry-After`.
- Bạn có 24 giờ từ khi tạo phiên để tải xong. Quá thời hạn, hệ thống hủy phiên và xóa các chunk.
- Hệ thống xóa file audio hoặc video sau 24 giờ kể từ khi job chuyển sang `completed`, `failed` hoặc `canceled`. Transcript vẫn còn cho đến khi bạn gọi `DELETE /v1/transcriptions/:id`.
- Khi biết thời lượng file, hệ thống tính số phút sử dụng và làm tròn lên. Nếu job thất bại hoặc bạn hủy, bạn không mất phí. Xem [Thanh toán và giới hạn](https://transcript1.com/vi/docs/billing-and-limits).
