Three Headers, One Date, Three Different Risks
Because the three entries share a date and a sentence structure, they invite a single cleanup ticket: find every anthropic-beta value that is now redundant and delete it. The per-feature documentation does not support that reading. Each page says something different about what a request looks like once the header is gone.
| Beta name | Where it applies | What changes when you remove it |
|---|---|---|
files-api-2025-04-14 |
/v1/files, and Messages requests that reference an uploaded file |
The list endpoint switches pagination scheme, and file objects start carrying expires_at |
skills-2025-10-02 |
/v1/skills, and container.skills in Messages requests |
Nothing. Both headers "remain valid opt-ins", and the guide's own examples still send them |
ce-user-management-2026-07-13 |
Group and custom-role requests on the Admin API | Nothing. Requests that still send it "are accepted and behave identically" |
Two of the three really are free. The Skills guide states that Skills are generally available and require no anthropic-beta header, for either the Skills API or container.skills in Messages requests, and that its examples keep sending skills-2025-10-02 only because it still works. The user-management page is flatter still: the header is no longer required on group and custom-role requests, and the group and custom-role examples on that page continue to send it. Neither documents any difference in what comes back. The Files API is the one that does.
What a Files Response Actually Does
The Files API documentation spells the difference out in two places, and both are about reads rather than writes. With the beta header, the list endpoint "paginates with before_id and after_id, returns has_more, first_id, and last_id instead of next_page, and rejects the page and ids[] parameters as unknown fields." Without it, GET /v1/files takes page and returns next_page, with limit defaulting to 20 and capped at 1,000. The second difference is smaller and harder to see: file objects returned under the header "omit expires_at instead of returning null when no expiration is set."
Neither of those is an error. A pagination loop written against the beta shape does not throw when the shape changes underneath it. It stops early instead.
// Written against the beta list format. Dropping the header does not
// fail this loop - it makes it return page one and call that the answer.
const all = [];
let params = { limit: 100 };
for (;;) {
const page = await listFiles(params); // GET /v1/files
all.push(...page.data);
if (!page.has_more) break; // GA: has_more absent -> falsy -> break
params.after_id = page.last_id; // GA: last_id absent -> undefined
}
A strict deserializer would at least raise on the missing fields. A permissive one — a hand-rolled fetch wrapper, a dynamic language, a JSON blob passed straight into business logic — returns the first 100 files and reports success. The same asymmetry runs the other way for the field: code that decides whether a file is temporary by testing for the presence of the expires_at key now sees that key on every file, holding null for the files that never expire. A presence check that was reliable under the beta format is wrong under the GA format, and nothing in the request tells you so.
This is unrelated to the header change, but the GA notice is what will send you back to this documentation page, so read it in the same sitting. Uploaded files "are accessible to your entire workspace, not scoped to an end user, conversation, or session", any API key in that workspace can read any file in it, and the guidance is explicit that you should never accept file_id values from end users. For a multi-tenant product the recommended isolation boundary is one workspace per tenant, with up to 100 workspaces per organization. If the cleanup touches the code that maps your users to their files, that is the moment to check this. The tenancy and data-handling side is covered in the AI data privacy chapter.
In an SDK, the Header Is Not a String You Can Grep
Here is the part that turns a ten-minute ticket into an incident. The documentation states that the SDK beta.files methods and the CLI's ant beta:files commands add the header automatically, and the cURL examples on the Files page include it by hand. So in a typical Python or TypeScript service, nobody ever typed files-api-2025-04-14 anywhere. Grep your repository for that string and you get zero hits while every list call you make is still receiving the beta response format.
Which means the migration is not an edit to a header map. It is a move off the beta namespace: client.beta.files.list() becomes the non-beta call, and the day it does, the response shape changes with it. Inventory by call site instead — beta.files usage, ant beta:files in scripts, an explicit betas=[...] argument, a raw header on a hand-built request — and treat each one as its own switch. The general discipline for changing an API contract without breaking the clients on the other side is in the AI API contract change plan; this is that same problem seen from the client's chair, where you do not get to choose the timing but you do get to choose the order.
One relief: the Messages side is genuinely inert. Requests that use an uploaded file as a document or image source, or in a container_upload block for the code execution tool, "work with or without the header". The blast radius is confined to what /v1/files hands back.
What the GA Format Gives You Back
There is a reason to do this beyond tidiness, and it is the expiration and lookup behaviour that exists only on the header-free path.
- Expiring uploads. Send an
expires_in_secondsform field at upload, as an integer between 3,600 (one hour) and 7,776,000 (90 days). The resultingexpires_atis an RFC 3339 timestamp present on every file response. It is set once at upload and cannot be changed afterwards, so a wrong value means re-uploading the file, not patching it. - What expiry actually does. Downloading the content returns 404, and a Messages request that references the file "fails before inference" — model inference does not begin, but your caller still gets an error where an answer used to be. The metadata stays readable for up to 30 days with
expires_atin the past, and the file keeps appearing in list responses for that whole window. Filtering expired files out of a listing is your code's job, done by comparingexpires_atto the current time. - Batch lookup without paging. Pass up to 100 file IDs as
ids[]and you get a single page back withnext_pageset tonull. The trap is documented: any ID that does not resolve to a file in your workspace "is silently omitted fromdata", so compare the returned IDs against the ones you asked for instead of assuming a full set.ids[]cannot be combined withpageorlimit. - The operating numbers. 500 MB maximum per file, 1 TB of total storage per organization, and file-related calls limited to approximately 500 requests per minute. Note that hitting the storage ceiling is a 400, not the 413 you get for an oversized single file — two different failures that one catch-all handler will blur together.
An Order That Stays Reversible
- Do Skills and user management first. Both are documented as behaving identically with and without the header. Removing them clears two thirds of the ticket and leaves exactly one real change to think about.
- Inventory the Files call sites, not the string. The SDK adds the header for you, so the audit runs over
beta.filesusage rather than over a grep result. - Split reads from writes. Upload, retrieve-metadata, download, and delete look the same either way. The endpoint whose shape moves is
GET /v1/files. That is where the review time goes. - Fix the pagination loop before you flip anything. Make it handle both shapes: page on
next_pagewhen the field is present, onhas_morepluslast_idwhen it is not. Once the loop is shape-agnostic, the switch itself becomes inert. - Make the header a runtime flag, not a deleted line. One environment variable decides whether the call goes through the beta namespace. Flip it in staging, watch the list path, and if a downstream consumer chokes, flip it back in one deploy instead of reverting a code change under pressure.
- Adopt the new fields afterwards, deliberately.
expires_in_secondsandids[]are the payoff, not part of the cutover. Shipping them in the same change removes your ability to say which edit caused the behaviour you are looking at.
One testing note, because it is the assertion most likely to be missing. A test that checks data[0].id passes under both formats and proves nothing. Assert on the pagination envelope itself — that a second page was fetched at all, and that the total count matches a known fixture — because the envelope is the only part that changed.
Not a Shutdown, and That Is Exactly the Problem
It is worth being precise about how this differs from a deprecation. When a provider removes an endpoint, as with the Assistants API shutdown on August 26, you get a date, a documented replacement, and eventually an error that forces the issue whether or not anyone scheduled the work. Here there is no published end date, no error, and no forcing function at all. The old behaviour continues for anyone who does nothing under the documentation checked today. The only event that changes your response format is an edit you make yourself, in whatever week someone decides to tidy up headers — which is also the week nobody is watching the file-listing code path.
That said, a beta name is not a permanent parking spot either. An invalid beta name, or one your organization cannot access, returns a 400 whose message reads "Unexpected value(s) ... for the anthropic-beta header." No removal date has been published for any of these three names, and today's documentation says requests that still send them keep working. But "keeps working" is a present-tense statement about three specific strings, not a guarantee about next year. Treat the header as a rollback lever with a shelf life: worth keeping for the length of one cutover, not worth leaving in the code indefinitely. If you are wiring any of this up for the first time rather than migrating it, the groundwork is in the Claude API chapter.
- Anthropic: Claude Platform release notes, the August 19, 2026 entries
- Anthropic: Files API — beta versus GA list format, expiration, and limits
- Anthropic: Using Agent Skills with the API
- Anthropic: User management for Claude Enterprise organizations
- Anthropic: Beta headers, including the error returned for an invalid beta name
- Anthropic: API overview — pagination schemes across the Claude API