Public API & MCP server
The data behind this site is exposed through a small read-only HTTP API, plus an MCP server that wraps it for AI agents. No account, API key, or token is required. Paths are relative to the site root.
List repositories: GET /api/repos
Returns the ranked repository list, the same data the site's pages load. All parameters are optional: window (24h default · 7d · 30d · 365d), sort (hot default · github · momentum · stars · gems · cooling · busy · fastpr · pushed · oldest), collection, language, page, and kind (repos default · tag · search). With kind=tag pass the exact tag in tag; with kind=search pass the query in q.
Every request counts against a per-IP budget of 120 requests per minute. Answers are cached at the edge for 60 seconds and may be served stale for up to 10 more minutes (s-maxage=60, stale-while-revalidate=600). Over the budget the API answers 429 with a retry-after header.
Repository detail: GET /api/repos/{owner}/{name}
One repository with everything the site knows about it: identity, star totals, per-window score deltas, merge latency and contribution status, collections, topics, and the classification verdict. A 200 response is { repo, standing, mentions }; both standing and mentions are null when not measured yet: never zero. A repository that is not tracked, or is excluded by a display rule, answers 404 with {"error":"not found"}.
Collections: GET /api/collections
Returns { collections: [...] }: every collection as { slug, name, description, sortOrder }, read from the same table the collection pages use. It takes no parameters.
Early signals: GET /api/early
Returns { scorecard, signals, filter }: repositories we flagged as rising before GitHub Trending showed them, each with the measurements that raised it and its outcome once the window elapsed. The scorecard reports how often the flags were right, how many are still undecided and the date our real data starts. Optional query parameters: rule (accel · first_mover · small_and_hot · topic_tailwind), language (exact GitHub language name, e.g. Rust) and limit (1-50, default 25). An outcome whose status is null means the window has not elapsed: not measured, never a miss.
filter echoes what the request actually applied ({ rule, language, limit }), plus languages, the list of languages that have flags, so an empty signals array can be read correctly instead of guessed at. A rule or language we cannot match answers 400 with the accepted values; it never quietly returns nothing.
Feeds: /rss.xml, /rss/early, /rss/blog
The same flags as a feed, for readers and scripts that cannot hold an account: /rss/early (everything), /rss/early/<rule> (one rule: accel, first_mover, small_and_hot, topic_tailwind) and /rss/early?language=Rust. The same language spelling as the API. An item is identified by repository, rule and flag day, so subscribing to several early feeds at once does not show the same flag twice. A rule we do not know answers 404 rather than an empty feed, and /rss/early/topics carries the topics whose share of the week's growth is rising before their repositories trend. Topics are matched on width, so that number is a cohort measure, and it is labelled as one on /early/topics.
/rss/blog is the feed of our own writing: the analysis articles and the weekly Thai digest (/digest/<weekStart>) in one list, each item carrying the day it became readable. /rss.xml is the repositories moving right now, so a reader can subscribe to the data, to our writing, or to both.
Repository badge: GET /badge/{owner}/{name}.svg
A small SVG to paste into a README: a repository's total stars plus how many were gained in a window. The optional window query parameter picks the window: 7d default · 24h · 30d · 365d.
[](https://bestofgit-blue.vercel.app/projects/<owner>/<name>)
<a href="https://bestofgit-blue.vercel.app/projects/<owner>/<name>">
<img src="https://bestofgit-blue.vercel.app/badge/<owner>/<name>.svg?window=7d"
alt="Star and growth badge for <owner>/<name> on BestOfGit" />
</a>The same two snippets are printed ready to copy on every repository page, at /projects/<owner>/<name>, with that repository's name already filled in (“Embed this badge”).
Badges live outside /api/ (so image proxies can fetch them), are cached at the edge for 60 seconds (s-maxage=60, stale-while-revalidate=600), and are meant to be hotlinked directly. A repository we have not measured answers with a neutral “not on BestOfGit” badge (HTTP 200), so an embed never shows a broken image. The delta segment is coloured by momentum, the 7d-to-30d share, the same signal as the cards.
Build briefs: GET /api/brief
A build brief is one pair: a topic that is rising in this index, and a systems language (Rust, Go or Zig) that no tracked repository in that topic is written in. Each brief carries the numbers it was built from (repositories in the topic, how many of them are rising, stars gained, how many are written in that language), the projects that already tried with their status, why those are not enough, stack evidence from repositories solving a similar problem that are growing, the first measurable milestone and the date the brief becomes a miss. Nothing on a brief is written by a model: every sentence is a template filled with counts from the database, and each number names the table it came from.
GET /api/brieflists the published briefs with their numbers. Optionallimit(1-50, default 20).GET /api/brief?id=rust-ai-agentsreturns one brief in full, including the 20 nearest attempts and the closing check. An id that was never published answers404; briefs are never deleted once published.- A brief is published with a 90-day window. A repository counts as matching only when it carries the same topic and primary language and first appeared in this index after the brief was published: that is category matching, and it is never a claim that the brief caused the repository to exist. A brief with no match when its window closes is reported as a miss on
/brief.
MCP server (read-only)
An MCP server that wraps the endpoints above, so an AI agent can ask about repositories the same way a person browses the site. It speaks stdio, runs on your machine, and talks only to the public API, never the database. It exposes seven tools: list_trending, search_repos, list_repos_by_tag, get_repo, list_collections, list_early_signals, and build_brief (a deterministic brief built from a rising topic and the language missing from it, with the numbers each sentence is derived from).
The server lives at packages/mcp and runs with tsx:
# from the repo root — runs on your machine via tsx, no build step pnpm --filter @bestofgit/mcp start # point it at a local dev server instead of production BESTOFGIT_API_URL=http://localhost:3000 pnpm --filter @bestofgit/mcp start
BESTOFGIT_API_URL defaults to https://bestofgit-blue.vercel.app. It is the only environment variable it reads.
The internal contract: /docs
The complete, field-by-field data contract for these endpoints lives at /docs, which is an internal-only page (served only when running the app locally; it returns 404 on the public site). It is the authoritative shape reference if you need exact response fields. How rankings work is explained on the how-it-works page.