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: *andCache-Control: no-store. We skipOPTIONSpreflight, 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/runstakes?limit=instead. Summary blocks hold only clean current-kit rows; 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://durkbench.com/api/status"
const res = await fetch("https://durkbench.com/api/speed?hours=24");
const { models } = await res.json();
// models is sorted fastest first, so [0] is the current leader.
console.log(models[0]?.model, models[0]?.medianWallTokensPerSec, "wall tok/s");Data endpoints
Five JSON routes. Every board on this site is built from these and nothing else. Each entry folds a trimmed real response under Sample response.
- /api/today
The daily card as JSON: who wrote fastest in the last 24 hours, the full ranking, how each model moved since yesterday, the ping leader, and ready-made share text.
- Query
- None.
- Returns
- generatedAt, timezone, harnessVersion, dateLabel, weather, fastest, ranking, deltas, pingLeader, shareText, shareUrl
- Example
- /api/today
Sample response
{ "generatedAt": "2026-08-19T09:55:15.025Z", "harnessVersion": 5, "dateLabel": "Aug 19, 2026", "weather": { "level": "many", "label": "Lots of limits", "count": 14 }, "fastest": { "provider": "claude", "model": "fable", "medianWallTokensPerSec": 61.4 }, "ranking": [ "… one entry per model, fastest first …" ], "shareText": "… ready-made text you can post as-is …" } - /api/speed
The speed board plus every sample behind it. models holds one summary per pinned model, sorted the same way the board is. samples holds the raw rows.
- Query
- hours — how far back to look. Default 24, max 720.
- Returns
- generated_at, timezone, prompt, window_hours, models, samples
- Example
- /api/speed?hours=24
Sample response
{ "window_hours": 24, "prompt": "Count from 1 to 120 in plain text …", "models": [{ "provider": "claude", "model": "fable", "effort": "high", "harnessVersion": 5, "medianWallTokensPerSec": 61.4, "medianTtftMs": 4839, "reliability": 1, "ok": 1, "attempts": 1 }], "samples": [ "… every raw row, fails included …" ] } - /api/probes
Our ping log: one row per tool per round, with how long the tiny ok check took and whether it worked. This is the data behind the day strip on the home board.
- Query
- hours — how far back to look. Default 24, max 720.
- Returns
- generated_at, timezone, probe, window_hours, samples
- Example
- /api/probes?hours=24
Sample response
{ "probe": "Reply with exactly: ok", "window_hours": 24, "samples": [{ "started_at": "2026-08-19T09:00:04.786Z", "agent": "claude", "latency_ms": 5300, "answer_text": "ok", "status": "ok", "error_class": "ok" }] } - /api/runs
The run log: every time our lab started a round, what it aimed at, what came back, and the tokens it spent. Newest first.
- Query
- limit — how many runs to return. Default 100, max 2000. This route takes no hours.
- Returns
- generated_at, timezone, total_runs, runs
- Example
- /api/runs?limit=100
Sample response
{ "total_runs": 41, "runs": [{ "run_id": "2026-08-19T09:42:03.296Z-57988", "kind": "pulse", "harness_version": 5, "recorded": 1, "skipped": 0, "failed": 0, "output_tokens": 282, "results": [ "… one line per target …" ] }] } - /api/status
Lab health: the last pulse, whether the login check passed, recent rate limits, how old the pulse is, and when the next round is due.
- Query
- None.
- Returns
- generatedAt, lastPulse, preflight, recentRateLimits, lastErrorClass, pulseAgeMs, nextSlotAt
- Example
- /api/status
Sample response
{ "generatedAt": "2026-08-19T09:55:14.763Z", "preflight": { "ok": true, "claude": { "loggedIn": true, "subscription": "max" }, "codex": { "ok": true, "method": "chatgpt" }, "grok": { "loggedIn": true } }, "recentRateLimits": [ "… newest first …" ], "pulseAgeMs": 779000, "nextSlotAt": "2026-08-19T10:00:00.000Z" }
Community reports
Opinion data from the Share your take button. It is a separate module and it can be switched off.
- /api/sentiment
Visitor opinion from the Share your take button, counted per tool. This is not lab data — never mix it into the speed numbers. POST here is only for this site's own button, not a public write API. When the module is off, the route answers 404 with { "enabled": false }.
- Query
- hours — how far back to count. Default 24, max 720.
- Returns
- enabled, hours, summaries
- Example
- /api/sentiment?hours=24
Sample response
{ "enabled": true, "hours": 24, "summaries": [{ "provider": "claude", "total": 12, "counts": { "fast": 8, "fine": 3, "slow": 1, "broken": 0 }, "fastShare": 0.67 }] }
Files, feeds, and images
Not JSON, but public and live in the same way.
- /badgeimage/svg+xml
A live SVG badge, 20 pixels tall. With no model it shows the current fastest. Add ?model= with a slug from the table below to pin one row.
- /feed.xmlapplication/rss+xml
RSS 2.0. One item, rebuilt on every request, holding the current daily card and its share text. Poll it like a status feed, not an archive.
- /llms.txttext/plain
A plain-text summary written for AI agents: what we measure, the current test kit version, every page worth reading, the machine endpoints, and what each unit means.
- /today/opengraph-imageimage/png
The share image for the daily card, drawn with live numbers. No parameters.
- /api/ogimage/png
A compare share image for two models over the last 24 hours. Takes a and b only, defaulting to fable and sol. There is no c here, even though the compare page takes one.
The live badge
Paste it in a README or a status page. It redraws itself with our newest number every time it loads.
Markdown:
 
Model slugs
Use any of these in ?model= on the badge, in ?a=, ?b=, and ?c= on Compare, and in the /models/ address.
Featured
- opus
Opus 5 · claude · we ask for claude-opus-5
- fable
Fable 5 · claude · we ask for claude-fable-5
- sol
GPT-5.6 Sol · codex · we ask for gpt-5.6-sol
- grok-4.6
Grok 4.6 · grok · we ask for grok-4.6
Other models
- haiku
Haiku 4.5 · claude · we ask for claude-haiku-4-5
- sonnet
Sonnet 5 · claude · we ask for claude-sonnet-5
- luna
GPT-5.6 Luna · codex · we ask for gpt-5.6-luna
- terra
GPT-5.6 Terra · codex · we ask for gpt-5.6-terra
- gpt-5.5
GPT-5.5 · codex · we ask for gpt-5.5
- grok-4.5
Grok 4.5 · grok · we ask for grok-4.5
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
- Say the number came from DurkBench and link the page or endpoint you took it from.
- Name the test kit version and the window, for example: DurkBench, test kit v6, 24-hour window.
- Do not present our numbers as official vendor figures, and do not edit a number and keep our name on it. The rest of the rules are on Terms and Brand.
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.