# 파일 업로드

> 95MB까지는 요청 한 번으로 보내세요. 더 큰 파일은 최대 2GB까지 청크로 나눠 올리고, 전송이 끊기면 이어서 업로드할 수 있어요.

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

보유한 오디오·영상 파일을 올리면 AI 음성 텍스트 변환(STT)으로 스크립트를 만들어요. 파일 업로드의 `source`는 `auto`와 `stt`만 허용해요. 언어와 화자 분리를 지정하는 `language`, `diarize`는 [지원 링크와 변환 옵션](https://transcript1.com/ko/docs/sources-and-options)에서 확인하세요.

## 파일 형식과 크기

파일 확장자는 `mp3`, `wav`, `m4a`, `flac`, `ogg`, `opus`, `mp4`, `mov`, `webm`, `mkv` 중 하나여야 해요. 다른 확장자는 `422 unsupported_media`를 반환해요. 지원 형식이라도 오디오 트랙이 없으면 `unsupported_media`로 실패해요.

| 항목 | 한도 |
|---|---|
| 단일 요청 업로드(`multipart/form-data`) | 95MB |
| 청크 업로드 | 2GB(2,000,000,000바이트). 요금제에 따라 한도가 더 작을 수 있어요. |
| 팀당 완료 전 청크 업로드 | 최대 20개 |

크기 한도를 넘으면 `413 media_too_large`를 반환해요. 오류 응답의 `max_bytes`에서 허용 크기를 확인하세요.

## 요청 한 번으로 업로드하기

95MB 이하의 파일은 `POST /v1/transcriptions`에 `multipart/form-data`로 보내세요. 파일을 넣을 파트 이름은 `file`이에요. 필요하면 폼 필드에 `language`, `diarize`(`true` 또는 `false`), `source`(`auto` 또는 `stt`), `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
```

응답으로 받는 변환 작업 객체에서 `source.platform`은 `upload`예요. 영상 링크를 보냈을 때처럼 처리 중에는 `202`, 작업이 끝나면 `200`을 반환해요. 빈 파일을 보내면 `422 invalid_request`를 반환해요.

## 큰 파일을 청크로 나눠 올리기

큰 파일은 청크 단위로 전송하세요. 업로드가 끊겨도 아직 도착하지 않은 청크만 다시 보내면 돼요. 아래 순서대로 진행하세요.

### 1. 업로드 예약하기

먼저 파일 이름과 크기를 JSON으로 보내 업로드를 예약하세요. `mime_type`은 생략할 수 있어요. 생성 요청을 재시도해도 작업이 중복되지 않도록 `Idempotency-Key` 헤더를 넣으세요.

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

`201` 응답에 `awaiting_upload` 상태의 작업과 전송에 필요한 `upload` 블록이 들어 있어요.

```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 }
    ]
  }
}
```

서버가 파일을 약 8MiB(8,388,608바이트)씩 나눠 `chunks` 목록으로 알려 줘요. 위 예제는 목록의 일부만 보여 줘요. `X-Upload-Token`은 해당 업로드에만 쓰는 비밀 토큰이에요. 유효 기간은 1시간이며 만료 시각은 `expires_at`에 있어요.

### 2. 청크 전송하기

`chunk_url_template`의 `{index}`를 청크의 `index`로 바꾼 주소에 `PUT` 요청을 보내세요. 파일의 `offset` 위치에서 `size` 바이트를 읽어 본문에 넣고, Content-Type은 `application/octet-stream`으로 지정하세요. 청크 크기는 서버가 지정한 값과 정확히 같아야 해요. `X-Upload-Token` 헤더로 인증하므로 `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
```

청크를 받으면 `{ "object": "upload_chunk", "index": 0, "size": 8388608 }` 형태로 응답해요. 같은 청크를 다시 보내면 기존 데이터를 교체해요. 크기나 인덱스가 서버의 목록과 맞지 않으면 `422 invalid_request`, 토큰이 없거나 만료됐으면 `401 unauthorized`를 반환해요.

### 3. 끊긴 업로드 이어서 올리기

`GET /v1/transcriptions/:id/upload`로 이미 도착한 청크를 확인하세요.

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

`received_chunks`에 없는 청크만 추가로 전송하세요. 토큰이 만료됐으면 1단계 요청을 같은 `Idempotency-Key`와 같은 파라미터로 다시 보내세요. 새 작업을 만들지 않고 기존 작업과 새 업로드 토큰을 반환해요.

### 4. 업로드를 마치고 변환 시작하기

```bash
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`를 호출하면 서버가 청크의 누락 여부와 크기를 확인해요. 모두 맞으면 파일을 합치고 변환 작업을 대기열에 넣은 뒤, 작업 객체와 함께 `202`를 반환해요. 빠진 청크가 있으면 `409 upload_incomplete`를 반환하고 `missing_chunks`에 다시 보낼 인덱스를 알려 줘요. 이미 업로드를 완료했거나 취소했다면 `409 invalid_state`를 반환해요.

업로드를 마친 뒤에는 `GET /v1/transcriptions/:id?wait=60`으로 상태를 조회하거나 [웹훅](https://transcript1.com/ko/docs/webhooks)으로 알림을 받으세요.

### 전체 흐름을 실행하는 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"])
```

## 업로드 한도와 파일 보관 기간

- 팀당 `awaiting_upload` 상태의 업로드는 동시에 20개까지 가능해요. 한도를 넘겨 생성하면 `429 too_many_active_jobs`와 `Retry-After` 헤더를 반환해요.
- 생성 후 24시간 안에 업로드를 완료하지 않으면 작업을 취소하고 청크를 삭제해요.
- 작업이 `completed`, `failed`, `canceled` 중 하나로 끝나면 24시간 뒤에 미디어 파일을 삭제해요. 스크립트는 `DELETE /v1/transcriptions/:id`로 직접 삭제할 때까지 남아요.
- 파일 재생 시간이 확인되면 분 단위로 올림해 크레딧을 차감해요. 작업이 실패하거나 취소되면 비용이 들지 않아요. 자세한 기준은 [요금제와 사용 한도](https://transcript1.com/ko/docs/billing-and-limits)를 확인하세요.
