What do GitHub download statistics really mean?

After a summer being busy with other stuff, I’m looking forward to an autumn of blogging.

In this blog post, I’ll share a fun investigation into GitHub download statistics, especially their meaning. For every GitHub release, you can use the GitHub REST API to get the release count. But does this number represent the number of downloads started? Ended downloads? Or something else? I couldn’t find anything in the API docs, so I started a short investigation.

TL;DR: The download count for releases on GitHub seems to include simple release link accesses, with deduplication.

Why would you care about the data? Download statistics are used by projects to gather insights into which releases are used most and are sometimes used to justify focusing on a specific version or dropping support for a specific platform.

The investigation started by adding a few releases to a new GitHub project (download-count-experiment) to have releases that probably nobody else downloads. But how do we get the statistics, and what data does it contain?

Getting Data

You can get the download count of all releases by using the GitHub API:

gh api /repos/parttimenerd/download-count-experiment/releases

Or via curl:

curl -L \
  -H "Accept: application/vnd.github+json" \
  -H "Authorization: Bearer <YOUR-TOKEN>" \ # optional, helps against rate limits
  -H "X-GitHub-Api-Version: 2026-03-10" \ # optional if you're just experimenting
  https://api.github.com/repos/parttimenerd/download-count-experiment/releases

This gives you, for every release, something like:

[
  {
    "url": "https://api.github.com/repos/parttimenerd/download-count-experiment/releases/383918188",
    "assets_url": ...,  "upload_url": ...,  "html_url": ...,
    "id": 383918188,
    "author": { "login": "parttimenerd", ... },
    "node_id": "RE_kwDOUQ67_s4W4iBs",
    "tag_name": "v-zero-abort-2",
    "target_commitish": "main",
    "name": "zero-abort (run 2)",
    "draft": false, "immutable": false, "prerelease": false,
    "created_at": "2026-09-07T07:43:26Z", "updated_at": ..., "published_at": ...,
    "assets": [
      {
        "url": "https://api.github.com/repos/parttimenerd/download-count-experiment/releases/assets/548405675",
        "id": ..., "node_id": ...,
        "name": "release-1mb.bin",
        "label": "",
        "uploader": { "login": "parttimenerd", ... },
        "content_type": "application/octet-stream",
        "state": "uploaded",
        "size": 1048576,
        "digest": "sha256:7a83c647e5f7235db8edf0db4067d6a3a73d333e0dc34afe7bf1fcfdfc42e3dd",
        "download_count": 2,
        "created_at": ..., "updated_at": ...,
        "browser_download_url": ...
      }
    ],
    "tarball_url": ..., "zipball_url": ..., "body": null
  },
...

This includes the download_count property for every release.

To trigger a download, e.g., of the timing release, just fetch https://github.com/parttimenerd/download-count-experiment/releases/download/v-timing/release-1mb.bin. More on that later.

Granularity of the Data

You might wonder how these download counts change. I couldn’t find anything on the internet, so I did my own testing. Apparently, GitHub uses a 10-minute (~621-second) cool-down window per IP and releases after each release, so new downloads to the same release are not counted. This could be to prevent download retries from appearing in the data. But it also effectively deduplicates the data in batches.

Important: That is only my feeble understanding after some empirical testing; I don’t have any deeper insights. My insights are probably only an approximation of GitHub’s behavior and might be totally wrong.

This should usually not be a problem for release download counting, especially because I haven’t told you about what the counter actually counts:

What the Counter Counts

Assuming we ignore the deduplication, what does the counter actually count? When I first thought about the download counter, I wondered whether it counts:

  • Any fetch of the download URL
  • Downloads that download at least one kilobyte
  • Downloads that download more than half of the release asset
  • Downloads that finish

To my utter surprise, it was the first entry. It is important here to note that a URL like https://github.com/parttimenerd/download-count-experiment/releases/download/v-timing/release-1mb.bin just forwards to a much more complicated raw asset URL like https://release-assets.githubusercontent.com/github-production-release-asset/1359920126/799302bf-af50-419c-a84a-0e87a82b48c0?sp=r&sv=2018-11-09&sr=b&spr=https&se=2026-09-08T08%3A33%3A39Z&rscd=attachment%3B+filename%3Drelease-1mb.bin&rsct=application%2Foctet-stream&[…]&response-content-disposition=attachment%3B%20filename%3Drelease-1mb.bin&response-content-type=application%2Foctet-stream (shortened for readability), with an expiry that is usually one hour (extended to around 90 minutes to account for clock skew). I built a script that you can also find in the download-count-experiment GitHub repository that starts downloads on different assets in different ways. This way, I found out that GitHub just tracks how many times the short release URL was accessed, so the download statistics don’t care how much data from any asset was downloaded, not even whether the raw asset URL was accessed. It is important to note that GitHub’s documentation never seems to mention what the download count truly represents, which is fine yet still confusing.

But this means that gaming the download count is trivial; you need to fetch the release URL. This incurs only minimal traffic to GitHub itself (but may prompt them to provision the raw download).

Should you care?

Kind of. But you probably shouldn’t have trusted the numbers anyway if you did. A simple misconfigured CI pipeline can skew any download count by inflating it with spurious downloads. A CI that caches downloads can do this too. So the data is next to meaningless, because the numbers lack context. What we should care about is true usage, but that is usually far harder to achieve without tracking and is therefore seldom done by smaller projects.

Conclusion

It’s easy to assume that the statistics you get from platforms like GitHub match your mental model. Most of the time, it doesn’t (perfectly). This investigation showed that you should always check what statistics truly represent. I didn’t expect GitHub to count simple access to release links, proving my point.

I hope you liked my blog post on this GitHub topic. Come by in a week or two for something fun related to OpenJDK.

Author

  • Johannes Bechberger

    Johannes Bechberger is a JVM developer working on profilers and their underlying technology in the SapMachine team at SAP. This includes improvements to async-profiler and its ecosystem, a website to view the different JFR event types, and improvements to the FirefoxProfiler, making it usable in the Java world. His work today comprises many open-source contributions and his blog, where he regularly writes on in-depth profiling and debugging topics. He also works on hello-ebpf, the first eBPF library for Java. His most recent contribution is the new CPU Time Profiler in JDK 25.

    View all posts

New posts like these come out at least every two weeks, to get notified about new posts, follow me on BlueSky, Twitter, Mastodon, or LinkedIn, or join the newsletter:

Leave a Reply

Your email address will not be published. Required fields are marked *