はじめに / 対象と前提
Claude API で PDF や画像を扱うとき、毎リクエストで base64 に変換して送り直していないだろうか。自分は同じ 30 ページの PDF に対して質問を変えながら何度も投げる処理を書いていて、リクエストボディが毎回数 MB になるのが気になっていた。Files API を使うと「一度アップロードして file_id で参照する」形にでき、転送量とコードの見通しが大きく改善する。
この記事は以下を前提にする。
- 想定読者:Claude API(Messages API)を Python から叩いたことがある人
- 環境:Python 3.13 / anthropic SDK 1.x / モデルは
claude-opus-5 - Files API はベータ機能(ベータヘッダー
files-api-2025-04-14が必要)
TL;DR
-
client.beta.files.upload()でアップロードし、レスポンスのid(file_...)を content block のsource: {"type": "file", "file_id": ...}に渡すだけで使い回せる - ハマりどころは 3 つ:beta 指定はアップロードと Messages の両方に必要、ファイル種別と content block の型(document / image)を一致させる、自分でアップロードしたファイルは再ダウンロードできない
- ファイル操作自体は無料。本文で使った分だけ通常の入力トークンとして課金される
手順 / 動かし方
1. ファイルをアップロードする
import anthropic
client = anthropic.Anthropic()
uploaded = client.beta.files.upload(
file=("report.pdf", open("report.pdf", "rb"), "application/pdf"),
)
print(uploaded.id) # file_011CNha8iCJcU1wXNR6q4V8w のような ID
print(uploaded.size_bytes) # バイト数
file 引数は (ファイル名, バイナリ, MIME タイプ) のタプルのほか、pathlib.Path をそのまま渡す形でもよい。SDK が anthropic-beta: files-api-2025-04-14 ヘッダーを付けてくれる。
実行結果(抜粋):
file_011CNha8iCJcU1wXNR6q4V8w
1248733
2. file_id で Messages API から参照する
response = client.beta.messages.create(
model="claude-opus-5",
max_tokens=16000,
messages=[{
"role": "user",
"content": [
{"type": "text", "text": "このレポートの要点を3行でまとめて"},
{
"type": "document",
"source": {"type": "file", "file_id": uploaded.id},
},
],
}],
betas=["files-api-2025-04-14"],
)
print(next(b.text for b in response.content if b.type == "text"))
ポイントは client.messages.create ではなく client.beta.messages.create を使い、betas=["files-api-2025-04-14"] を渡すこと。質問を変えて 10 回投げても、アップロードは最初の 1 回だけで済む。
3. 一覧・削除
for f in client.beta.files.list(): # 全ページ自動で辿ってくれる
print(f.id, f.filename, f.size_bytes)
client.beta.files.delete(uploaded.id)
ファイルは明示的に削除するまで残る。上限は 1 ファイル 500 MB、組織全体で 100 GB。
ハマりどころ
その1:beta 指定を Messages 側に付け忘れて 400
アップロード側は SDK がヘッダーを自動で付けるので成功する。ところが参照側で普段の癖で client.messages.create(beta なし)を使うと、source: {"type": "file"} が未知の型として弾かれてエラーになる。
原因:beta ヘッダーはアップロードと、file_id を参照する Messages リクエストの両方に必要。片方だけでは動かない。
回避策:Files API を使う処理では Messages 呼び出しも client.beta.messages.create(..., betas=["files-api-2025-04-14"]) に統一する。自分は「upload は成功してるのに参照で落ちる」状態で 10 分ほど溶かした。
その2:content block の型とファイル種別の不一致で 400
PNG をアップロードして "type": "document" で参照したら 400 が返ってきた。
原因:content block の型はファイルの MIME タイプと対応している必要がある。PDF・テキスト系は document、画像(PNG/JPEG など)は image。file_id を渡す場所は同じ source でも、外側の型が違う。
回避策:アップロード時の MIME タイプを基準に分岐する。メタデータは client.beta.files.retrieve_metadata(file_id) の mime_type で後から確認できる。
{"type": "image", "source": {"type": "file", "file_id": image_file.id}}
その3:アップロードしたファイルは再ダウンロードできない
「アップロード済みならローカルのファイルを消してもいいか」と思って client.beta.files.download() を叩いたらエラーになった。
原因:ダウンロードできるのはコード実行ツールや Skills が生成したファイルだけで、自分がアップロードしたファイルは対象外という仕様。Files API はストレージサービスではなく、あくまで「Messages に渡すための置き場」と考えるのが正しい。
回避策:元ファイルは手元(または自前のストレージ)に必ず残す。逆に、コード実行ツールに CSV からグラフを生成させたケースでは、返ってきた file_id を download() して PNG を取り出せる。
背景・補足
課金は素直で、upload / list / delete などのファイル操作自体は無料。Messages で参照した時点で、その内容が通常の入力トークンとして数えられる。つまり Files API は「入力トークンを節約する機能」ではなく「転送とコード管理を楽にする機能」で、トークン側を削りたければプロンプトキャッシュと組み合わせることになる。
なお Amazon Bedrock / Google Vertex AI 経由では Files API は使えない(2026年9月時点)。マルチプラットフォーム構成のコードでは分岐が必要になる。
まとめ
- Files API は
upload()→file_id参照で、同じファイルの base64 再送をなくせる - beta 指定(
files-api-2025-04-14)はアップロードと Messages の両方に必要 - PDF は
document、画像はimage。block 型と MIME タイプを一致させる - 自分でアップロードしたファイルは再ダウンロード不可。元ファイルは手元に残す
- ファイル操作は無料、参照した内容は入力トークンとして課金