犀速AI
Contents
Updated 2026-10-16

Models & Selection

How to check you are getting the real model

Asking who it is proves nothing. Check the tokenizer fingerprint, vendor-only features and each call by request_id, with a ready-to-run script.

When you use a relay service, it is natural to wonder: I am paying for model A — am I actually getting the cheaper model B? This article shows you how to check for yourself, without taking anyone's word for it (ours included).

A 3-step quick check

If you are short on time, these three steps are enough:

  1. Run the check script (about 5 minutes). Copy check_model.py from Method 1 below and run it. For GPT, all 5 tokenizer segments should match OpenAI's official tokenizer; for Claude, the second request should hit the prompt cache.
  2. Reconcile one call. Note the x-request-id response header and the usage object in the response body, then search that request ID under Request log in the console. The model and every token figure should match exactly, and the cost should work out from the formula below. See "Checking each call with the request_id, tokens and cost" below.
  3. If you suspect a smaller model, check the signal table. See "How to tell whether a model was swapped or downgraded" below.

None of this involves asking the model who it is. Its answer is not evidence, and the next sections explain why.

The short version

Method What it catches Effort
Run the script below (tokenizer + vendor-only features) A different vendor, e.g. another model passed off as Claude or GPT ⭐ copy and paste
Run the same hard questions side by side with the official API A downgrade within one vendor, e.g. a small model passed off as a large one ⭐⭐ needs an official account
Reconcile by request_id (response usage ↔ Request log ↔ unit price) Records that don't match: the logged model, token counts or cost differ from what you actually received ⭐ one search in the console
Ask "who are you", judge the writing style Almost nothing Don't

Why asking "who are you" proves nothing

What a model says about itself is very unreliable:

  • one line of system prompt can make any model claim any name;
  • many models were trained on conversations with other vendors' models, so genuine models often misidentify themselves;
  • the same goes for the knowledge cutoff — what the model reports is often wrong.

So "it says it is Claude" proves nothing, and neither does "it says it is GPT". What matters is what it cannot fake.

Step 1: get ready

  1. Install Python (3.8 or newer). On Windows, download it from python.org and tick Add Python to PATH during installation; macOS and Linux usually have it already.
  2. Open a terminal (Command Prompt or PowerShell on Windows) and run:
pip install tiktoken

This is OpenAI's official tokenizer; the script uses it as the reference answer.

Have three things ready — the script asks for them in turn. The base URL is this one:

https://api.xisu.ai/v1
  • API key: create one under API keys in the console;
  • Model name: copy it from the Models page with one click; don't type it by hand.

Step 2: save the script

Copy the whole block below and save it as a file named check_model.py (on your desktop, for example):

#!/usr/bin/env python3
# Model authenticity self-check. Standard library only; checking GPT needs: pip install tiktoken
import getpass
import json
import time
import urllib.error
import urllib.request
import uuid

# Mixed CJK/English, emoji, indentation, rare characters, digits: where tokenizers differ most.
# Compared segment by segment: another tokenizer can match OpenAI on one segment, or even on the total, by chance; matching on every segment is no coincidence.
SEGMENTS = [
    "鱻 龘 齉 —— 这是一段用来比对分词器的测试文本,量子纠缠。",
    "The quick brown fox jumps over the lazy dog, 1234567890 times.",
    "🙂🚀🧪 emoji 也算进来。🙂🚀🧪",
    "def tokenize(text):\n        return [ord(c) for c in text]    # 缩进与空格",
    "Pneumonoultramicroscopicsilicovolcanoconiosis, Ünïcödé, ½ ≈ 0.5",
]
SHORT_TEXT = "x"


def ask(prompt):
    value = ""
    while not value:
        value = input(prompt).strip()
    return value


