Claude Media
Skillsの生成ファイルをダウンロードする4ステップ

Skillsの生成ファイルをダウンロードする4ステップ

Skillsがコード実行環境で作ったExcel・PowerPoint・PDFは、応答に埋め込まれずfile_idだけが返ります。Files APIで実体を取り出すまでの4ステップを扱います。

Messages APIのSkillsにExcelファイルの作成を頼んでも、応答にファイルの中身は入っていません。返るのはfile_idという識別子だけです。実体のバイト列を手に入れるには、Files APIへ別リクエストを送る必要があります。この記事では、file_idを取り出してから実ファイルを保存するまでの4ステップと、症状から原因をたどる切り分けを扱います。

なぜ応答にファイルがそのまま入らないのか

Skillsはコード実行ツールの上で動きます。コンテナ内のコードがファイルを作り、拾われるのはコマンド終了時に$OUTPUT_DIR直下に残っているものだけです。bash_code_executionを1回呼ぶたびに、$OUTPUT_DIRは新しい空のディレクトリとして用意されます。拾われたファイルのfile_idが、応答のcontentリストに載ります。

前提はAPIキーと、コード実行ツール(code_execution_20250825)を有効にしたリクエストです。Skillsはコード実行ツールを必要とするため、モデルはコード実行ツールの対応モデル一覧から選びます。Skillはcontainer.skillsに指定します。

{
  "model": "claude-opus-5-5",
  "max_tokens": 4096,
  "container": {
    "skills": [
      { "type": "anthropic", "skill_id": "xlsx", "version": "latest" }
    ]
  },
  "messages": [
    { "role": "user", "content": "Create an Excel file with a simple budget spreadsheet" }
  ],
  "tools": [
    { "type": "code_execution_20250825", "name": "code_execution" }
  ]
}

type・skill_id・versionの書式は、Anthropic製Skillとカスタムskillで次のように違います。

項目Anthropic製SkillカスタムSkill
typeAnthropic製Skill"anthropic"カスタムSkill"custom"
skill_idAnthropic製Skillxlsx / pptx / docx / pdfのような短い名前カスタムSkillskill_01AbCdEfGhIjKlMnOpQrStUvのような生成ID
versionAnthropic製Skill日付形式(20251013など)かlatestカスタムSkillskver_01AbCdEfGhIjKlMnOpQrStUv形式のIDかlatest
管理Anthropic製SkillAnthropicが用意・保守カスタムSkillSkills APIで自分のワークスペースにアップロード

リクエストの形は共通で、container.skillsには1リクエストあたり最大20個まで指定できます。ファイルの取り出し方も、どちらのSkillでも変わりません。

手順

生成ファイルを手元に保存するまで

  1. 1

    Skillでファイルを作る

    上のリクエストを送ります。

  2. 2

    応答からfile_idを取り出す

    作成されたファイルごとに1件ずつ入っています。

  3. 3

    メタデータでファイル名を得る

    GET /v1/files/{file_id}のfilenameを使い、拡張子込みの保存名をコード側で決め打ちしないようにします。

  4. 4

    Files APIで実体を保存する

    GET /v1/files/{file_id}/contentのバイト列を、手順3で得たファイル名で書き出します。

ステップ1〜2: file_idを応答から取り出す

応答のJSONは入れ子が3段あります。jqではselectでブロックの型を絞りながら、file_idまで辿ります。

