Skip to content

feat(files): serve Anthropic's GA Files API shape and refuse the files beta header - #1509

Merged
peteski22 merged 6 commits into
mainfrom
feat/anthropic-files-ga-shape
Sep 22, 2026
Merged

peteski22 merged 6 commits into
mainfrom
feat/anthropic-files-ga-shape

Conversation

@peteski22

@peteski22 peteski22 commented Sep 22, 2026 •

Copy link
Copy Markdown
Contributor

Description

#1366 made Otari's files API answer Anthropic's SDK in Anthropic's own shape. That shape was Anthropic's files-api-2025-04-14 beta, and Anthropic's Files API is now generally available with a different one. This PR moves the Anthropic flavor to the GA shape before it ships in a release:

  • A listing is {data, next_page}. Pass next_page back as page for the next page, or name up to 100 files with ids[] to read them in one page. With Anthropic's SDK, client.files.list() now pages past the first page.
  • Every file object carries expires_at, which is null when the file does not expire.
  • A files request whose anthropic-beta header includes files-api-2025-04-14 gets a 400 that says to send it without that beta. This is stricter than Anthropic, which still honors the beta, so that Otari keeps one Anthropic contract. The cost: client.beta.files on Anthropic's Python SDK before 1.2.0, which includes the version in our lock file, gets the 400. client.files works.

The OpenAI flavor does not change. The code-execution demo and the files guide now use client.files.

For review: the listing query stays in the route, which is on the architecture baseline, because #1483 moves the whole files domain into repositories and a service. A NUL character in the OpenAI after cursor or in a file ID path still gives a 500; that was there before this PR, and this PR only guards the two new inputs.

How to test it locally

  • uv run pytest tests/integration/test_files_endpoint.py (needs Docker for PostgreSQL). test_anthropic_sdk_files_client_pages_and_reads_expires_at drives Anthropic's own SDK through the app: it lists past the first page, reads expires_at, and gets the 400 from client.beta.files. The other new tests cover the page token, ids[], the refused after_id and before_id, and the 400 on all five routes.
  • By hand, against a running gateway with an API key:
    • curl -H "x-api-key: <key>" -H "anthropic-version: 2023-06-01" "http://localhost:8000/api/v1/files?limit=1" answers {"data": [...], "next_page": ...}.
    • The same call with -H "anthropic-beta: files-api-2025-04-14" answers 400.
    • Without anthropic-version, the answer is the OpenAI list, as before.
  • make lint, make typecheck, make openapi-check and make postman-check pass, and the dashboard client matches the spec. I did not run demo/code-exec/plot_with_anthropic_sdk.py, which needs provider credentials; the two calls it changed to are the ones the SDK test runs.

PR Type

  • New Feature
  • Bug Fix
  • Refactor
  • Documentation
  • Infrastructure / CI

Relevant issues

Fixes #1473. Part of #1470.

Checklist

  • I understand the code I am submitting.
  • I have added or updated tests that cover my change (tests/unit, tests/integration).
  • I ran the Definition of Done checks locally (make lint, make typecheck, make test).
  • Documentation was updated where necessary.
  • If the API contract changed, I regenerated the OpenAPI spec (uv run python scripts/generate_openapi.py).
  • If this changes a rule in ARCHITECTURE.md or scripts/check_architecture.py, the description names the rule and says why.

make test passes locally apart from failures that come from the local environment and not from this change: tests that read the gitignored .env, and test_mcp_dependency_ceiling, whose throwaway virtual environment cannot load its Python library on this machine.

AI Usage

  • No AI was used.
  • AI was used for drafting/refactoring.
  • This is fully AI-generated.

AI Model/Tool used:

Claude Code (Claude Opus 5)

Any additional AI details you'd like to share:

The design comes from the issue. Claude Code wrote the tests first, then the code, the docs and the regenerated artifacts, and ran security, error-handling, state-machine and style checks over the diff, which found three input-handling bugs that are fixed here. Peter reviewed each commit before this PR was opened.

NOTE:
When responding to reviewer questions, please respond yourself rather than copy/pasting reviewer comments into an AI and pasting back its answer. We want to discuss with you, not your AI :)

  • I am an AI Agent filling out this form (check box if true)

Summary

Otari now serves Anthropic’s GA Files API shape. Anthropic listings support page pagination and up to 100 ids[] values, and file metadata always includes expires_at.

Requests that use the legacy files-api-2025-04-14 beta header now return a clear 400 error. The OpenAI Files API remains unchanged.

The demo, documentation, client schema, Postman collection, OpenAPI artifacts, and integration coverage were updated. The demo now uses client.files.

Technical notes

  • Anthropic responses use {data, next_page}.
  • Anthropic rejects after_id, before_id, invalid page tokens, and incompatible page/ids[] or limit/ids[] combinations.
  • Added coverage for SDK pagination, metadata retrieval, downloads, deletion, beta-header rejection, and OpenAI behavior.