def post(url, headers, body):
    try:
        req = urllib.request.Request(
            url,
            data=json.dumps(body).encode("utf-8"),
            headers={"content-type": "application/json", **headers},
            method="POST",
        )
        with urllib.request.urlopen(req, timeout=120) as resp:
            return resp.status, json.loads(resp.read().decode("utf-8"))
    except urllib.error.HTTPError as e:
        raw = e.read().decode("utf-8", "replace")
        try:
            return e.code, json.loads(raw)
        except ValueError:
            return e.code, {"raw": raw[:500]}
    except (urllib.error.URLError, TimeoutError, ValueError) as e:
        raise RuntimeError(f"Cannot reach {url}: {e} (check the base URL)")


def error_text(data):
    err = data.get("error")
    if isinstance(err, dict):
        return str(err.get("message", err))
    return str(err or data)[:300]


def load_tiktoken():
    try:
        import tiktoken
    except ImportError:
        print("  ⚠ tiktoken is not installed, cannot compare with the OpenAI tokenizer. Run: pip install tiktoken")
        return None
    # OpenAI models from GPT-4o onwards use o200k_base.
    return tiktoken.get_encoding("o200k_base")


class Client:
    def __init__(self, base, key, model):
        self.base = base.rstrip("/")
        self.root = self.base[:-3] if self.base.endswith("/v1") else self.base
        self.key = key
        self.model = model

    def openai_chat(self, content, **extra):
        body = {"model": self.model, "messages": [{"role": "user", "content": content}]}
        body.update(extra)
        body.setdefault("max_tokens", 16)
        status, data = post(
            self.base + "/chat/completions", {"authorization": "Bearer " + self.key}, body
        )
        # Reasoning models reject max_tokens and want max_completion_tokens.
        if status == 400 and "max_completion_tokens" in error_text(data):
            body["max_completion_tokens"] = body.pop("max_tokens")
            status, data = post(
                self.base + "/chat/completions", {"authorization": "Bearer " + self.key}, body
            )
        return status, data

    def anthropic_messages(self, content, system=None):
        body = {
            "model": self.model,
            "max_tokens": 16,
            "messages": [{"role": "user", "content": content}],
        }
        if system is not None:
            body["system"] = system
        return post(
            self.root + "/v1/messages",
            {"x-api-key": self.key, "anthropic-version": "2023-06-01"},
            body,
        )

    def input_tokens(self, vendor, content):
        if vendor == "claude":
            status, data = self.anthropic_messages(content)
            field = ("usage", "input_tokens")
        else:
            status, data = self.openai_chat(content)
            field = ("usage", "prompt_tokens")
        if status != 200:
            raise RuntimeError(f"Request failed (HTTP {status}): {error_text(data)}")
        value = (data.get(field[0]) or {}).get(field[1])
        if not isinstance(value, int):
            raise RuntimeError(f"The response has no {field[0]}.{field[1]}; nothing to compare")
        if vendor == "claude":
            # Long inputs may be cached automatically; input_tokens then counts only the uncached part.
            usage = data.get("usage") or {}
            value += (usage.get("cache_creation_input_tokens") or 0) + (usage.get("cache_read_input_tokens") or 0)
        return value


def official_claude_counts(official_key, model):
    def count(content):
        status, data = post(
            "https://api.anthropic.com/v1/messages/count_tokens",
            {"x-api-key": official_key, "anthropic-version": "2023-06-01"},
            {"model": model, "messages": [{"role": "user", "content": content}]},
        )
        if status != 200:
            raise RuntimeError(f"The official API returned HTTP {status}: {error_text(data)}")
        return data["input_tokens"]

    base = count(SHORT_TEXT)
    return [count(text) - base for text in SEGMENTS]