FILE_ID=$(echo "$RESPONSE" | jq -r '
  .content[]
  | select(.type=="bash_code_execution_tool_result")
  | .content
  | select(.type=="bash_code_execution_result")
  | .content[]
  | select(.file_id)
  | .file_id
')

Python SDKなら、レスポンスのcontentをループして同じ構造を辿ります。

def extract_file_ids(response):
    file_ids = []
    for item in response.content:
        if item.type == "bash_code_execution_tool_result":
            content_item = item.content
            if content_item.type == "bash_code_execution_result":
                for file in content_item.content:
                    file_ids.append(file.file_id)
    return file_ids

file_idが返らない・ダウンロードが通らないときの切り分け

詰まったときに見る場所は、症状ごとにほぼ決まっています。

症状原因切り分け方
file_idが0件しか返らない原因ファイルが$OUTPUT_DIR直下にない切り分け方作ったファイルを$OUTPUT_DIRへコピーし、同じコマンドでlsするよう指示する
content取得が404になる原因ファイルをdeleteした、file_idが違う・別のワークスペースのもの、または自分でexpires_in_secondsを付けたファイルがexpires_atを過ぎた切り分け方メタデータを取得し、404なら削除済みか別ID。200でexpires_atが過去なら期限切れ
ダウンロードが400になる原因アップロードしたファイルのfile_idを指定している切り分け方メタデータのdownloadableがfalseか確認する
アップロードが413・400になる原因1ファイル500MBか、組織1TBの上限を超えた切り分け方サイズと使用量を確認し、不要なファイルをdeleteする

1行目の対処は、コマンドの最後にlsを入れておくと、その出力でファイルが拾われたかを確認できます。応答のcontentリストはClaudeからは見えないためです。

python /tmp/make_report.py && cp /tmp/report.pdf "$OUTPUT_DIR/" && ls "$OUTPUT_DIR"

$OUTPUT_DIRの外に作ってしまったファイルも、コンテナの中には残っています。同じコンテナを再利用して、$OUTPUT_DIRへのコピーを頼む方法があります。

コンテナは約5分操作がないとチェックポイントされ、作成から30日以内ならIDを付けたリクエストで復元されます。応答のcontainerオブジェクトにあるexpires_atは、それより短い更新され続ける値で、30日の期限は示しません。この値をコンテナの寿命やファイルの期限と読み違えないようにします。

ステップ3〜4: メタデータ取得とFiles APIダウンロード

# ステップ3: メタデータからファイル名を取得
FILENAME=$(curl "https://api.anthropic.com/v1/files/$FILE_ID" \
  -H "x-api-key: $ANTHROPIC_API_KEY" \
  -H "anthropic-version: 2023-06-01" | jq -r '.filename')
 
# ステップ4: Files APIで実体をダウンロード
curl "https://api.anthropic.com/v1/files/$FILE_ID/content" \
  -H "x-api-key: $ANTHROPIC_API_KEY" \
  -H "anthropic-version: 2023-06-01" \
  --output "$FILENAME"

公式のSkillsガイドには、curlやSDKのほかにantコマンド(Anthropic公式のCLI)の例もあります。応答のJSONをjqで辿る代わりに、--transformのGJSONパスでfile_idを直接取り出す書き方です。

FILE_ID=$(ant messages create \
  --transform 'content.#.content.content.#.file_id|@flatten|0' \
  --raw-output <<'YAML'
model: claude-opus-5-5
max_tokens: 4096
container:
  skills:
    - type: anthropic
      skill_id: xlsx
      version: latest
messages:
  - role: user
    content: Create an Excel file with a simple budget spreadsheet
tools:
  - type: code_execution_20250825
    name: code_execution
YAML
)
 
FILENAME=$(ant files retrieve-metadata --file-id "$FILE_ID" --transform filename --raw-output)
ant files download --file-id "$FILE_ID" --output "$FILENAME" > /dev/null

@flatten|0は、複数のファイルが返った場合に先頭の1件だけを使う指定です。複数ファイルを保存するなら、SDKでループを書くほうが素直です。

Python SDKではretrieve_metadataとdownloadという2つのメソッドに対応します。

for file_id in extract_file_ids(response):
    file_metadata = client.files.retrieve_metadata(file_id=file_id)
    file_content = client.files.download(file_id=file_id)
    file_content.write_to_file(file_metadata.filename)

TypeScriptでは、response.contentをループしてブロックの型を判定する書き方になります。ステップ2〜4を1つのループにまとめられます。

import { writeFile } from "node:fs/promises";
 
// ステップ2: 応答からfile_idを抽出
const fileIds: string[] = [];
for (const block of response.content) {
  if (
    block.type === "bash_code_execution_tool_result" &&
    block.content.type === "bash_code_execution_result"
  ) {
    for (const outputBlock of block.content.content) {
      fileIds.push(outputBlock.file_id);
    }
  }
}
 
// ステップ3〜4: メタデータ取得とダウンロードを1件ずつ実行
for (const fileId of fileIds) {
  const fileMetadata = await client.files.retrieveMetadata(fileId);
  const fileResponse = await client.files.download(fileId);
  await writeFile(fileMetadata.filename, Buffer.from(await fileResponse.arrayBuffer()));
}

Files API全体の使い方はClaude Files APIでPDFを処理する方法で扱っています。ファイル関連のAPI呼び出しは、1分あたり約500リクエストが上限です。大量の生成ファイルを1件ずつ取りに行くバッチ処理では、この上限が効いてきます。

組織でCompliance APIを有効にしている場合、Claude APIキーかClaude ConsoleからのFiles API操作は、Activity Feedに記録されます。ダウンロード(GET /v1/files/{file_id}/content)はplatform_file_content_downloadedとして残り、一覧の取得とメタデータの取得は記録されません。

この記事のPython例はclient.filesを使っています。client.beta.filesという名前空間もあり、Python SDK 1.2.0以降では、こちらもベータ用ヘッダーを送らずに同じ形のレスポンスを返します。

ダウンロードした画像・動画・音声はコンテナ内のものと一致しない

Claude APIでは、コード実行環境で作られた対応形式のファイルに、Files APIでダウンロードする時点でC2PA(Coalition for Content Provenance and Authenticity)のContent Credentialsが付きます。対象は画像のPNG・JPEG・GIF・WebP・TIFF・HEIC・AVIF・SVG、動画のMP4・MOV、音声のMP3・WAV・FLAC・M4Aです。Skillsが作ったファイルも含まれます。

署名付きのマニフェストがファイルのメタデータに埋め込まれます。発行者はAnthropicです。記録されるのはタイムスタンプと、「Claudeが利用者の依頼でこのファイルを提供し、内容を作成または変更した可能性がある」という趣旨の説明です。マニフェストに利用者・組織・リクエストの情報は記録されません。

見た目は変わりませんが数キロバイト増えるため、ダウンロードしたファイルのサイズとチェックサムはコンテナ内の元ファイルと一致しません。生成直後のファイルをハッシュで突合する検証を組むなら、この差を織り込む必要があります。

テキストファイル・PDF・Officeドキュメント(Excel・PowerPoint・Word)は署名の対応形式ではないため、署名されません。この記事で使ったExcelの例も対象外です。リクエストやレスポンスの扱いを変える必要はありません。

署名の確認には、オープンソースのコマンドラインツールc2patoolなど、C2PA対応のツールを使います。再エンコード・形式変換・スクリーンショット・メタデータを削除するツールを通すと署名は消えます。署名が無いことは、Claudeで作っていない証拠にはなりません。

Files APIが使えない環境がある

ステップ3〜4で使うFiles APIは、提供面が限られます。Claude APIとClaude Platform on AWSでは一般提供(GA)です。Amazon BedrockとGoogle Cloudでは使えません。Microsoft Foundryでは、Anthropic側でホストされるデプロイメントが必要です。ゼロデータリテンション(ZDR)の対象にもなりません。

BedrockやGoogle Cloud経由で呼んでいる場合、この記事のステップ3〜4はそのままでは使えません。

Skillへ入力ファイルを渡す場合

逆方向、つまりSkillに処理させたいファイル(元データのExcelなど)を渡す場合は、先にFiles APIへアップロードします。そのIDをリクエストのcontainer_uploadブロックに入れます。

{
  "container": {
    "skills": [{ "type": "anthropic", "skill_id": "xlsx", "version": "latest" }]
  },
  "messages": [
    {
      "role": "user",
      "content": [
        { "type": "text", "text": "このシートを集計して" },
        { "type": "container_upload", "file_id": "file_011CNha8iCJcU1wXNR6q4V8w" }
      ]
    }
  ]
}

同じFiles APIのIDでも、出どころでできることが違います。

くらべる

file_idの出どころと、できること

出力

Skillが作ったファイル

メタデータのdownloadableがtrueで、/contentからバイト列を取得できます。

入力

自分でアップロードしたファイル

downloadableはfalseです。container_uploadで渡す用途に限られます。

ダウンロードした後、生成ファイルはいつまで残るか

コード実行ツールがFiles APIに作ったファイルは、明示的に削除するまで残ります。コンテナ自体は作成から30日で期限切れになりますが、これはコンテナの話です。Files API上のファイルとは別の寿命です。

アップロード時にexpires_in_seconds(3,600秒から7,776,000秒=90日)を指定したファイルは、expires_atが設定されます。この値は一度決めると変更できません。期限が来た(またはdeleteした)ファイルは、/contentが404を返します。メタデータは期限切れから最大30日間、expires_atが過去のまま読め、listにも載ります。

実行のたびにファイルを作るSkillsの使い方では、保存済みのファイルを定期的に消さないと容量が減っていきます。回収はlistで一覧を見て、不要なものをdeleteする2手順です。

for file in client.files.list():
    print(f"{file.filename} - {file.created_at}")
 
client.files.delete(file_id=file_id)

antならant files list --transform '{filename,created_at}' --format yamlとant files delete --file-id "$FILE_ID"が同じ操作です。

期限切れで消えたファイルは、容量の計算から外れます。ただし、内容は安全性の確認のために限られた期間保持されうるため、期限は即時削除の保証ではありません。deleteしたファイルも、Anthropicのデータ保持ポリシーに従って削除されます。

まとめ

保存先をBedrockやGoogle Cloud経由で組んでいるなら、ステップ3〜4の前にFiles APIが使えるかを確かめるのが先です。画像・動画・音声の整合性をハッシュで検証する場合は、署名で増えた数キロバイト分を見込んでおきます。Skills自体の全体像はAgent Skillsとは何か、コード実行環境の制約はコード実行ツールが無料になる条件にあります。

この記事を共有:XはてブLinkedIn