GitHub Stats Card API: Cards That Stay Current

GitHub Stats Card API: Cards That Stay Current

A GitHub stats card API renders a card as an image and hands the bytes straight back, so a README can point at a URL instead of a PNG somebody committed six months ago. You design the card once, mark the numbers dynamic, and send a different username or repo per call.

The rendering is the easy half. The half worth writing down is what happens after you paste that URL into a README, because GitHub doesn't fetch your image the way a browser does, and that changes both how fresh the card looks and how much of your allowance it spends.

If you only want one card for your own profile, the GitHub stats card generator and the repo language chart do it free in the browser, and the walkthrough for those covers the markdown embed. This post is for the case where you own 40 repos and want them all to look the same.

One design every repoOne design, every repo

Dimensions are fixed when you build a design and can't be overridden in a render call. For cards that's the right constraint: every repo's card comes out at the same size, so a README grid never breaks because one project has a longer name.

Mark the fields that change and leave everything else alone:

curl -X POST 'https://render.imejis.io/v1/YOUR_DESIGN_ID' \
  -H 'dma-api-key: YOUR_KEY' \
  -H 'Content-Type: application/json' \
  -d '{
    "repo_name": { "text": "acme/parser" },
    "stars":     { "text": "1,284" },
    "language":  { "text": "Rust" }
  }' \
  --output card.png

The response is the image. No job id, no polling, nothing to wait on. Loop that over a repo list and you've got a whole org's cards in one script.

What github does to your image urlWhat GitHub does to your image URL

GitHub does not let a README load an image from your server directly. Every image in markdown is rewritten to run through Camo, GitHub's own image proxy, which fetches the file once and serves a copy from camo.githubusercontent.com. GitHub documents this behaviour under anonymized URLs, and the reason is privacy: the proxy stops your server from seeing the IP address of everyone who reads the README.

Two consequences follow, and both surprise people.

Your card refreshes on GitHub's timing, not yours. A render URL in a README is live in the sense that Camo will eventually re-fetch it. GitHub doesn't publish a cache lifetime, so treat the refresh as best-effort. If a number has to be right at a specific moment, render it on a schedule and commit the file.

Your view count and your render count are different numbers. Camo absorbs most of the traffic, so a README read a thousand times does not mean a thousand renders. It also means you can't predict the ratio, which matters for the next section.

A cache hit still costs a renderA cache hit still costs a render

This one is worth stating plainly because it's the opposite of what most people assume.

The render service does keep a cache, keyed on the design and the exact payload, and an identical request comes back from it without re-rendering. But the quota is consumed when the API key is checked, and that check runs before the cache lookup. So a cached response is faster and still costs one render.

The rule we'd give: budget on requests received, not on unique images produced. When you run out, the endpoint answers 429 with the numbers you need to handle it:

{
  "success": false,
  "message": "You have reached your quota limit",
  "data": {
    "usage": 100,
    "limit": 100,
    "remaining": 0,
    "resetAt": "2026-10-01T00:00:00.000Z"
  }
}

Read resetAt and back off to a committed PNG until the period rolls over. A README that silently 429s shows a broken image to every visitor, which is worse than a card that's a week stale.

Three ways to put a card in a readmeThree ways to put a card in a README

ApproachFreshnessRenders usedBest for
Commit a PNGWhenever CI runsOne per CI runBusy public repos, predictable cost
Embed a render URLCamo's timingOne per Camo fetchPersonal profiles, low traffic
Render with delivery=hostedWhenever you re-renderOne per renderOrg dashboards, many readers, one source image

Hosted delivery is the middle path people miss. Instead of the image bytes, the endpoint stores the render and answers with JSON:

{
  "success": true,
  "delivery": "hosted",
  "url": "https://storage.googleapis.com/.../card.png",
  "format": "png"
}

Point the README at that permanent URL. Readers hit storage rather than the render endpoint, so a repo with 50,000 monthly readers costs exactly as many renders as you choose to trigger. The trade is that nothing refreshes until you re-render, which is a scheduling problem rather than a billing one.

What a github stats card api costsWhat a GitHub stats card API costs

As of September 2026 that's 100 free renders a month, no card. A nightly refresh of one card is about 30 renders a month, so a personal profile fits inside the free tier with room to spare.

An organization is a different sum. 40 repos refreshed nightly is 1,200 renders a month, which lands on Basic at $14.99. Refresh weekly instead and the same 40 repos cost about 170 a month, which is still Basic but with a lot of headroom. The pricing page has the full table, and render quota explains how the two counters work.

Wire it into ciWire it into CI

The scheduled-commit approach is a dozen lines and it makes the cost a fixed number you chose:

name: refresh-cards
on:
  schedule:
    - cron: "0 6 * * 1"
jobs:
  render:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Render the card
        run: |
          curl -sS -X POST "https://render.imejis.io/v1/${{ secrets.DESIGN_ID }}" \
            -H "dma-api-key: ${{ secrets.IMEJIS_KEY }}" \
            -H "Content-Type: application/json" \
            -d '{"repo_name":{"text":"${{ github.repository }}"}}' \
            --output .github/card.png
      - run: git add .github/card.png && git diff --cached --quiet || git commit -m "refresh card" && git push

Weekly, one render, and the README never shows a broken image because you hit a limit on a Tuesday.

Where to go nextWhere to go next

Build the card in the free generator until the layout is right, then move that same layout to a design and render it. If you want the numbers themselves to come from an agent rather than a script, MCP covers that path, and the API docs have tested calls in Node, Python, and Go.

Frequently Asked Questions

Design the card once with the username, stars, and language split marked dynamic, then send those values to the render endpoint. The finished PNG comes back in the response body, so a CI job can write it to the repo or a README can point straight at the URL.

It updates when the image is re-fetched, not on a fixed schedule. GitHub serves README images through its Camo image proxy, which caches them, so a card refreshes on GitHub's timing rather than yours. Regenerate on a CI schedule if you need a guaranteed refresh.

Yes. The render service consumes one quota unit when it checks your API key, which happens before the cache lookup. An identical payload comes back faster from cache, but the render still counts. Budget on requests received, not on unique images.

Fewer than its page views, because Camo absorbs most of them, but the number isn't yours to control. If a repo gets heavy traffic, render the card on a schedule and commit the PNG instead of embedding a live render URL.

Yes. Dimensions are fixed at design time, so every card comes out the same size, and only the properties you marked dynamic change per call. One design plus a loop over your repo list gives an org a single card style.