# ファイルのアップロード

> 95MBまでは1リクエストで送れます。2GBまでは分割して送れ、通信が途切れても途中から再開できます。

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

手元の音声・動画ファイルをアップロードすると、AIが音声を文字起こしします。`source`に指定できるのは`auto`と`stt`だけです。言語と話者分離の指定には`language`と`diarize`を使います。詳しくは[対応リンクと文字起こしの設定](https://transcript1.com/ja/docs/sources-and-options)を確認してください。

## 使えるファイルとサイズ上限

ファイル名の拡張子を`mp3`、`wav`、`m4a`、`flac`、`ogg`、`opus`、`mp4`、`mov`、`webm`、`mkv`のいずれかにしてください。それ以外の拡張子には`422 unsupported_media`を返します。対応する拡張子でも、音声トラックがなければ`unsupported_media`で失敗します。

| 送信方法・制限項目 | 上限 |
|---|---|
| 1リクエストで送信（`multipart/form-data`） | 95MB |
| 分割アップロード | 2GB（2,000,000,000バイト）です。プランによっては上限が下がります。 |
| チームで同時に保持できる未完了の分割アップロード | 20件 |

サイズ上限を超えると`413 media_too_large`を返します。エラーの`max_bytes`で、適用される上限を確認できます。

## 95MBまでのファイルを送る

`POST /v1/transcriptions`へ、`multipart/form-data`で送ります。ファイルは`file`という名前のパートに入れてください。95MBまでなら、このリクエストだけで送信できます。

フォームには任意で`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`です。動画リンクと同じく、HTTPステータスは処理中なら`202`、最終ステータスに達していれば`200`になります。空のファイルを送ると`422 invalid_request`を返します。

## 大きなファイルを分割して送る

大きなファイルはチャンクに分けて送ります。中断しても、届いていないチャンクだけを再送できます。次の4つの手順で実装します。

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

HTTPステータスは`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. チャンクをPUTで送る

送信先は`chunk_url_template`の`{index}`を、そのチャンクの`index`で置き換えたURLです。ファイルの`offset`の位置から`size`バイトを読み取り、`PUT`のボディに`application/octet-stream`として送ります。送るバイト数は、指定された`size`と完全に一致させてください。

認証には`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`でポーリングするか、[Webhook](https://transcript1.com/ja/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/ja/docs/billing-and-limits)で確認できます。
