← Back to blog
claude-codeanthropicopenclawreverse-engineering

I Spent an Evening Bisecting Anthropic's Claude Code Fingerprint

· 5 min read

If you have a Claude.ai Pro or Max subscription, you can run claude setup-token and get a token that looks like sk-ant-oat01-.... Anthropic’s docs call it a setup token. It’s an OAuth access token, scoped to the Claude Code CLI, billed against your existing plan. No extra usage charges. No “what’s my burn rate this month” anxiety.

So I tried to use it from OpenClaw instead of Claude Code itself. Same token, same Anthropic API endpoint, same model. Different agent framework on top.

Anthropic said no.

HTTP 400 invalid_request_error
"Third-party apps now draw from your extra usage, not your plan limits.
We've added a $200 credit to get you started. Claim it at
claude.ai/settings/usage and keep going."

The token authenticates fine. But Anthropic somehow knows this isn’t Claude Code, and routes the request to a separate “extra usage” credit pool.

I wanted to know exactly how they know. So I spent an evening bisecting it.

The obvious things don’t work

The first thing anyone tries is matching Claude Code’s HTTP headers. Claude Code’s CLI sends:

User-Agent: claude-cli/2.1.76
x-app: cli
anthropic-beta: claude-code-20250219,oauth-2025-04-20,fine-grained-tool-streaming-2025-05-14,interleaved-thinking-2025-05-14
anthropic-dangerous-direct-browser-access: true

Set those headers, send the same body Claude Code would send → success. Set them and send OpenClaw’s body → 400, third-party billing wall.

So it’s not just headers. Anthropic is also looking at the request body.

Bisecting the body

OpenClaw’s request body is dense — 21 tools, 57 messages of conversation history, a 44KB system prompt, plus thinking and output_config fields. Total payload around 115KB. Plenty of places for a signal to hide.

I started removing fields one at a time:

VariantResult
Remove output_configStill 400
Remove thinkingStill 400
Drop conversation history (1 message instead of 57)Still 400
Drop tools (0 tools instead of 21)200 OK
Drop system prompt entirely200 OK

Two independent triggers. Either the tools array or the system prompt was enough to flunk the check on its own. Anthropic isn’t fingerprinting one thing — they’re fingerprinting several, and any one of them misfiring is enough.

The tools array has a vocabulary

The tools array was the easier one to diagnose. OpenClaw’s tools are named read, edit, write, exec, process, cron, sessions_spawn, web_search, memory_get, … Lowercase, snake_case, very framework-specific.

Claude Code’s tool list is something else: Read, Edit, Write, Bash, Glob, Grep, Task, TodoWrite, WebFetch, WebSearch, NotebookEdit. PascalCase. A specific vocabulary.

On the traffic I tested, the naming style of the tools array was one of the signals the API reacted to — not the descriptions, not the schemas, just the names as a set. A linguistic tell. Claude Code’s tool vocabulary is small, stylistically distinctive, and apparently part of the billing decision.

The system prompt signal

The system prompt was the weirder one. OpenClaw’s is 44KB of agent persona, runtime context, memory rules, heartbeat protocols, and 100+ literal mentions of “OpenClaw” or “openclaw.” My first hypothesis was the obvious one: Anthropic is grepping for the brand name.

I tried it. Replaced every case-insensitive occurrence of “openclaw” with “claude” in the system text. Same request otherwise. Still 400.

So it’s not literal string matching. The classifier is looking at content patterns, not specific tokens.

I went back to bisecting, truncating the system prompt at progressively smaller lengths. The verdict flipped sharply between two prompt lengths a handful of characters apart, which was tempting to chase by exact character position — but that turned out to be a red herring. There are several signals firing in the same neighborhood, not one clean tripwire. Whatever the exact machinery is, the classifier is content-based. Probably ML. Definitely not regex.

I’ll stop short of publishing the exact boundaries I found. The point isn’t to draw a map. The point is that Anthropic’s classifier is doing real work, it’s clearly the product of someone who thought carefully about the problem, and string matches that are trivially defeated were never going to be it.

So what did we learn?

Anthropic’s third-party detection is at least three layers deep:

  1. Headers — necessary, not sufficient. claude-cli/2.1.76 and the claude-code-20250219 beta flag are the entry ticket.
  2. Tool names — Claude Code’s canonical PascalCase tool vocabulary. Lowercase or snake_case names read as third-party.
  3. System content — the system field is inspected by what looks like a content classifier, not literal pattern matching. Long agent-framework prompts are flagged even when brand markers are stripped.

This is, of course, an arms race. Anthropic can update the classifier tomorrow. The rules will keep getting tighter. Which is completely their prerogative — the bits on the wire belong to the owner of the API.

Where that leaves you

If you’re running OpenClaw (or any other agent framework) and hitting the third-party billing wall against your Claude plan, there are two honest paths.

The boring, supported one: get a standard Anthropic API key under their Commercial Terms. Third-party software is explicitly allowed there. It bills per-token rather than flat-rate, but it’s the path Anthropic built for exactly this use case, and it Just Works.

The other one lives in wallets that handle SDK-shaped request conventions on your behalf. Before wiring any subscription token into any tool, read Anthropic’s Consumer Terms and Usage Policy and make sure what you want to do is actually permitted. The bits on the wire belong to Anthropic, and so does the last word on what you’re allowed to send them.

The broader lesson is the one this whole ecosystem is stumbling into together: a token used to be a bearer instrument, and now it’s a context-sensitive object whose value depends on which binary is holding it when it hits the server.

— Michael 🇦🇹