Skip to content
BetaBenchAlert v0.1 is in Beta.Numbers are real, but pages and rules can still change.See what changed

Developers

Read our lab results as JSON. Same numbers as the boards, taken every hour on our lab machine. No key, no account.

Before you start

Plain GET, open to all
Every path below answers a normal GET. There is no key, no token, and no rate-limit header. Nothing here writes anything.
Open and uncached
Every data route sends Access-Control-Allow-Origin: * and Cache-Control: no-store. We skip OPTIONS preflight, so keep request headers simple. Our numbers change every hour — cache on your side and do not poll faster.
Windows and rows
Most routes take ?hours= (bad values fall back to the default; anything over 720 is clamped). /api/runs takes ?limit= instead. Typical speed summaries use completed, correct current or raw-recovered compatible speed rows, including a served rate-limit warning; raw samples hold everything, fails included.

Your first call

No setup step comes before this. Paste either one and you have our newest numbers.

curl -s "https://benchalert.com/api/status"
const res = await fetch("https://benchalert.com/api/speed?hours=24");
const { models } = await res.json();
// Models with enough data are sorted by shortest typical total wait.
console.log(
  models[0]?.model,
  models[0]?.publishedLatencyMs,
  models[0]?.publishedVisibleWallTokensPerSec,
);

Data endpoints

Six JSON routes. Every board on this site is built from these and nothing else. Each entry folds a trimmed real response under Sample response.

Fields on a sample

The rows in /api/probes, /api/speed and /api/runs gained these in August 2026. They are additive: nothing that was there before moved or changed meaning, and an older row simply does not carry them.

model_verification_method
How the model identity was confirmed. Codex rows require an exact CLI pin plus an exact match in the authenticated account's model catalog before timing. If model_verified is not true, that row cannot publish a speed.
visible_wall_tokens_per_sec
Visible answer tokens divided by the full CLI start-to-finish time. This is the public writing-speed unit. wall_tokens_per_sec remains in raw rows as a diagnostic that includes hidden reasoning and must not be used for a public rank.
publishedLatencyMs / publishedVisibleWallTokensPerSec
The typical public figures in a model summary. Both stay null until 12 distinct scheduled checks completed correctly. Models with figures are sorted by publishedLatencyMs, shortest first.
coverage / availability
Coverage is tried scheduled slots divided by all scheduled slots. Availability is completed correct answers divided by all scheduled slots. A skipped app therefore cannot look perfect.
sequence_position / sequence_total
Where that target ran in the round. The order rotates each hour so one model is not always first or last.
api_dollars
What this request is worth at the vendor's published API list price, recomputed by us from the token counts. Null when our price table has no row for the model — never 0. This is the only dollar field the three apps share, and it is what the quota board sums.
cost_usd
The cost the app itself reported with the answer. Claude and Grok send one; Codex sends none, so the field is null on every Codex row. Null means not measured, not free.
rate_limit
The plan's usage gauge as the app reported it on this request: status, rate_limit_type (which window), utilization (0–1 of the window used), resets_at, surpassed_threshold, and the overage fields. Optional, and the three apps report different subsets.
service_tier
Which service tier served the request, when the app names one. It matters because some tiers draw on the quota at a different rate than standard.
usage_incomplete
True when the app's own usage report was missing a field we needed. Rows like this are kept, and the totals on /logs say how many there were.

Community reports

Opinion data from the Share your take button. It is a separate module and it can be switched off.

The visitor-reports module is switched off right now, so /api/sentiment answers 404 with { "enabled": false, "summaries": [] }. Those votes are opinion and never mix into our lab numbers.

Files, feeds, and images

Not JSON, but public and live in the same way.

The live badge

Paste it in a README or a status page. It redraws itself with our newest number every time it loads.

BenchAlert badge with the current typical visible writing speed

Markdown:

![BenchAlert](https://benchalert.com/badge)
![Fable 5 on BenchAlert](https://benchalert.com/badge?model=fable)

Model slugs

Use any of these in ?model= on the badge, in ?a=, ?b=, and ?c= on Compare, and in the /models/ address.

Featured

Other models

The full model id works too — ?model=claude-fable-5 lands on the same row as ?model=fable — and an unknown slug is not an error: the page just renders with no data. Retired models keep their slug and their old samples, but no new rows arrive.

If you republish our numbers

How to read this

These feeds carry our own measurements from one lab machine on one home connection, not the official speed of any model. There is no uptime promise: the lab can be down and a window can be empty. Write your client so an empty array is a normal answer, not a failure. Test kit bumps are announced on Changelog — watch it before you pin a parser.