Disclosure: I run APIVAI, a pay-as-you-go gateway for Claude and GPT. I ran the tests below against my own service too, and the results are at the end. The method works on any relay that speaks the native Anthropic format, so use it on mine, on others, or on the one you already pay for.
If you buy Claude through a reseller, a relay or a gateway, you are trusting that a request for claude-sonnet-4-6 actually reaches Claude Sonnet 4.6. That trust is not always deserved:
- A CISPA paper accepted to ACM CCS 2026 ("Real Money, Fake Models", arXiv 2603.01919) audited "shadow APIs" used in 187 research papers. On the three it tested in depth, performance diverged by up to 47% and 45.83% of fingerprint checks failed identity verification.
- In Chinese developer forums the complaint has its own vocabulary: 以次充好 (passing off a cheaper product), 掺水 (watering down), 降智 (the model "got dumber"). Typical posts: "how do they guarantee it isn't another model?" Reply: "they don't, and they mix them."
- On Hacker News, people report being routed to poor quantizations on marketplaces "while paying maybe 15% less".
So how do you check? Most of the advice you will find doesn't work.
Tests that don't work
"Ask it who it is." A relay can inject a system prompt, and real models get their own identity wrong anyway. One survey of 190 models found about 60% misnamed themselves at least once.
"Ask for its knowledge cutoff." A backend serving genuine Claude gave 12 different cutoff dates across 34 asks in one published test. The answer is noise.
"Check the model field in the response." The relay writes that field. It can say anything.
"It feels slower/dumber today." Real regressions exist (Anthropic published a postmortem in 2025 about infrastructure bugs that degraded some Sonnet responses), and so do prompt and context differences. A feeling is not evidence.
The test that does work: the thinking signature
When extended thinking is on, Anthropic returns each thinking block with an opaque signature. In a tool-use conversation you must send the previous assistant turn back, thinking blocks included, and Anthropic verifies that signature. A fake or modified signature is rejected with:
400 invalid_request_error: messages.1.content.0: Invalid `signature` in `thinking` block
That gives you a clean experiment:
- Ask a question that makes the model call a tool, with thinking enabled. You get back a
thinkingblock (with a signature) and atool_useblock. - Send the turn back unchanged with a tool result. A genuine backend accepts it.
- Send it back again with the last few characters of the signature changed. A genuine backend rejects it with the error above.
Step 3 is the important one. A relay that serves another model can invent a signature string, but it cannot make Anthropic's verifier reject a tampered one and accept the real one, unless the requests really are going to Anthropic.
Supporting signals in the same responses (useful, but forgeable on their own):
- Message IDs look like
msg_011…, tool-call IDs liketoolu_…. -
usagecontains the newer fields:service_tier,inference_geo, and a nestedcache_creationobject.
The script
Python, standard requests, native Anthropic format. It costs well under a cent on Haiku and about a cent on Sonnet.
import os, requests
BASE = os.environ["RELAY_BASE_URL"] # e.g. https://your-relay.example (no /v1)
KEY = os.environ["RELAY_API_KEY"]
MODEL = os.environ.get("MODEL", "claude-sonnet-4-6")
H = {"x-api-key": KEY, "anthropic-version": "2023-06-01", "content-type": "application/json"}
TOOLS = [{"name": "get_weather", "description": "Get the weather for a city",
"input_schema": {"type": "object", "properties": {"city": {"type": "string"}}, "required": ["city"]}}]
THINK = {"type": "enabled", "budget_tokens": 1024}
Q = {"role": "user", "content": "What is the weather in Paris? Use the tool."}
def call(messages):
r = requests.post(f"{BASE}/v1/messages", headers=H, timeout=120, json={
"model": MODEL, "max_tokens": 1600, "thinking": THINK, "tools": TOOLS, "messages": messages})
return r.status_code, r.json()
s, a = call([Q])
blocks = a.get("content", [])
print("1st:", s, a.get("id"), [b["type"] for b in blocks])
print("usage fields:", sorted(a.get("usage", {}).keys()))
tool = next((b for b in blocks if b["type"] == "tool_use"), None)
think = next((b for b in blocks if b["type"] == "thinking"), None)
if not (tool and think and think.get("signature")):
raise SystemExit("No thinking block with a signature: cannot run the test (try another model or prompt).")
def second(content):
return call([Q, {"role": "assistant", "content": content},
{"role": "user", "content": [{"type": "tool_result", "tool_use_id": tool["id"], "content": "Sunny, 18C"}]}])
s2, b2 = second(blocks)
print("2nd, signature unchanged:", s2, "(expect 200)")
bad = [dict(b, signature=b["signature"][:-8] + "AAAAAAAA") if b["type"] == "thinking" else b for b in blocks]
s3, b3 = second(bad)
print("3rd, signature tampered:", s3, b3.get("error", {}).get("message", ""), "(expect 400 Invalid `signature`)")
Read the result like this:
| 2nd call | 3rd call | Meaning |
|---|---|---|
| 200 | 400 "Invalid signature" |
Requests reach Anthropic's Claude and the relay passes thinking blocks through intact |
| 200 | 200 | Nobody is checking signatures: not a genuine Anthropic backend |
| 400 | 400 | The relay breaks thinking blocks (common when it converts to OpenAI format); Claude Code multi-turn sessions will hit this too |
What this test cannot tell you
Be clear about the limits, because nobody can prove more than this from the outside:
- It proves "Anthropic's Claude", not "which Claude". A relay that quietly sends your Opus requests to Sonnet, both on Anthropic, passes. To catch tier swaps, compare cost and latency against the official API on the same prompts, or run a small fixed set of hard questions from time to time.
- It's a point in time. Relays change backends. Run it again now and then, and on the models you actually use.
- It needs the native Anthropic format. OpenAI-format endpoints drop thinking signatures, so you can't run it there.
- Passing is "no evidence of substitution", not proof. That's also the conclusion of the academic audits; only hardware attestation would be real proof, and no reseller channel offers it.
Other things worth checking before you prepay anyone
These come from complaints that keep recurring in forums:
- Billing you can reconcile: per-request logs with input, output, cache-read and cache-write tokens and the cost of each request. Cache hit rates that never change from request to request are a red flag.
- Prices next to the official price, and notice before any change. "They silently changed my group/multiplier and burned my balance" is a common story.
- Small top-ups and a written refund policy for unused balance. Relays that vanish with prepaid balances are not rare.
- A clear statement of what is logged. Metadata (tokens, model, cost) is normal; request and response content should not be stored.
- Honest limits: rate limits and concurrency stated up front, not "upstream fluctuation" after the fact.
My own results
I ran the script against my service (APIVAI) through its public endpoint on Claude Haiku 4.5, and against Claude Sonnet 4.6 on the same backend:
- 1st call:
msg_011…ID,toolu_…tool ID,usagewithservice_tier,inference_geoand nestedcache_creation, and a thinking block with a signature. - 2nd call (unchanged signature): 200.
- 3rd call (tampered signature): 400
Invalid \signaturein \thinkingblock. - Not present:
anthropic-ratelimit-*response headers, so I can't offer those as a signal.
That is all the test shows, with the limits above. If you run it against any relay, including mine, I'd be glad to see the results in the comments, especially failures.
Top comments (0)