Skip to content
github

Read what the API will not tell you

The page plane, --deep, and github page.

Some of the most useful things on GitHub have no API route at all: trending, contribution calendars, the dependency graph, release download counts, the marketplace, and the language histogram. Others are served to a browser and not to /api.

This is not a scraping problem, because GitHub does not render its data as prose. A modern GitHub page carries a React payload as JSON in a <script type="application/json" data-target="react-app.embeddedData"> element, and the same route answers with that JSON directly when you ask for Accept: application/json. github reads those, not the text around them.

The whole page as JSON

github page gohugoio/hugo
github page https://github.com/golang/go/issues/1
github page https://github.com/trending

What comes back is everything the page carries, organised:

Section What it is
payload The embedded React payload, which is where most of the data lives
queries The preloaded Relay query results, which is where issue and pull request pages keep everything
structured_data GitHub's own schema.org block
linked_data Any ld+json on the page
meta The og:, twitter:, and octolytics- meta tags
microdata itemscope and itemprop, which is how profiles state their fields
partials Named HTML fragments the page inlines
fragments The deferred fragments the page names for itself and fetches separately

Narrow it with --section:

github page gohugoio/hugo --section meta
github page gohugoio/hugo --section fragments
github page gohugoio/hugo --section payload | jq '.props.initialPayload.repo'

Preloaded queries

Issue and pull request pages hydrate from Relay, so their data is in named query results rather than in the payload:

github page https://github.com/golang/go/issues/1 --query IssueViewerViewQuery

With no name, --query lists what the page has. Most page kinds preload nothing at all, and the command says so rather than printing an empty list.

The raw markup

When the question is about the HTML rather than about the data in it:

github page gohugoio/hugo --raw | grep -o 'data-target="[^"]*"' | sort -u

--deep

A page names fragments it does not include, and fetches them after first paint. The sponsor button, the language histogram, and the "used by" count all arrive that way.

--deep fetches them and merges what only they carry:

github repo gohugoio/hugo --deep

It costs extra requests, which is why it is not the default. Everything a record can state without them, it states without them.

Which surface answered

github routes

prints the whole mapping, one line per route, with the primary surface, the fallback, and a note where the choice is not obvious:

{"route":"/{owner}/{repo}/refs","primary":"xhr","fallback":"git","note":"names only, 6 KB against 588 KB"}
{"route":"/{owner}/{repo}/commit/{sha}","primary":"route-json","fallback":"raw","note":"the diff comes from .patch"}
{"route":"/{owner}/{repo}/graphs/contributors","primary":"xhr","note":"answers 202 while it computes, so it polls"}

Every record also carries a sources array naming the URLs it was built from, so you can always tell where a field came from without re-reading anything.

When a page moves

GitHub reorganises its pages, and when that happens this tool notices rather than returning something thin and plausible. github doctor checks the plane directly:

$ github doctor
{"name":"page","status":"ok","detail":"the react payload is where it should be, 4 keys in 298283 bytes"}

If that check fails, everything else is downstream of it. If it passes and one command is still empty, the selector for that one route moved, and github page <ref> shows what arrived instead.

Next: use it as a driver.