Open any repo you own and click Insights → Traffic.
You get views, clones, unique visitors, top referrers, and popular paths. It's a genuinely good little dashboard.
It also forgets everything older than 14 days.
Not "archived somewhere." Not "available on request." Gone.
There is no API parameter that reaches further back, no export button, and no support ticket that recovers it.
If your project got picked up by a newsletter three weeks ago, the only record you have is the memory of seeing a spike.
That bothered me enough to start polling the endpoints myself and writing everything into Postgres.
Four endpoints. Every six hours. Per repository.
Sounds like an afternoon of work.
It was not an afternoon of work.
Here's what got me.
1. The traffic endpoints sit behind Administration permission
If you build this as a GitHub App, GET /repos/{owner}/{repo}/traffic/views requires Administration: read.
That's it.
That's the only scope GitHub offers for traffic data. There's no "Insights: read" and no narrower alternative.
Which means the installation screen shows your users a permission that reads, roughly:
This app can administer your repositories.
For an app whose entire job is counting page views.
I spent a while trying to find a way around it. There isn't one.
What I did instead was request nothing else:
no Contents
no Issues
no Actions
no Secrets
Just read-only Administration and read-only Metadata.
I also added a page to the site explaining exactly why the permission is required, because I knew that screen was going to be the number one reason someone bounces.
If you're building anything that touches traffic data, budget time for explaining that permission.
It's a product problem, not a technical one.
2. Daily uniques do not add up
This one I got wrong, shipped, and then had to go back and fix in five places.
The views endpoint with per=day gives you a row per day with count and uniques.
Views are a count, so summing them across days is fine.
Uniques are deduplicated within each day, so summing them across days gives you a number that doesn't really mean anything.
Someone who clones your repo on Monday, Tuesday, and Wednesday is one unique visitor in each of those three rows.
Add them together and you get:
3 unique visitors
Which is wrong.
And you can't fix it after the fact because GitHub never gave you the identity needed to deduplicate across days.
There is no correct sum.
So I stopped pretending there was one and labelled the column honestly:
Daily uniques, 30-day sum
Ugly label. Accurate label.
Every export also includes a limitations field explaining the same thing, because a CSV someone opens six months from now has no tooltip to hover over.
Worth checking your own dashboards for this.
I'd bet a lot of them quietly sum uniques.
3. Today's row is a moving target
Poll at 06:00 UTC and today's row is only a few hours old.
Poll again at 12:00 and the same date comes back with bigger numbers.
So the write can't be a simple insert.
It has to be an upsert keyed on:
(repository_id, metric_date)
And the whole pipeline has to agree on what a "day" means.
I run the scheduler in UTC and store UTC dates because GitHub's day boundaries are UTC. Any translation layer between those two is a bug waiting for a DST change.
Small thing.
But it'll cost you a day of confused debugging if you get it wrong, because the numbers are only slightly off.
And slightly-off numbers are the worst kind.
4. Referrers have no dates at all
traffic/popular/referrers returns a top 10 for the last 14 days.
No timestamps.
No per-day breakdown.
It's a single snapshot of a rolling window, and next week it'll be a different snapshot with no direct relationship to the previous one.
The only way to build a time series is to write down what you saw and when you saw it.
So I snapshot the top 10 daily, keyed by date.
The subtle part is deletion.
If Hacker News was in yesterday's top 10 and drops out today, a plain upsert leaves yesterday's HN row sitting in today's snapshot with a stale count.
Today's snapshot has to be replaced as a set:
delete whatever isn't in the new list
upsert the rest
do it all in one transaction
Same for popular paths.
5. Release downloads are cumulative
The releases API gives you download_count per asset.
It only ever goes up.
Useful for a total.
Useless for the actual question:
Did this release land better than the previous one?
To get velocity, you have to store the cumulative number every day and diff consecutive days yourself.
Nobody hands you a rate.
The part I'd tell past me
The API itself is small:
four endpoints
an installation token
a scheduled job
The work was almost entirely in the semantics:
which numbers can be added
which day a row belongs to
what to do when a value disappears from a list
And then there's the one thing you can't engineer around:
You cannot backfill.
GitHub does not give you day 15.
Whatever you didn't record, you don't have.
My first sync grabs the roughly 14 days GitHub still holds. Everything after that accumulates from the moment tracking starts.
Every chart is labelled with the date tracking started so nobody mistakes a gap for a zero.
Which is a slightly uncomfortable thing to build a product on.
The value only shows up later.
I turned this into RepoMeter: https://repometer.online
It's a hosted GitHub App that does all of the above for your repositories and keeps the history.
It's free, with no billing in the app right now.
It's read-only, and you can export everything to CSV and JSON so the archive isn't hostage to whether I keep the thing running.
But if you just want the data, the endpoints are right there, and this post contains most of what you need to know.
Start recording today rather than discovering in December that you have nothing from this autumn.
If you've hit other quirks in these endpoints, I'd like to hear them.
I'm fairly sure I haven't found them all.
Top comments (1)
The 14-day memory limit is exactly the kind of API detail that quietly shapes strategy. Archiving turns a temporary dashboard into a real trend line that can answer questions months later.