def test_tokenizer(client, vendor, results):
    print("\n[Tokenizer fingerprint]")
    # Each segment is measured against the single letter "x", so the fixed per-message overhead cancels out.
    base = client.input_tokens(vendor, SHORT_TEXT)
    diffs = [client.input_tokens(vendor, text) - base for text in SEGMENTS]
    print(f"  Tokens per segment reported by this provider: {diffs}")
    if min(diffs) <= 0:
        results.append(("Tokenizer", "suspicious", f"a segment cost no more tokens than a single letter ({diffs}); the counts cannot be trusted"))
        return

    enc = load_tiktoken()
    expected = None
    if enc is not None:
        expected = [len(enc.encode(text)) - len(enc.encode(SHORT_TEXT)) for text in SEGMENTS]
        print(f"  Tokens per segment by the OpenAI tokenizer: {expected}")

    if vendor == "gpt":
        if expected is None:
            results.append(("Tokenizer", "skipped", "tiktoken not installed"))
        elif diffs == expected:
            results.append(("Tokenizer", "pass", f"all {len(SEGMENTS)} segments match the official OpenAI tokenizer"))
        else:
            results.append(("Tokenizer", "suspicious", f"OpenAI would give {expected}, this gave {diffs}"))
        return

    if expected is not None and diffs == expected:
        results.append(("Tokenizer", "suspicious", "not sold as GPT, yet every segment tokenizes exactly like OpenAI"))
    elif expected is not None:
        differ = sum(1 for a, b in zip(diffs, expected) if a != b)
        results.append(("Tokenizer", "pass", f"{differ} of {len(SEGMENTS)} segments differ from OpenAI, as expected"))

    if vendor == "claude":
        official_key = getpass.getpass(
            "  Paste an official Anthropic API key for an exact comparison (hidden; press Enter to skip): "
        ).strip()
        if official_key:
            try:
                official = official_claude_counts(official_key, client.model)
            except RuntimeError as e:
                print(f"  ⚠ {e}")
                return
            print(f"  Tokens per segment by Anthropic's official count: {official}")
            if all(abs(a - b) <= 1 for a, b in zip(official, diffs)):
                results.append(("Official Claude count", "pass", "matches Anthropic's official count"))
            else:
                results.append(("Official Claude count", "suspicious", f"official {official}, this gave {diffs}"))


def test_logprobs(client, results):
    print("\n[Per-token probabilities (logprobs)]")
    status, data = client.openai_chat("Describe the giant panda in one sentence.", logprobs=True, max_tokens=30)
    items = None
    if status == 200:
        items = (((data.get("choices") or [{}])[0].get("logprobs")) or {}).get("content")
    if not items:
        why = error_text(data) if status != 200 else "no logprobs in the response"
        print(f"  Not returned: {why}")
        results.append((
            "logprobs", "info",
            "not returned. Reasoning GPT models do not support it officially; a regular chat GPT model should",
        ))
        return

    enc = load_tiktoken()
    if enc is None:
        results.append(("logprobs", "skipped", "tiktoken not installed"))
        return
    # Every token the model emits must be a single entry in the OpenAI vocabulary.
    bad = 0
    for item in items:
        raw = bytes(item["bytes"]) if item.get("bytes") else item["token"].encode("utf-8")
        try:
            enc.encode_single_token(raw)
        except KeyError:
            bad += 1
    print(f"  {len(items)} tokens, {bad} not in the OpenAI vocabulary")
    if bad == 0:
        results.append(("logprobs", "pass", f"all {len(items)} tokens are in the OpenAI vocabulary"))
    else:
        results.append(("logprobs", "suspicious", f"{bad} token(s) not in the OpenAI vocabulary"))


