Four endpoints
every path is a GET, and this build answers them from the frozen capture in data/| Endpoint | Returns | Why |
|---|---|---|
| /api/tools | tool records | the whole board, with every raw signal. /api/tools |
| /api/tools/{slug} | one record | one tool, its answer sentence and up to 90 nightly captures. /api/tools/spec-kit |
| /api/dead | tool records | everything currently scored dead, most starred first, 27 today. /api/dead |
| /api/stats | 4 counts | the counts, the timestamp of the last nightly refresh, and how the population is assembled. /api/stats |
Responses are cached at the edge for an hour; the underlying verdicts move once a night.
/api/tools
the whole board, with every raw signalGET/api/tools
| Parameter | Accepts | Optional |
|---|---|---|
| verdict | maintained, stale or dead, exact match | yes, and every one is applied together |
| category | spec-framework, orchestrator, cli-agent, ide-extension, harness | yes, and every one is applied together |
| q | substring over name, slug, repository, tagline and aliases | yes, and every one is applied together |
| order | freshness, stars, days_since_push or name (default stars) | yes, and every one is applied together |
| dir | asc or desc (default desc, nulls always last) | yes, and every one is applied together |
| limit | integer, default 200, capped at 2000 | yes, and every one is applied together |
{
"count": 200,
"total": 345,
"limit": 200,
"refreshed_at": "2026-09-13T06:20:39.935+00:00",
"generated_at": "when this response was built, not when the board was read",
"tools": [
{
"slug": "spec-kit",
"name": "Spec Kit",
"repo_full_name": "github/spec-kit",
"category": "spec-framework",
"verdict": "maintained",
"freshness": 100,
"reasons": [{ "fact": "last commit", "effect": 0, "detail": "Pushed 1 day ago." }],
"verdict_at": "...",
"days_since_push": 1,
"recent_commits": 100,
"last_release_tag": "v0.15.1",
"issue_response_hours": 0.8,
"contributors_90d": 17,
"bus_factor": 6,
"archived": false,
"license": "MIT",
"stars": 124895
}
]
}/api/tools/{slug}
one tool, its answer sentence and up to 90 nightly capturesGET/api/tools/{slug}
{
"tool": { /* the same shape as above */ },
"answer": "Yes. Spec Kit is maintained. The last commit was yesterday, and it scores 100/100.",
"history": [
{ "captured_on": "2026-09-13", "verdict": "maintained", "freshness": 100 }
],
"refreshed_at": "2026-09-13T06:20:39.935+00:00",
"generated_at": "..."
}/api/dead
everything currently scored dead, most starred first, 27 todayGET/api/dead
{
"count": 27,
"refreshed_at": "2026-09-13T06:20:39.935+00:00",
"generated_at": "...",
"tools": [ /* ... */ ]
}/api/stats
the counts, the timestamp of the last nightly refresh, and how the population is assembledGET/api/stats
{
"total": 345,
"maintained": 299,
"stale": 19,
"dead": 27,
"refreshed_at": "2026-09-13T06:20:39.935+00:00",
"inclusion": { "seeded": 82, "discovered": 263, "queries": 9 }
}The two dates, and why there are two
Responses are cached and revalidated hourly; the underlying verdicts change once a night. That is why the list endpoints carry two dates and not one. refreshed_at is when these rows were last read from the GitHub API and is the only one that means freshness, while generated_at is when the response itself was assembled and moves with the cache. Poll on the first and ignore the second.
The only writer is the nightly refresh job. Nothing on this site accepts input except the claim check and the change-alert signup, which write one row each about a listing somebody has proved is theirs, and that is also why there is no key to issue.
The method page writes out what the figures in a record mean, and every tool’s own record shows the arithmetic that produced its freshness.