I pay for a Claude subscription every month, and in early January I wanted my own code to use it. The login flow completed, and the first actual inference request came back with:
“This credential is only authorized for use with Claude Code.”
The credential came from Claude Code. PKCE had completed; the token carried the expected sk-ant-oat prefix. Everything obvious was right, and the server said no anyway.
The request came from a proof-of-concept coding agent that now lives in foundry. It used Claude Code’s OAuth flow instead of an API key so it could draw on my existing subscription rather than metered billing. I needed to find what the server was actually checking.
Plausible suspects
Headers were the first suspect, because Anthropic’s OAuth path requires a particular set of beta flags:
const OAUTH_BETAS = [
"oauth-2025-04-20",
"interleaved-thinking-2025-05-14",
"fine-grained-tool-streaming-2025-05-14",
"context-management-2025-06-27",
]
All four were present. No x-api-key header, which is correct for OAuth; the Authorization: Bearer header well formed; the User-Agent matching Claude Code’s own signature. Nothing in the header block explained the refusal.
The token was healthy. The profile endpoint returned my account details, and refresh tokens refreshed. The server could describe my account while still refusing to run inference.
Model restrictions came next, and the failure appeared regardless of whether the request targeted Haiku or Opus, so the model wasn’t the gate either.
I compared my request with three working implementations: the authentication plugin inside Link Assistant’s agent, the opencode-anthropic-auth npm package, and claude.js, the bundled source of the official CLI. The notes became claude-auth.md. OAuth requests use the beta Messages path, /v1/messages?beta=true, while the plain path may return the same credential error. Some OAuth tokens also reject the claude-code-20250219 beta flag that identifies a client as Claude Code, again with the same error. One server sentence covered several refusals and provided little direction.
By the end of that comparison, my request matched claude.js everywhere I knew to check, but only the official client worked. With headers, model, and transport eliminated, the remaining difference had to be in the request body.
The gate was in the body
Authentication failures train you to stare at headers. Tokens live in Authorization, API keys in x-api-key, and beta flags in their own header. I spent the first hour there. Staring harder did not change them.
The check was in the system field of the request body. For Claude Code OAuth tokens, the API expects the system prompt to match one sentence exactly:
You are Claude Code, Anthropic's official CLI for Claude.
The sentence has fifty-seven characters. Set the system field to exactly that text and the request succeeds. Append instructions or even one newline and the original error returns:
This credential is only authorized for use with Claude Code.
I didn’t keep the captures, but the rule they proved is easy to state: no more and no less than those fifty-seven characters will pass. The OAuth flow worked, the headers were correct, the token was valid; the request body had disqualified it.
Once the check was visible, the fix, in src/provider/anthropic.ts, was small:
function getOauthSystemPrompt(systemPrompt: string): string {
if (process.env.CODING_AGENT_OAUTH_SYSTEM_MODE === "full") {
if (!systemPrompt) {
return CLAUDE_CODE_SYSTEM_HEADER
}
if (systemPrompt.includes(CLAUDE_CODE_SYSTEM_HEADER)) {
return systemPrompt
}
return `${CLAUDE_CODE_SYSTEM_HEADER}\n\n${systemPrompt}`
}
// OAuth tokens require the system prompt to be exactly the Claude Code header.
return CLAUDE_CODE_SYSTEM_HEADER
}
In OAuth mode the system prompt now stays on the allowed sentence. Custom instructions must live elsewhere in the request, or the tool must use an API key.
What the token was issued for
The restriction has a plausible security rationale. OAuth tokens from Claude Code’s flow are tied to consumer subscriptions, and pinning the system prompt limits their use to the issued client behaviour. In practice, the sentence must appear alone. A Claude Code-compatible tool that needs its own system instructions needs an API key, because the OAuth system field cannot carry product behaviour.
The error message had been telling the truth the whole time. It said the credential was only authorized for use with Claude Code, and a request body carrying my own instructions was, by the server’s definition, no longer Claude Code. I had spent the investigation reading a body problem at the header layer. The full write-up of the flow, considerably expanded after this adventure, is in claude-auth.md in the repo.