def test_prompt_cache(client, results):
    print("\n[Prompt caching]")
    if input("  This sends two requests of over 10,000 tokens each (most of the second is billed at the cache rate), roughly the cost of one long chat. Continue? [Y/n] ").strip().lower() == "n":
        results.append(("Prompt caching", "skipped", "skipped by you"))
        return
    # A random prefix guarantees the first request writes the cache rather than hitting an old one.
    lines = [f"Reference line {i:04d}: the quick brown fox jumps over the lazy dog." for i in range(500)]
    system = [{
        "type": "text",
        "text": f"[{uuid.uuid4()}]\n" + "\n".join(lines),
        "cache_control": {"type": "ephemeral"},
    }]
    rounds = []
    for n in (1, 2):
        start = time.time()
        status, data = client.anthropic_messages("Reply with OK only.", system=system)
        elapsed = time.time() - start
        if status != 200:
            print(f"  Request {n} failed (HTTP {status}): {error_text(data)}")
            results.append(("Prompt caching", "skipped", "request failed"))
            return
        usage = data.get("usage") or {}
        write = usage.get("cache_creation_input_tokens") or 0
        read = usage.get("cache_read_input_tokens") or 0
        print(f"  Request {n}: cache write {write}, cache read {read}, {elapsed:.1f} s")
        rounds.append((write, read))
        time.sleep(1)

    if rounds[1][1] > 0:
        results.append(("Prompt caching", "pass", f"second request read {rounds[1][1]} tokens from cache"))
    else:
        results.append(("Prompt caching", "suspicious", "no cache hit; genuine Claude would hit"))


def main():
    print("Model authenticity self-check\n")
    base = ask("Base URL (from the docs, ends with /v1): ")
    key = getpass.getpass("API key (hidden; paste and press Enter): ").strip()
    model = ask("Model name to check (copy it from the Models page): ")

    name = model.lower()
    if "claude" in name:
        vendor = "claude"
    elif "gpt" in name or name.startswith(("o1", "o3", "o4")):
        vendor = "gpt"
    else:
        vendor = "other"
    label = {"claude": "Claude", "gpt": "GPT", "other": "another vendor"}[vendor]
    print(f"\nChecking as {label}.")

    client = Client(base, key, model)
    results = []
    try:
        test_tokenizer(client, vendor, results)
        if vendor == "gpt":
            test_logprobs(client, results)
        if vendor == "claude":
            test_prompt_cache(client, results)
    except RuntimeError as e:
        print(f"\n✖ {e}")
        return

    print("\n================ Results ================")
    marks = {"pass": "✅", "suspicious": "❌", "info": "ℹ️", "skipped": "⏭"}
    for item, verdict, note in results:
        print(f"{marks[verdict]} {item}: {verdict} - {note}")
    if any(v == "suspicious" for _, v, _ in results):
        print("\nSuspicious results. Re-run at a few different times; if they hold, send this output to the provider and ask for an explanation.")
    else:
        print("\nNothing suspicious found.")


if __name__ == "__main__":
    main()

Step 3: run it

In the terminal, go to the folder that holds the file and run:

python check_model.py

(On macOS / Linux, use python3 if python is not found.)

Paste the base URL, key and model name when asked. The key is not shown as you type; just paste it and press Enter. The script only sends a few very short requests; the Claude check adds one step that costs about as much as one long chat, and it asks before running it.

Step 4: read the results

At the end it prints a results table. Each line is ✅ pass, ❌ suspicious, ℹ️ for information only, or ⏭ skipped:

Check Applies to ✅ means ❌ means
Tokenizer every model GPT: token counts match OpenAI's official tokenizer exactly; other models: they differ from OpenAI (as they should) GPT does not match the official count; or a model not sold as GPT tokenizes exactly like OpenAI
logprobs GPT every token the model emits is in OpenAI's vocabulary some tokens are not in OpenAI's vocabulary, so it is not an OpenAI model behind it
Prompt caching Claude the second request hit the cache neither request hit the cache, which genuine Claude would not do

Don't jump to conclusions on a ❌. Run it two or three more times at different times of day; if the result holds, send the full output to the provider and ask for an explanation.

Why these checks are hard to fake

Skip this section if you don't need the reasoning.

The tokenizer is a model's fingerprint

