Claude Files API and Skills API Are GA: How to Migrate Off the Beta Headers

TL;DR
Claude Files API and Skills API are GA: drop two beta headers, add file expiry up to 90 days, and switch to page cursors. Type-checked TS and Python steps.
Last updated: October 17, 2026
Since August 19, 2026, the Claude Files API (/v1/files) and the Skills API (/v1/skills) are generally available on the Claude API, so you no longer need the files-api-2025-04-14 or skills-2025-10-02 beta headers - including on Messages requests that reference a file_id or load Skills through the container parameter. The verdict: write new code against client.files and client.skills now, but treat existing integrations as a small typed migration rather than a header delete, because dropping the header changes response shapes (pagination, expires_at, display_name, latest_version_id). The August 27 SDK releases (TypeScript 0.122.0, Python 1.2.0, Go 1.68.0, Java 2.59.0, Ruby 1.67.0, C# 12.44.0) moved client.beta.files and client.beta.skills to the GA shapes and renamed the container reference type BetaSkill to BetaContainerSkill.
If you want the concept first - what a Skill is and why progressive loading matters - read our Claude Skills deep dive. Note that its API section predates GA: it still shows the skills-2025-10-02 flag and an 8-Skill cap. This post is the current API follow-up.
What changed on August 19 and August 27#
| Date | Change | Source |
|---|---|---|
| May 22, 2025 | Files API enters public beta (files-api-2025-04-14) | Release notes |
| Oct 16, 2025 | Agent Skills enter beta (skills-2025-10-02) | Release notes |
| Aug 19, 2026 | Files API GA: no header on /v1/files or on Messages requests that reference a file. Header-less requests get expires_in_seconds on upload, expires_at on file objects, page/next_page pagination and an ids[] filter on list | Release notes |
| Aug 19, 2026 | Agent Skills and Skills API GA: no header on /v1/skills or on Messages requests that load Skills via container | Release notes |
| Aug 27, 2026 | SDK releases: client.beta.files / client.beta.skills stop sending beta headers and return GA shapes; client.beta.skills.delete() deletes a Skill with all its versions; BetaSkill (container reference) renamed BetaContainerSkill | Release notes |
Two details matter for planning. First, requests that still send either beta header keep working with the previous response format, so nothing breaks the day you upgrade. Second, the new features (file expiry, the ids[] filter, display_name, skver_ version IDs) only appear on header-less requests. You cannot mix and match.
On registries, @anthropic-ai/sdk 0.122.0 was published on 2026-08-27 and anthropic 1.2.0 on PyPI the same day. As of October 17, 2026 the current releases are @anthropic-ai/sdk 0.135.0 and anthropic 1.15.0, both from October 15.
How Files, Skills and the container fit together#
Three resources cooperate in a typical document-generation request:
- Files API stores inputs you upload and outputs Claude generates. You upload once and reference the file by
file_idafterwards. - Skills API stores versioned Skill bundles: a
SKILL.mdwith frontmatter, plus scripts and reference files. - Messages API with the code execution tool ties them together. Skills listed in
container.skillsare copied into the sandbox at/skills/{skill-name}/(the directory is the Skill name, not theskill_01...ID). Files attached with acontainer_uploadblock are preloaded into the container. Anything a command writes to the top level of$OUTPUT_DIRcomes back as afile_idin the tool result.
Claude only sees each Skill's name and description in the system prompt and reads the full instructions when it decides a Skill is relevant. That is why you can load many Skills without paying for all of their instructions on every turn.
%% title: Files and Skills in one request
flowchart LR
A[Your app] -->|POST /v1/files| F[Files API]
A -->|POST /v1/skills| S[Skills API]
A -->|messages.create + container| M[Messages API]
F -->|container_upload file_id| C[Code execution container]
S -->|mounted at /skills/name| C
M --> C
C -->|files in OUTPUT_DIR| O[file_id in tool result]
O -->|GET /v1/files/id/content| AThe Files API also works without Skills: PDFs and text/plain files go into document blocks, JPEG/PNG/GIF/WebP into image blocks, and datasets or other types go to the code execution tool through container_upload. .docx and .xlsx are not supported in document blocks; the Files API docs suggest converting them to plain text, or passing them to code execution via container_upload, where the Anthropic docx and xlsx Skills can also work with them.
Prerequisites#
- An API key from the Claude Console, exported as
ANTHROPIC_API_KEY. - An SDK at or after the August 27 releases:
@anthropic-ai/sdk>= 0.122.0 oranthropic>= 1.2.0. The examples below were type-checked against@anthropic-ai/sdk@0.135.0and compiled againstanthropic==1.15.0. - A model on the code execution compatibility list. The examples use
claude-opus-5-5. - The code execution tool. Current versions are
code_execution_20250825,code_execution_20260120andcode_execution_20260521, and none of them needs a beta header. Skills require this tool.
Platform note: the August 19 GA announcement itself names only the Claude API. Separately, the platform-support sections of the Files API and code execution docs list both as available on Claude Platform on AWS and Microsoft Foundry too, where Foundry requires a Hosted on Anthropic deployment.
Step 1: Upload a file with an expiry#
The headline Files feature at GA is expiration. Pass expires_in_seconds as a form field on upload and the file gets an expires_at timestamp. Rules from the docs:
- The value is an integer from 3,600 (1 hour) to 7,776,000 (90 days).
- It is set once at upload and cannot be changed later.
- Without it, the file lives until you delete it, and
expires_atisnull(on header-less requestsexpires_atis always present). - After expiry, downloading content returns 404 and a Messages request that references the file fails before inference. Metadata stays readable and the file keeps appearing in list responses for up to 30 days.
DELETEremoves an expired file's metadata immediately.
Here is the raw request, with no anthropic-beta header. The form field names follow the documented upload fields:
curl https://api.anthropic.com/v1/files \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-F file=@sales.csv \
-F expires_in_seconds=86400
The Content-Type on the uploaded file part is now optional; the API detects the type when you omit it. One thing that has not changed: files you upload have downloadable: false. Only files created by Skills or the code execution tool can be downloaded.
Step 2: Create a custom Skill and version it#
A custom Skill is a directory with a SKILL.md and whatever scripts or references it needs. If you have never written one, start with our guide to writing your first Claude Code skill - the same SKILL.md format uploads to the API.
Upload rules from the Skills guide:
SKILL.mdmust sit at the upload root, or at the top of a single enclosing folder. (Root placement is new with the GA shape.)- Total upload must be under 30 MB uncompressed.
name: up to 64 characters, lowercase letters, numbers and hyphens only, and it cannot contain the reserved wordsanthropicorclaude.description: non-empty, up to 1,024 characters, no XML tags.display_nameis optional, up to 255 characters, not unique, and derived from theSKILL.mdname when omitted.
A minimal bundle:
---
name: weekly-report
description: Builds the weekly sales report workbook from a CSV export. Use when asked for the weekly report.
---
# Weekly report
1. Load the CSV with pandas.
2. Run build.py to produce report.xlsx.
3. Save the workbook to $OUTPUT_DIR.
You can upload with the Python SDK's files_from_dir helper (shown in Step 3), with explicit file lists in TypeScript, or with the ant CLI: ant apply report_skill uploads a directory.
Versioning has two sharp edges:
- A new version is a complete snapshot, not a delta. Files you leave out of the new upload are not carried over, and the
SKILL.mdname must match the existing Skill. - Pin version IDs in production.
latestmeans any upload by anyone in your workspace immediately changes what production agents run. Pin theskver_...ID you tested, and roll forward deliberately.
Step 3: Load Skills in a Messages request and download outputs#
This TypeScript example does the whole loop: upload an expiring input, create a custom Skill, run a Messages request that loads both an Anthropic Skill and the custom one, download whatever the Skill wrote to $OUTPUT_DIR, then page through files with the GA cursor shape.
import Anthropic, { toFile } from "@anthropic-ai/sdk";
import { createReadStream } from "node:fs";
import { writeFile } from "node:fs/promises";
const client = new Anthropic(); // reads ANTHROPIC_API_KEY
// 1. Upload an input file that deletes itself after 24 hours.
const input = await client.files.upload({
file: createReadStream("sales.csv"),
expires_in_seconds: 86_400,
});
console.log(input.id, input.expires_at);
// 2. Upload a custom Skill (SKILL.md at the root or inside one enclosing folder).
const skill = await client.skills.create({
files: [
await toFile(createReadStream("report_skill/SKILL.md"), "report_skill/SKILL.md"),
await toFile(createReadStream("report_skill/build.py"), "report_skill/build.py"),
],
});
console.log(skill.id, skill.display_name, skill.latest_version_id, skill.source.type);
// 3. Load the Skill in a container and hand it the uploaded file.
const response = await client.messages.create({
model: "claude-opus-5-5",
max_tokens: 4096,
container: {
skills: [
{ type: "anthropic", skill_id: "xlsx", version: "latest" },
{ type: "custom", skill_id: skill.id, version: skill.latest_version_id },
],
},
tools: [{ type: "code_execution_20250825", name: "code_execution" }],
messages: [
{
role: "user",
content: [
{ type: "text", text: "Build the weekly report workbook from this CSV." },
{ type: "container_upload", file_id: input.id },
],
},
],
});
// 4. Download any files the Skill wrote to $OUTPUT_DIR.
for (const block of response.content) {
if (
block.type === "bash_code_execution_tool_result" &&
block.content.type === "bash_code_execution_result"
) {
for (const out of block.content.content) {
const meta = await client.files.retrieveMetadata(out.file_id);
const res = await client.files.download(out.file_id);
await writeFile(meta.filename, Buffer.from(await res.arrayBuffer()));
}
}
}
console.log("container", response.container?.id);
// 5. Page through files with the GA cursor shape.
for await (const f of client.files.list({ limit: 100 })) {
if (f.expires_at && new Date(f.expires_at) < new Date()) continue;
console.log(f.id, f.filename);
}
// 6. Check a known set of IDs in one call (max 100, no page/limit).
const page = await client.files.list({ ids: [input.id] });
console.log(page.data.length);
Notice what is missing: no client.beta, no betas: [...], no header. Skill references take type (anthropic or custom), skill_id and an optional version. Anthropic Skill IDs are pptx, xlsx, docx and pdf, with date versions such as 20251013 or latest. Custom Skill IDs look like skill_01... with version IDs skver_01....
The Python equivalent, which also shows creating a second version and pinning it:
from pathlib import Path
import anthropic
from anthropic.lib import files_from_dir
client = anthropic.Anthropic() # reads ANTHROPIC_API_KEY
# Upload an input that expires after 24 hours (3,600 to 7,776,000 seconds allowed).
csv = client.files.upload(file=Path("sales.csv"), expires_in_seconds=86_400)
print(csv.id, csv.expires_at)
# Create a custom Skill from a local directory, then a new version later.
skill = client.skills.create(files=files_from_dir("report_skill"))
print(skill.id, skill.display_name, skill.latest_version_id, skill.source.type)
v2 = client.skills.versions.create(skill_id=skill.id, files=files_from_dir("report_skill"))
response = client.messages.create(
model="claude-opus-5-5",
max_tokens=4096,
container={
"skills": [
{"type": "anthropic", "skill_id": "xlsx", "version": "latest"},
{"type": "custom", "skill_id": skill.id, "version": v2.id},
]
},
tools=[{"type": "code_execution_20250825", "name": "code_execution"}],
messages=[
{
"role": "user",
"content": [
{"type": "text", "text": "Build the weekly report workbook from this CSV."},
{"type": "container_upload", "file_id": csv.id},
],
}
],
)
for block in response.content:
if block.type == "bash_code_execution_tool_result" and block.content.type == "bash_code_execution_result":
for out in block.content.content:
meta = client.files.retrieve_metadata(out.file_id)
client.files.download(out.file_id).write_to_file(meta.filename)
# GA pagination: the SDK follows next_page for you.
for f in client.files.list(limit=100):
print(f.id, f.filename, f.expires_at)
How these were checked: the TypeScript example type-checks with npx tsc --noEmit (strict, moduleResolution: bundler) against @anthropic-ai/sdk@0.135.0. The Python example compiles with python -m py_compile against anthropic==1.15.0, and an import check confirmed that files.upload accepts expires_in_seconds and files.list accepts ids, limit and page. No live API calls were made, so treat output handling as documented behaviour rather than observed output.
%% title: Request lifecycle with Skills
sequenceDiagram
participant App
participant API as Messages API
participant Sbx as Container
participant Files as Files API
App->>API: create(container.skills, container_upload)
API->>Sbx: copy Skills to /skills/name
API->>Sbx: preload uploaded file
Sbx-->>API: stdout + OUTPUT_DIR file_ids
API-->>App: content blocks + container.id
App->>Files: files.download(file_id)Two output rules to bake into your Skill instructions. Each bash_code_execution call gets an empty $OUTPUT_DIR, and only top-level files there are captured - a file written to a subfolder or anywhere else in the container is not returned. If you want a follow-up turn to reuse the same sandbox state, pass the returned container ID on the next request; otherwise each request gets a fresh container.
Step 4: Migrate an existing beta integration#
If you built against the beta, here is the checklist in the order we would do it:
- Pick your header strategy per endpoint. Either drop the
anthropic-betavalues or moveclient.beta.files/client.beta.skillscalls toclient.files/client.skills. The beta namespace only returns GA shapes on SDK releases from August 27 onward; earlier releases still send the headers and are typed to the beta shapes, so stay on them until you migrate if you depend on those types. - Rewrite pagination. Replace
before_id/after_idloops withpage/next_page, or let the SDK iterator do it. On header-less requests,before_idandafter_idreturn a 400. - Handle
expires_at. It can now be a timestamp, and expired files still show up in list results. - Rename Skill fields.
display_titlebecomesdisplay_name,latest_versionbecomeslatest_version_id, andsourceis now an object - comparesource.type, not a string. - Replace stored version strings. Beta versions were epoch-microsecond strings; GA versions are
skver_...IDs, andGET /v1/skills/{skill_id}/versions/latestresolves the latest one. The versions list is now newest first, so code that took the first entry as the oldest needs flipping. - Review delete calls.
DELETE /v1/skills/{id}now removes the Skill and all its versions, where the beta returned 400 while any version existed. Deleting a Skill's only version now returns 400. - Rename types. Container references typed as
BetaSkillbecomeBetaContainerSkill(same fields:type,skill_id,version).BetaSkillnow names the Skill resource itself.
| Field or behaviour | With beta header | Without header (GA) |
|---|---|---|
| Files list response | { data, has_more, first_id, last_id } | { data, next_page } |
| Files cursors | before_id / after_id | page (pass back next_page); old cursors return 400 |
expires_at | Not returned | Always present, null when no expiry |
Upload Content-Type | Required | Optional, detected |
| Skill title | display_title (64 chars, unique) | display_name (255 chars, not unique) |
| Skill latest version | latest_version (epoch-microsecond string) | latest_version_id (skver_...) |
Skill source | String | Object { type } |
| Skills list | { data, has_more, next_page } | { data, next_page }, limit 1-1,000 (default 20) |
| Versions list order | Oldest first | Newest first |
| Delete a Skill | 400 while any version exists | Deletes the Skill and all its versions |
| Delete a Skill's only version | Allowed | 400 |
| Response types | Beta types | Skill, SkillVersion, DeletedSkill, DeletedSkillVersion |
Page cursors from one shape are not valid on the other, so do not persist a cursor across a deploy that flips the header.
A short TypeScript migration sketch, type-checked against @anthropic-ai/sdk@0.135.0:
import Anthropic from "@anthropic-ai/sdk";
import type { BetaContainerSkill } from "@anthropic-ai/sdk/resources/beta/messages/messages";
import type { BetaSkill } from "@anthropic-ai/sdk/resources/beta/skills/skills";
const client = new Anthropic();
// Before (beta header era): display_title, latest_version, source === "custom"
// After: display_name, latest_version_id, source.type
for await (const s of client.skills.list({ source: "custom" })) {
console.log(s.id, s.display_name, s.latest_version_id, s.source.type);
}
// The beta namespace still works on SDK >= 0.122.0, now with GA shapes.
const ref: BetaContainerSkill = { type: "custom", skill_id: "skill_01AbC", version: "latest" };
const res: BetaSkill = await client.beta.skills.retrieve("skill_01AbC");
console.log(ref, res.display_name);
In Python, the renamed reference type imports as from anthropic.types.beta import BetaContainerSkill.
%% title: Which response shape you get
flowchart TD
R[Request to /v1/files or /v1/skills] --> H{Beta header sent?}
H -->|yes| B[Beta shapes: has_more, before_id, display_title]
H -->|no| G[GA shapes: next_page, expires_at, display_name]
B --> K[Keeps working, no new fields]
G --> N[New: expiry, ids filter, skver IDs]Gotchas and limits#
- Cursors are shape-specific. A beta cursor sent to a GA request (or the reverse) is invalid.
- Expired files linger in lists. They stay visible for up to 30 days. Filter on
expires_atbefore referencing a file, because a Messages request that references an expired file fails before inference. - The
ids[]filter has its own rules. Up to 100 IDs, always one page (next_pageisnull), unresolved IDs are silently omitted, and it cannot be combined withpageorlimit. Compare the returned count to what you asked for. - List pages default to 20 files and go up to 1,000, newest first.
- Uploaded files are never downloadable. Only Skill or code-execution outputs are.
- Files limits: 500 MB per file stored, 1 TB total per organization, roughly 500 file-related requests per minute. A file used in a
documentorimageblock is still bounded by the context window (the API returns a 400 when it exceeds it). Upload, download, list, metadata and delete are free; file content used in Messages is billed as input tokens. - 20 Skills per request. Our 2025 deep dive cites beta-era headers and an 8-Skill cap, and the beginner guide to skills still lists the three beta headers.
- 30 MB per Skill upload, and every version is a full snapshot.
latestin production hands every workspace member a live switch on your agent's behaviour. Pinskver_IDs.- Prompt caching: changing the Skills list or its order breaks the cache. Pinning custom versions helps too, because a new
latestcan change the description that sits in the system prompt. - Sandbox constraints: no network access and no runtime package installation inside the container. The container runs Python 3.11 with 5 GiB RAM, 5 GiB disk and 1 CPU. Containers expire 30 days after creation and are checkpointed after about 5 minutes idle; the
container.expires_atin a response is a shorter rolling value. - Workspace scope: files and containers are workspace-scoped. Never accept file IDs from untrusted input, or one user can point your agent at another user's file.
- Data retention: the Files API, code execution and Agent Skills are not eligible for Zero Data Retention (ZDR) arrangements.
- Billing: code execution has a 5-minute minimum, 1,550 free hours per organization per month, then $0.05 per hour per container. Execution time is billed when files are included in a request even if the tool is never called.
- Outputs: only top-level files in
$OUTPUT_DIRcome back.
For handling the new 400s (old cursors, deleting a Skill's only version) and 404s (expired file content) in a retry layer, see our guide to Claude API reliability and error handling. These are not retryable errors - fix the request.
Files + Skills vs inline content and system prompts#
The closest alternative is what most apps already do: send documents inline as base64 or text with each request, and keep procedures in the system prompt. Both approaches are documented and supported; they fit different jobs.
| Capability | Files API + Skills + code execution | Inline content + system prompt |
|---|---|---|
| Reuse across requests | Upload once, reference file_id | Re-send the bytes every request |
| Size | 500 MB per file stored; content used in Messages is still bounded by the context window | 32 MB request cap (varies by platform); 600 PDF pages per request (100 below a 1M-token context window) |
| Generated files back | Yes, downloadable file_ids from $OUTPUT_DIR | No files, text and tool output only |
| Instructions | Only name and description in the system prompt; full instructions loaded on demand; versioned and shared in the workspace | Full prompt sent every turn; versioned in your repo |
| Infrastructure | Needs the code execution container and its time-based billing | None beyond the Messages call |
| Retention story | Files API, code execution and Skills are not ZDR-eligible | Inline PDF support is ZDR-eligible |
Inline content wins for one-off, small documents, when you want zero container cost, or when you need ZDR coverage. Files plus Skills wins when the same inputs are reused, when you need generated documents (xlsx, docx, pptx, pdf) back, and when a team shares a procedure that should change in one place with a pinned version. The Files API on its own sits in the middle: a file_id in a document block avoids re-uploading a large PDF without bringing in a container at all, though the Files API is not ZDR-eligible either, so it does not keep the inline retention story.
If your real question is live tool access versus packaged procedure, that is MCP versus Skills, covered in MCP vs Agent Skills. For when a Skill should become a full subagent, see Claude agents vs skills.
%% title: Picking an approach
flowchart TD
Q[Need Claude to use a file or procedure] --> R{Reused across requests?}
R -->|no| I[Inline base64 or text]
R -->|yes| D{Need generated files back?}
D -->|yes| FS[Files API + Skills + code execution]
D -->|no| F[Files API file_id in document block]FAQ#
Do I have to remove the files-api-2025-04-14 and skills-2025-10-02 headers?#
No. Requests that still send them keep working with the previous response format. But the new features - expires_in_seconds and expires_at, the ids[] filter, display_name, latest_version_id and skver_ IDs - only appear on requests without the headers, so plan the migration rather than leaving the headers in forever.
Why did my after_id pagination start returning 400?#
You dropped the header (or upgraded to an SDK whose beta namespace no longer sends it) and the GA shape rejects before_id and after_id. Pass the next_page value back as page, or use the SDK's auto-paginating iterator. Cursors saved under the beta shape will not work on the GA shape.
What happened to BetaSkill in the TypeScript and Python SDKs?#
Since TypeScript SDK 0.122.0 and Python SDK 1.2.0 (both August 27, 2026), BetaSkill names the Skill resource, matching Skill. The container reference you pass in container.skills is now BetaContainerSkill, with the same type, skill_id and version fields the old type had.
How long can an uploaded file live?#
Indefinitely unless you delete it. With expires_in_seconds you can set a lifetime from 1 hour (3,600) to 90 days (7,776,000), once, at upload time. After expiry, content and Messages references fail, while metadata stays listable for up to 30 days.
How many Skills can one request load?#
Up to 20 Skills per request, mixing Anthropic and custom Skills, and every request that loads Skills needs the code execution tool. Each custom Skill upload must be under 30 MB uncompressed.
Sources#
- Claude Platform release notes - August 19 and August 27, 2026 entries; beta launch dates
- Files API - expiration, pagination,
ids[], limits, context-window error, migration table - Agent Skills guide - upload rules, versioning, loading, limits, SDK beta namespace, migration table
- Code execution tool - container specs,
$OUTPUT_DIR, tool versions, billing - PDF support - request size and page limits
- API and data retention - ZDR feature eligibility table (Files API, code execution and Agent Skills: No; inline PDF support: Yes)
- Create Skill API reference
- @anthropic-ai/sdk on npm
- anthropic on PyPI
Continue Reading#
Get the next deep dive like this in your inbox
One email a week on Claude and the rest of the AI dev stack. Free.
Read next on Claude Code
Claude Skills: A technical deep dive into Anthropic's new approach to AI context management
Claude Skills load instructions, scripts and resources progressively so context stays small. A technical deep dive into SKILL.md, the API and Claude Code use.
8 min readWhat Are Claude Code Skills? A Complete Beginner Guide
Claude Code skills are SKILL.md playbooks that load only when relevant. What they are, where they live, how to write one, and how they differ from CLAUDE.md.
11 min readMCP vs Agent Skills: When to Use Which (and Why You Need Both)
MCP vs Agent Skills: MCP gives agents live access to tools and data, Skills give them packaged procedure. The decision rule for when to use which, or both.
11 min readTechnical content at the intersection of AI and development. Building with AI agents, Claude Code, and modern dev tools - then showing you exactly how it works.