@peteski22
peteski22 requested review from a team and tbille and removed request for a team September 22, 2026 14:26
@peteski22
peteski22 deployed to integration-tests September 22, 2026 14:27 — with GitHub Actions Active
@peteski22
peteski22 deployed to integration-tests September 22, 2026 14:27 — with GitHub Actions Active
@peteski22
peteski22 deployed to integration-tests September 22, 2026 14:27 — with GitHub Actions Active
@peteski22
peteski22 deployed to integration-tests September 22, 2026 14:27 — with GitHub Actions Active
@coderabbitai

coderabbitai Bot commented Sep 22, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

Note

Currently processing new changes in this PR. This may take a few minutes, please wait...

⚙️ Run configuration

Configuration used: Repository: mozilla-ai/otari/.coderabbit.yaml

Review profile: CHILL

Plan: Advanced

Run ID: 51e1cfc7-d4a1-4f01-8241-7d6528732b06

📥 Commits

Reviewing files that changed from the base of the PR and between e424d08 and f40ef1c.

⛔ Files ignored due to path filters (1)
  • docs/public/openapi.json is excluded by !docs/public/openapi.json
📒 Files selected for processing (7)
  • demo/code-exec/plot_with_anthropic_sdk.py
  • docs/files.md
  • docs/public/otari.postman_collection.json
  • src/gateway/api/routes/files.py
  • src/gateway/models/tools.py
  • tests/integration/test_files_endpoint.py
  • web/src/client/schema.ts
✨ Finishing Touches
📝 Generate docstrings
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
✨ Simplify code
  • Commit to this branch
  • Create a new PR

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

The Anthropic flavor of /v1/files serves the files-api-2025-04-14 beta
shape, which Anthropic's GA Files API has replaced. These tests state the
GA contract: every file object carries expires_at, a listing is
{data, next_page} paged by page or read by ids[], after_id and before_id
are refused, and a request carrying the beta header gets a 400 on every
route.

One test drives Anthropic's own SDK through the app: client.files.list()
must page past the first page and retrieve_metadata() must return
expires_at. The ascending-order check moves to the OpenAI flavor, which it
only reached through Anthropic's headers before.

The new tests fail until the following commits land.
Anthropic's GA Files API puts expires_at on every file object, null when
the file does not expire, and its SDK reads it from there. The beta shape
the gateway served left it out.

Both timestamps now go through one RFC 3339 helper, which keeps reading a
naive stored value as UTC.
Anthropic's GA Files API lists files as {data, next_page}: the caller
passes next_page back as page, or names up to 100 files with ids[] to read
them in one page. The gateway served the beta's {data, has_more, first_id,
last_id} paged by after_id, so client.files.list() in Anthropic's SDK
stopped after the first page.

The Anthropic flavor now answers in the GA shape. Its page token is the
last file's ID behind a page_ prefix, encoded so that callers treat it as
opaque; a token the gateway did not issue, including one naming another
user's file, is a 400. ids[] cannot be combined with page or limit, and
after_id and before_id get a 400 as they do on Anthropic without the beta
header. The OpenAI flavor keeps its shape and its after cursor.

The public artifacts are regenerated for the changed list parameters.
Anthropic still honors the files-api-2025-04-14 beta and answers it in
the beta shapes. The gateway serves one Anthropic contract, the GA one, so
a files request whose anthropic-beta header names that beta now gets a 400
that says to send the request without it. Anthropic's Python SDK before
1.2.0, including the locked 0.125.0, sends the header from
client.beta.files; client.files does not.

The check is a dependency on the router, so a route added to it inherits
the refusal. The Anthropic flavor is selected by anthropic-version alone,
which Anthropic's SDK sends on every call, rather than also by an
anthropic-beta value that starts with files-api.
The Anthropic SDK demo read the produced chart through client.beta.files,
which sends the files-api-2025-04-14 beta header that the gateway now
refuses. client.files speaks the GA Files API the gateway serves.
The Files API guide described the beta shape and showed client.beta.files.
It now names the GA fields, including expires_at, the 400 for the
files-api-2025-04-14 header and which SDK releases send it, and how each
flavor pages its listing.
@peteski22
peteski22 force-pushed the feat/anthropic-files-ga-shape branch from 45ce1fc to f40ef1c Compare September 22, 2026 14:34
@peteski22
peteski22 deployed to integration-tests September 22, 2026 14:34 — with GitHub Actions Active
@peteski22
peteski22 deployed to integration-tests September 22, 2026 14:34 — with GitHub Actions Active
@peteski22
peteski22 deployed to integration-tests September 22, 2026 14:34 — with GitHub Actions Active
@peteski22
peteski22 deployed to integration-tests September 22, 2026 14:39 — with GitHub Actions Active
@peteski22
peteski22 merged commit 05c7192 into main Sep 22, 2026
36 of 37 checks passed
@peteski22
peteski22 deleted the feat/anthropic-files-ga-shape branch September 22, 2026 14:43
@otari-bot otari-bot Bot mentioned this pull request Sep 22, 2026
4 tasks

This branch was successfully deployed

1 active deployment
integration-tests — f40ef1c8 Deployed Sep 22, 2026 by peteski22 via test-integration (4/4) #2463
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Serve Anthropic's GA Files API shape, and refuse the files beta header with a clear error

2 participants