---
title: "GitHub Stats Card API: Cards That Stay Current"
description: "GitHub stats card API: turn a README badge into a live render URL. How Camo caching works and what a card really costs you. Start free."
url: "https://www.imejis.io/blogs/tutorials/github-stats-card-api"
published: "2026-09-04"
---

# 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](/tools/github-stats-card) and the [repo language chart](/tools/github-repo-language-chart) do it free in the browser, and [the walkthrough for those](/blogs/tutorials/github-readme-stats-cards) 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 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:

```bash
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 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](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/about-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 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:

```json
{
  "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 README

| Approach                      | Freshness              | Renders used       | Best for                                       |
| ----------------------------- | ---------------------- | ------------------ | ---------------------------------------------- |
| Commit a PNG                  | Whenever CI runs       | One per CI run     | Busy public repos, predictable cost            |
| Embed a render URL            | Camo's timing          | One per Camo fetch | Personal profiles, low traffic                 |
| Render with `delivery=hosted` | Whenever you re-render | One per render     | Org 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:

```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 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](/pricing) has the full table, and [render quota](/help/pricing-and-quota) explains how the two counters work.

## Wire it into CI

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

```yaml
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 next

Build the card in the [free generator](/tools/github-stats-card) 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](/agents) covers that path, and the [API docs](/apis) have tested calls in Node, Python, and Go.
