The Harness Is the License
A Max subscription token authenticates fine against Anthropic's API and still gets rejected — because the subscription licenses the harness's use of a model on your behalf, not your own direct use of the token.
rosary talks to models three ways: driving an existing harness (claude -p, codex exec), and — as of last week — calling a model’s API directly. Collapsing those into one abstraction seemed obvious. A provider is a thing that takes a prompt and returns tokens; why would it matter how you got there? An empirical result stopped that plan in one HTTP response.
Two wallets, one token
The setup: a Max subscription mints an OAuth token (sk-ant-oat0…, via ant auth print-credentials). That token is real, current, and — as far as any client can tell — fully authenticated. Point claude -p at it and everything works, same as always. Point a raw POST /v1/messages call at it directly, and the response is HTTP 400 "Your credit balance is too low" — not a 401, not a 403. The token was accepted. The account behind it just has nothing in the pay-as-you-go balance the API call is billing against.
That’s the tell. A subscription and a metered API key aren’t two credentials pointing at the same wallet — they’re two separate wallets, and a token minted for one doesn’t carry balance in the other. The Max/Pro subscription bills against a shared quota that includes a specific monthly allotment — Anthropic’s docs name it the Agent SDK credit — covering the Agent SDK, claude -p, and Claude Code itself. claude -p was never spending from the pay-as-you-go pool it looked like it should be spending from; it was always billing the harness-specific quota. The direct API call skipped past that quota entirely and hit the one that was actually empty.
The correction, kept in the record
The first pass at this got it wrong, and the wrong version is worth keeping rather than quietly fixing, because the retraction is the actual finding. Before the live probe above, the working hypothesis — based on a real detail in Anthropic’s docs, that /v1/messages accepts a Bearer token plus an anthropic-beta: oauth-2025-04-20 header instead of an API key — was that a Max token, sent with the right headers, would work directly. That’s a reasonable reading of the header contract. It’s also not what happens: the header shape being accepted and the account having anything to bill against are different questions, and only the live call answers the second one. The correction landed the same day as the hypothesis, logged next to it rather than replacing it, because the wrong turn is part of what makes the right answer trustworthy.
Drive it, or become it
The consequence for rosary’s design: a harness and a direct API call aren’t two doors to the same room, and treating them as one Provider abstraction was the mistake that needed the HTTP 400 to surface.
RuntimeProvider drives a harness that already exists — claude -p, codex exec, an ACP session. The credential stays exactly where it already lived, inside the harness’s own keychain or OAuth store; rosary never touches it, never custodies it, never even sees it. This is the sanctioned, individual-use, seat-powered path, and it’s the one bdr_enrich and the rest of rosary’s Max-backed work already run on. Nothing about this post’s discovery changes it — it’s the path that was already correct.
ModelProvider is the other thing entirely: rosary stops driving a harness and becomes the runtime itself, making the HTTP call directly. That detachment is exactly what a subscription token can’t license, so this lane requires a credential rosary actually can hold — a real API key, sk-ant-api…, billed against pay-as-you-go credits that exist to be spent this way. The subscription seat and the API key were never interchangeable; they only looked that way until something asked the API to bill against one instead of the other.
The rest of the matrix, and the wall that isn’t always there
The same question — can a credential be custodied by something other than the process that originally minted it — turns out to have a different answer per provider, not one blanket rule. A Codex OAuth token refreshes cleanly from a non-originating holder using nothing but a public client ID; it’s the easy case. A Gemini token is custody-able the same way in principle, but needs three things to refresh — the refresh token, a client ID, and a client secret — and that secret isn’t sitting in the public package the way Codex’s is, which makes it custodible in theory and heavier in practice. Anthropic’s subscription token is the one case that isn’t a custody question at all: it’s harness-bound by policy as much as by mechanism, and no amount of correct header-wrangling changes that.
One corollary falls out of drawing the line this way instead of around “which harness”: point either lane at a local model — ANTHROPIC_BASE_URL swapped to localhost, a model server running on your own hardware — and the entire wall disappears. No seat, no token, no policy line, because the wall was only ever about the model being Anthropic’s, not about which harness was asking. A local model was never something anyone needed to license.
The part worth noticing
Somewhere in the middle of running these probes, an attempt to read a live credential and replay it against an outbound call got refused by the coding harness’s own safety classifier — for both providers being tested, on this machine, mid-research. Researching whether a non-originating process can legitimately hold and reuse someone else’s credential got stopped by the exact behavior the research was asking about. That’s not a footnote; it’s the mediation boundary this whole site keeps describing, working on the researcher instead of just being described by him.
rosary is open source: github.com/agentic-research/rosary · rosary.bot.