Files APIでダウンロードできない理由 — アップロードしたファイル
Files APIにアップロードしたファイルはdownloadableがfalseのままで、ダウンロードを試みると400エラーになります。ダウンロードできるのはSkillsとコード実行ツールが生成したファイルだけです。
Files APIへ自分でアップロードしたファイルは、あとから同じAPIでダウンロードできません。アップロード直後のレスポンスに含まれるdownloadableフィールドは常にfalseで、GET /v1/files/{file_id}/contentを叩くと400エラーが返ります。ダウンロードできるのは、Skillsやコード実行ツールがコンテナ内で作ったファイルだけです。
downloadableフィールドが何を表すか
Files APIへのアップロードが成功すると、レスポンスにfile_idと一緒にdownloadableという真偽値が返ります。
{
"id": "file_011CNha8iCJcU1wXNR6q4V8w",
"type": "file",
"filename": "budget.xlsx",
"mime_type": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
"size_bytes": 1024000,
"created_at": "2026-09-01T12:00:00Z",
"downloadable": false
}downloadableがtrueになるのは、Skillやコード実行ツールがコンテナの中で生成したファイルだけです。自分でアップロードしたファイルは、アップロードした時点でこの値がfalseに固定されます。一覧取得(list)やメタデータ取得(retrieve_metadata)でも同じ値が返るので、ダウンロードを試す前にこの1フィールドを見るだけで判定できます。
ダウンロードしようとすると何が起きるか
downloadable: falseのファイルに対してGET /v1/files/{file_id}/contentを呼ぶと、レスポンスは400エラーになります。
curl "https://api.anthropic.com/v1/files/$FILE_ID/content" \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01"$FILE_IDが自分でアップロードしたファイルを指していると、このリクエストは400を返します。エラーメッセージ自体は「not downloadable」系の内容になり、ファイルが存在しない、またはアクセス権がない場合の404とは区別されます。公式ドキュメントのエラー一覧では、この挙動を次のように定義しています。
| エラー | 発生条件 |
|---|---|
| Not downloadable(400) | 発生条件アップロードしたファイル(downloadable: false)に対してcontentエンドポイントを呼んだ |
| Invalid file type(400) | 発生条件ファイルの種類とコンテンツブロックの型が一致しない(画像ファイルをdocumentブロックに使う等) |
| Storage limit exceeded(400) | 発生条件組織のストレージ上限(1TB)に達している |
つまり「ファイルが見つからない」のではなく「そのファイルはそもそもダウンロード対象ではない」という区別が、ステータスコードではなくdownloadableフィールドとエラーメッセージの中身で表現されています。
なぜこの非対称な設計になっているか
Files APIは、アップロードと生成物の取得という2つの用途を1つのエンドポイント群で扱っています。アップロードしたファイルは、Messages APIのリクエストでfile_idとして参照するための入力であり、あなたの手元に元のバイト列がすでに存在します。もう一度ダウンロードする必要そのものがない、という前提です。
一方、Skillやコード実行ツールが作ったファイル(生成したExcel・グラフ画像・PDFなど)は、コンテナの中でしか存在しません。応答にはバイト列が直接埋め込まれず、file_idという参照だけが返るので、実体を手に入れるにはFiles APIからのダウンロードが唯一の経路になります。アップロード用のfile_idとダウンロード用のfile_idは、同じ形式の識別子を使いながら片方向にしか機能しないという非対称性が、この設計の中心にあります。
監査ログにも非対称性が表れる
Compliance APIを有効にしている組織では、Files APIの操作がActivity Feedに記録されます。アップロード(POST /v1/files)はplatform_file_uploaded、ダウンロード(GET /v1/files/{file_id}/content)はplatform_file_content_downloaded、削除はplatform_file_deletedというイベント種別で残ります。アップロードしたファイルはdownloadable: falseのため、そのファイルのplatform_file_content_downloadedイベントは原理的に発生しません。このイベントが記録されるのは、常にSkillやコード実行ツールが生成したファイルを取得したときだけです。監査ログを見てダウンロード操作の内訳を追うときは、この前提を踏まえておくと「アップロードしたファイルのダウンロード履歴がない」ことを異常として調べる無駄を避けられます。一覧取得やメタデータ取得(list / retrieve_metadata)自体はこのActivity Feedに記録されません。
生成ファイルだけに付くContent Credentials
ダウンロードできるファイルの側にも、アップロードファイルには無い挙動があります。コード実行ツールが生成した画像・動画・音声ファイル(対応フォーマットに限る)は、ダウンロード時にC2PA(Coalition for Content Provenance and Authenticity)規格のContent Credentialsが自動で埋め込まれます。この署名はAnthropicを発行者として記録するもので、ファイルの見た目やピクセル自体は変わりませんが、メタデータが数キロバイト分増えます。アップロードしたファイルはそもそもダウンロードの対象ではないため、この署名処理の対象にもなりません。ダウンロードできるかどうかの境界線が、C2PA署名が付くかどうかの境界線とも一致している形です。
ワークスペーススコープにも注意する
downloadableとは別の軸として、アップロードしたファイルはワークスペース全体からアクセス可能で、特定のエンドユーザーや会話に紐づいていません。そのワークスペースにアクセスできるAPIキーであれば、誰がアップロードしたファイルでも参照できます。エンドユーザーから受け取ったfile_idをそのまま信頼してはいけないのはこのためで、ファイルとユーザーの対応関係はアプリケーション側で管理する必要があります。分離が必要なファイルは、専用のワークスペースを切ってスコープするのが公式の推奨です。
400と404の使い分け — 「ダウンロード不可」と「期限切れ」は別のエラー
Files APIのダウンロードでは、似たエラーが2種類の別条件で返るので区別が必要です。
| ステータス | 発生条件 |
|---|---|
| 400(Not downloadable) | 発生条件ファイルは存在するが、アップロードしたファイルでdownloadable: false |
| 404(File not found) | 発生条件file_idが存在しない、アクセス権がない、または期限切れ(expires_atを過ぎた)ファイル |
アップロード時にexpires_in_seconds(1時間〜90日の範囲)を指定すると、そのファイルには有効期限が付きます。期限が来た後は、ダウンロード可能だったファイル(Skill生成物)であってもcontentエンドポイントは404を返すようになります。つまり400は「このファイルの種類ではダウンロードという操作が成立しない」、404は「そのfile_idは今アクセスできない」という別の意味を持ちます。エラーハンドリングを書くときは、この2つを同じ「取得失敗」として握りつぶさず、原因ごとに分岐させるのが安全です。
メタデータ(GET /v1/files/{file_id})だけは、期限切れから30日間はexpires_atが過去日時のまま読み取れます。この期間中は「ファイル自体は記録に残っているがダウンロードはできない」状態になり、アップロードファイルのdownloadable: falseと外見上は似ますが、原因も返るステータスコードも異なります。
downloadableを見てから叩く実装
400エラーを個別にキャッチするより、ダウンロード前にdownloadableを確認しておくほうが、意図が読み取りやすいコードになります。
def try_download(client, file_id: str):
metadata = client.files.retrieve_metadata(file_id=file_id)
if not metadata.downloadable:
print(f"{file_id} はアップロードされたファイルのためダウンロードできません")
return None
content = client.files.download(file_id=file_id)
content.write_to_file(metadata.filename)
return metadata.filenameretrieve_metadataはダウンロードの成否に関わらず呼べるため、Skillsの出力とユーザーがアップロードしたファイルのfile_idが混在するパイプラインでは、ダウンロードを試す前にこの分岐を挟むと400エラーの発生自体を避けられます。
アップロード済みファイルの中身をもう一度取り出したいとき
「アップロードしたファイルをAPI経由でダウンロードする」という操作自体が存在しないため、これができないことを前提に設計する必要があります。実務での対処は次の2つです。
- 手元の原本を保持する: アップロード前のファイルを自分の環境に残しておく。Files APIは再アップロード不要にするための参照であって、バックアップストレージではありません
- コード実行ツール経由で加工結果を取り出す: アップロードしたファイルを
container_uploadでコード実行環境に渡し、コード実行ツールに読み込ませて何らかの処理(変換・抽出・再保存)をさせると、その出力ファイルはdownloadable: trueになります。元のファイルそのものではなく、コンテナが新しく書き出したファイルとして扱われるためです
2つ目の経路は、たとえば「アップロードしたCSVをExcelに変換して受け取りたい」というような、入力と出力のフォーマットが変わるケースで実際に使われている流れです。単に同じファイルをもう一度受け取りたいだけなら、素直に手元の原本を使うほうが早くなります。
組織内に大量のアップロードファイルと生成ファイルが混在している場合、listエンドポイントの結果をdownloadableの値でフィルタすれば、「ダウンロードして回収できるファイル」と「原本を別途保持しておくしかないファイル」を機械的に仕分けられます。ストレージ上限に近づいたときの棚卸しでは、この仕分けを先に済ませておくと、削除してよいものと保持し続けるべきものの判断が速くなります。
アップロードファイルと生成ファイルの違い
| アップロードしたファイル | Skill/コード実行ツールが生成したファイル | |
|---|---|---|
downloadable | アップロードしたファイル常にfalse | Skill/コード実行ツールが生成したファイルtrue |
| ダウンロード | アップロードしたファイル不可(400エラー) | Skill/コード実行ツールが生成したファイル可能(GET /v1/files/{file_id}/content) |
file_idの使い道 | アップロードしたファイルcontainer_upload等でリクエストへの入力に使う | Skill/コード実行ツールが生成したファイル応答のbash_code_execution_tool_resultから抽出して取得する |
| スコープ | アップロードしたファイルワークスペース全体 | Skill/コード実行ツールが生成したファイル同左 |
| ライフサイクル | アップロードしたファイルexpires_in_secondsを指定しない限り無期限 | Skill/コード実行ツールが生成したファイル同左(組織1TB・1ファイル500MBの上限も共通) |
ストレージ上限や有効期限の扱いは方向によらず共通で、Claude Files APIの容量上限とファイルライフサイクルで扱っています。file_idを外部から受け取って使う場合のセキュリティ上の注意点はClaude Files APIのfile_idセキュリティリスクと対策にまとめています。
まとめ
Files APIにアップロードしたファイルはdownloadable: falseが固定され、contentエンドポイントを叩くと400エラーになります。ダウンロードできるのはSkillやコード実行ツールがコンテナ内で生成したファイルだけで、この区別はAPIのエラーコードではなくdownloadableフィールドの値で事前に判定できます。アップロードは「もう一度データを送らなくていいようにする参照」であって、取り出し用のストレージではないという前提を踏まえてリクエストを組む必要があります。同じファイルをもう一度手に入れたいときは、手元の原本を使うか、コード実行ツールを経由して新しい生成ファイルとして受け取る形になります。