Models don't read text character by character. They first split it into tokens, and billing is per token. Every vendor splits text differently: the same mix of Chinese, English and emoji comes out as one number at OpenAI, another at Claude, and different numbers again at Gemini, DeepSeek or Qwen. Even within one vendor, a new model generation may bring a new tokenizer.

The script prepares 5 deliberately awkward segments (rare characters, emoji, code indentation, a very long word…), sends each one on its own, and subtracts a request containing only the letter x. The fixed overhead of the message format is the same every time, so it cancels out; what remains is the token count of that segment. It compares segment by segment rather than the total: another tokenizer can match OpenAI on one segment, or even on the sum, by chance, but matching on all 5 is no coincidence.

  • GPT: OpenAI's tokenizer is public (that is the tiktoken you just installed), and models from GPT-4o onwards use o200k_base, so every segment must match exactly. If OpenAI ever moves new models to a new tokenizer, this check will stop matching — tiktoken's own documentation is the authority then.
  • Claude: Anthropic does not publish an offline tokenizer, so the script can only confirm it is "not OpenAI's split". If you have an official Anthropic API key, the script also calls the official counting endpoint (count_tokens, free of charge) to get the exact reference answer. ⚠️ The referee must be the official endpoint — a counting endpoint offered by any relay (including us) cannot be used to verify that same relay.

This check assumes the token counts are reported by the model itself. If a provider estimates token counts itself, this check measures its estimator rather than the model — but that in itself means it is not billing by real usage, which is worth asking about too.

logprobs: every token must be in the vocabulary

OpenAI's regular chat models can return "the probability of each output token" (logprobs). Every token a genuine GPT emits is necessarily an entry in OpenAI's vocabulary; another vendor's model produces tokens that don't line up with OpenAI's vocabulary, which is very hard to fake convincingly.

OpenAI's reasoning models don't support logprobs officially, so not getting them there is normal; the script marks it as "info".

Prompt caching: a Claude-native feature

Claude supports prompt caching: send the same long prefix twice in a row and the second request reads it from cache, with cache_read_input_tokens above 0 in the response. The script puts a random string at the start of the prefix every time, so the first request is always a fresh write and only a hit on the second counts.

When Claude is called through the OpenAI-compatible endpoint (/v1/chat/completions), Claude-specific features like this do not take effect — that comes from translating between the two protocols, not from a swapped model. The script checks Claude through the native Anthropic endpoint /v1/messages; see the article on the Anthropic native endpoint.

Checking each call with the request_id, tokens and cost

The check script answers "is this the model I asked for?". This section answers "was this call recorded and billed correctly?". Three things have to agree: the usage in the response, the same call in the console's Request log, and the cost you work out from the unit prices.

Step 1: get the request_id and usage from the response

Every chat response carries an x-request-id header; that is the ID of the call. The usage object in the body breaks down its tokens. Put your key in the XISU_API_KEY environment variable first (PowerShell: $env:XISU_API_KEY="your key"; macOS / Linux: export XISU_API_KEY="your key"), then run the code below.

OpenAI-compatible endpoint, with the official SDK (pip install openai):

import os
from openai import OpenAI

client = OpenAI(base_url="https://api.xisu.ai/v1", api_key=os.environ["XISU_API_KEY"])

raw = client.chat.completions.with_raw_response.create(
    model="gpt-6-luna",
    messages=[{"role": "user", "content": "Describe the giant panda in one sentence."}],
    max_tokens=50,
)
print("x-request-id:", raw.headers.get("x-request-id"))
print(raw.parse().usage)

Native Anthropic endpoint /v1/messages, standard library only:

import json, os, urllib.request

req = urllib.request.Request(
    "https://api.xisu.ai/v1/messages",
    data=json.dumps({
        "model": "claude-haiku-5-5",
        "max_tokens": 50,
        "messages": [{"role": "user", "content": "Describe the giant panda in one sentence."}],
    }).encode(),
    headers={
        "x-api-key": os.environ["XISU_API_KEY"],
        "anthropic-version": "2023-06-01",
        "content-type": "application/json",
    },
)
with urllib.request.urlopen(req) as resp:
    print("x-request-id:", resp.headers.get("x-request-id"))
    print(json.dumps(json.load(resp)["usage"], indent=2))

With curl, add -i to print the headers along with the body and look for the x-request-id line. The id field in the response body is something else; use the header value when reconciling.

How the usage fields map to the token breakdown in the Request log:

Request log OpenAI-compatible usage Native Anthropic usage
Input prompt_tokens input_tokens + cache_read_input_tokens
incl. cache hits prompt_tokens_details.cached_tokens cache_read_input_tokens
Cache write prompt_tokens_details.cache_write_tokens (when present) cache_creation_input_tokens
Output completion_tokens output_tokens
incl. reasoning completion_tokens_details.reasoning_tokens —

The two "incl." lines are already included in the line above them, so don't add them again. Cache writes are not part of Input and get their own line. Anthropic's input_tokens counts only the uncached part, so add cache_read_input_tokens back when comparing Input.

Step 2: find the same call in the Request log

Open Request log in the console sidebar and paste the request_id into the Request ID box. Each row shows the time and request ID, the model and key prefix, the token breakdown (Input / incl. cache hits / Output / incl. reasoning / Cache write / Total), latency, cost (in points) and status.

Searching the Request log by request ID

Compare line by line:

  • Model: matches the model you sent;
  • Token figures: match the usage from step 1;
  • Status: failed requests are listed too, with a cost of "0 points" and the note "No output". They are not billed.

A few things that are easy to misread:

  • A call you just made may not be there yet. Check the "Data as of" time on the page and refresh in a moment.
  • The log keeps only about the last 40 days.
  • A row marked Estimated under its tokens does not carry exact counts reported by the model, so don't use it for precise comparisons (including the script's tokenizer check).
  • "—" means the figure is unavailable; "0" means a real zero. See Reconciling usage and bills for more.

Step 3: work the cost back out from unit prices

Unit prices are on the Models page, in "points per 1M tokens", with separate prices for input, output, cache reads and cache writes. Costs in the console are shown in points too; ¥1 = 100,000 points.

cost (points) = [ (input − cache hits) × input price
                + cache hits × cache read price
                + cache writes × cache write price
                + output × output price ] ÷ 1,000,000

A worked example. The prices below are example prices chosen to show the arithmetic. They are not the real price of any model; see Models for current prices.

Say the example prices are: input 200,000, cache read 20,000, output 800,000 (points per 1M tokens). One call shows input 12,000 (of which cache hits 10,000), output 500 and no cache writes.

Part Calculation Points
Uncached input (12,000 − 10,000) × 200,000 ÷ 1,000,000 400
Cache hits 10,000 × 20,000 ÷ 1,000,000 200
Output 500 × 800,000 ÷ 1,000,000 400
Total 1,000 points (= ¥0.01)

When comparing:

  • points are displayed as whole numbers, so a difference of up to 1 point from your own figure is normal;
  • reasoning tokens are already part of output; don't add them again;
  • a call covered by a credit pack shows 0 in the cost column, with "Covered by package … points" underneath;
  • failed requests are not billed; a stream that is cut off midway is billed for what had been generated.

If the three don't agree, open a ticket with the request_id, the raw usage from the response and your working, and we will go through that call item by item.

Method 2: catching a downgrade within one vendor

The tokenizer catches "a different vendor", but not "a small model passed off as a large one from the same vendor" — they often share a tokenizer. For that you compare capability:

  1. Prepare 10–20 questions that separate large models from small ones: multi-step reasoning, a longer piece of code, an instruction with many detailed requirements (e.g. "write 5 sentences of exactly 12 words each; the 3rd ends with a question mark").
  2. Run the same set at the same time on the official API and on the service being tested, 3 times each, and compare pass rates. One or two runs mean nothing; model output varies from run to run.
  3. Look at speed too: smaller models are usually noticeably faster. If the tested service streams much faster than the official API on the same questions, that is worth questioning.

This needs an official account for comparison. Without one, you can at least run the same questions on a cheap small model and an expensive large one and confirm there is a real gap — if the expensive model performs exactly like the small one, that is a signal too.

How to tell whether a model was swapped or downgraded

A swap comes in two kinds. A different vendor (another vendor's model passed off as Claude or GPT) is what the check script catches. A downgrade within one vendor (a small model passed off as a large one) usually shares the tokenizer, so only a capability comparison like Method 2 above can catch it. The table below lists common signals, what may cause them and how to check. No single signal is proof; re-test at a different time at least once.

Signal Possible cause How to check
One or more GPT tokenizer segments don't match OpenAI's official tokenizer It is not an OpenAI model behind it; or OpenAI moved the new model to a new tokenizer Re-run 2–3 times at different times; check tiktoken's documentation for which encoding that model uses
Not sold as GPT, yet all 5 segments tokenize exactly like OpenAI An OpenAI model is behind it Re-run; for Claude, if you have an official Anthropic key, let the script compare with the official count_tokens
A segment costs no more tokens than a single letter, or the counts look implausible The token counts are estimated rather than reported by the model itself Check in the Request log whether those calls are marked Estimated; re-run at another time
A regular chat GPT model returns no logprobs, or some tokens are not in OpenAI's vocabulary It is not an OpenAI model (reasoning GPT models don't return logprobs officially, so that alone is not a signal) Confirm whether the model is a reasoning model; re-run
Neither Claude request hits the prompt cache It is not genuine Claude; or the call went through the OpenAI-compatible endpoint, where Claude-specific features don't apply Confirm the request used /v1/messages (the script does this automatically); re-run
Pass rate on the same hard questions is clearly below the official model A smaller model from the same vendor Follow Method 2: 10–20 questions, run 3 times each on the official API and on the tested service at the same time, compare pass rates
Much faster output than the official model on the same questions Possibly a smaller model Compare several times, at the same time of day, on the same questions; a single fast or slow run is affected by network and load and proves nothing
An expensive large model performs almost exactly like a cheap small one from the same vendor Possibly swapped for the small model Run the same questions on both and confirm there is a real gap
The model or tokens in the Request log don't match the usage in the response A recording error, or the model that ran differs from the one requested Find the call by request_id as in the previous section and open a ticket with the raw usage
An extra "Served by …" line appears under the model name in the Request log The call was served by a model with a different name from the one requested Check the model in your request; if you named a specific model and still see this line, open a ticket with the request_id

Asking the model who it is and reading the model field in the response are deliberately missing from this table: one line of system prompt changes the first, and one string on the server changes the second. Neither is evidence.

Methods that don't work

These are often used to "check the goods", but are all easy to fake or misread; treat them as hints at most:

  • asking who it is, or its knowledge cutoff — see above;
  • the model field or ID prefix in the response — the server can change a string;
  • writing style (lists, emoji) — the same model writes completely differently under another system prompt;
  • viral puzzles like "how many r's in strawberry" — current models from every vendor get them right, so they tell nothing apart.
  • whether it can call tools or return strict JSON — this only shows the capability exists; many cheap small models can do both, so it cannot reveal a swap. Asking for "JSON only" in the prompt tests whether the model follows instructions, not what the API supports; to test structured output, use the API parameter response_format (json_schema with strict).

Testing on this platform

  • Every call can be found by its request ID under Request log in the console; compare its token breakdown and cost with the script and the usage in the response, as described above.
  • Testing uses the same key as normal calls and is billed at normal prices; the script's requests are short, so the cost is very low.
  • If you get a ❌, open a ticket with the script output and we will explain each item.