# How to Use GitHub Actions with a Personal Access Token for Private Stats in GitHub Readme Stats

> Learn to use GitHub Actions with a Personal Access Token for private stats in GitHub Readme Stats. Authenticate requests and avoid rate limits with this guide.

- Repository: [Anurag Hazra/github-readme-stats](https://github.com/anuraghazra/github-readme-stats)
- Tags: how-to-guide
- Published: 2026-02-28

---

**Use a GitHub Actions workflow to generate static SVG cards locally by passing a Personal Access Token named `PAT_1` to the `github-readme-stats-action`, which authenticates requests for private repositories and automatically rotates through additional tokens (`PAT_2`, etc.) if rate limits occur.**

GitHub Readme Stats generates dynamic SVG cards for your profile README, but accessing **private** repository statistics requires authentication via a Personal Access Token (PAT). According to the `anuraghazra/github-readme-stats` source code, the server automatically detects and rotates through any environment variables matching the `PAT_<n>` pattern, making GitHub Actions the safest and most efficient deployment method for sensitive data.

## Why Private Stats Require a Personal Access Token

GitHub's API returns private contributions—including commits to private repositories and private activity data—only to authenticated requests. To enable this in GitHub Readme Stats, you must supply a PAT with specific scopes that grant read access to your private repository data.

The required scopes differ based on token type:

- **Classic tokens**: Select the `repo` and `read:user` scopes
- **Fine-grained tokens**: Select **Read access** under the *Repository permissions* section for the relevant repositories

Without these permissions, the API returns only public data, and your generated cards will exclude private contributions.

## How Token Rotation Works in the Source Code

The project implements automatic token rotation through the `retryer` utility located in [`src/common/retryer.js`](https://github.com/anuraghazra/github-readme-stats/blob/main/src/common/retryer.js). This mechanism allows the server to cycle through multiple PATs without manual intervention when encountering rate limits or authentication errors.

The server scans environment variables using the regex pattern `/PAT_\d*$/` to identify available tokens:

```javascript
// src/common/retryer.js – token discovery logic
const PATs = Object.keys(process.env).filter((key) => /PAT_\d*$/.exec(key)).length;

```

The code sets the `RETRIES` constant equal to the number of detected `PAT_<n>` variables. When a request fails due to rate limiting, expiration, or "Bad credentials" errors, the function automatically falls back to the next available token (e.g., from `PAT_1` to `PAT_2`). You can monitor token health through the internal `/api/status/pat-info` endpoint defined in [`api/status/pat-info.js`](https://github.com/anuraghazra/github-readme-stats/blob/main/api/status/pat-info.js), which reports whether each configured PAT is valid, exhausted, or erroring.

## Setting Up Your Personal Access Token

Before configuring the workflow, create and configure your PAT with appropriate permissions.

### Create the Token

1. Navigate to **GitHub Settings → Developer settings → Personal access tokens**
2. Generate either a **classic token** with `repo` and `read:user` scopes, or a **fine-grained token** with read access to repository contents
3. Copy the generated token value immediately

### Add the Token to Repository Secrets

The GitHub Actions workflow expects the secret to be named exactly `PAT_1`:

1. Open your profile repository (the one containing your README)
2. Go to **Settings → Secrets and variables → Actions → New repository secret**
3. Name the secret `PAT_1` and paste your token value
4. Click **Add secret**

If you have additional tokens for rotation, add them as `PAT_2`, `PAT_3`, etc., following the same process.

## Configuring the GitHub Actions Workflow

Create a workflow file at [`.github/workflows/grs.yml`](https://github.com/anuraghazra/github-readme-stats/blob/main/.github/workflows/grs.yml) that uses the official `readme-tools/github-readme-stats-action` to generate cards and commit them to your repository.

```yaml
name: Update README cards

on:
  schedule:
    - cron: "0 3 * * *"
  workflow_dispatch:

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Generate stats card
        uses: readme-tools/github-readme-stats-action@v1
        with:
          card: stats
          options: username=${{ github.repository_owner }}&show_icons=true
          path: profile/stats.svg
          token: ${{ secrets.PAT_1 }}

      - name: Generate top‑languages card
        uses: readme-tools/github-readme-stats-action@v1
        with:
          card: top-langs
          options: username=${{ github.repository_owner }}
          path: profile/top-langs.svg
          token: ${{ secrets.PAT_1 }}

      - name: Commit cards
        run: |
          git config user.name "github-actions"
          git config user.email "github-actions@users.noreply.github.com"
          git add profile/*.svg
          git commit -m "Update README cards" || exit 0
          git push

```

The workflow passes `secrets.PAT_1` to the action's `token` input, which forwards it to the server as the `PAT_1` environment variable. The `retryer` logic in [`src/common/retryer.js`](https://github.com/anuraghazra/github-readme-stats/blob/main/src/common/retryer.js) then picks up this variable to authenticate GitHub API requests for private data.

## Embedding Generated Cards in Your README

Because the workflow commits static SVG files directly to your repository, embed them using relative paths rather than external URLs. This approach eliminates API requests on every page view and prevents exposing your PAT through query parameters.

```markdown
![GitHub Stats](./profile/stats.svg)
![Top Languages](./profile/top-langs.svg)

```

Store the SVGs in a dedicated directory (e.g., `profile/`) to keep your repository organized. The images render instantly from GitHub's CDN without invoking the public API endpoint.

## Scaling with Multiple Tokens

If you encounter rate limits on a single PAT, add secondary tokens without modifying any code. Create additional secrets named `PAT_2`, `PAT_3`, etc., and the `retryer` function automatically incorporates them into the rotation sequence.

The system determines the total retry count dynamically based on how many `PAT_<n>` variables exist in the environment. When the first token returns a 401 or 403 error, or indicates rate limit exhaustion, the server transparently switches to the next available credential.

## Summary

- **GitHub Readme Stats** requires a PAT with `repo` and `read:user` scopes to fetch private repository statistics.
- The server automatically discovers and rotates through tokens matching the `PAT_<n>` pattern using logic in [`src/common/retryer.js`](https://github.com/anuraghazra/github-readme-stats/blob/main/src/common/retryer.js).
- Name your primary secret exactly `PAT_1` in **Settings → Secrets and variables → Actions**.
- Use the `readme-tools/github-readme-stats-action` in a scheduled workflow to generate static SVG files and avoid exposing tokens in public URLs.
- Commit generated cards to the repository and reference them with relative paths for instant loading and enhanced security.
- Add `PAT_2`, `PAT_3`, etc., to implement automatic failover when encountering rate limits.

## Frequently Asked Questions

### What scopes does my Personal Access Token need for private stats?

Your PAT requires the `repo` scope for classic tokens, which grants access to private repository data, and `read:user` to access user profile information. For fine-grained tokens, select read access to repository contents under the repository permissions section. Without these scopes, GitHub Readme Stats can only display public contribution data.

### Why does the secret need to be named PAT_1 specifically?

The source code in [`src/common/retryer.js`](https://github.com/anuraghazra/github-readme-stats/blob/main/src/common/retryer.js) specifically searches for environment variables matching the regex pattern `/PAT_\d*$/`, expecting tokens to follow the `PAT_1`, `PAT_2` naming convention. While the action accepts any secret name as input, the server side expects this specific format to enable automatic token rotation and retry logic.

### Can I use the same PAT for multiple repositories or deployments?

Yes, you can use the same PAT across multiple workflows or repositories, though this increases your rate limit consumption against that single token. For better resilience, distribute requests across multiple PATs by adding `PAT_2`, `PAT_3`, etc., to your secrets. The retryer automatically cycles through them, effectively pooling your rate limit capacity.

### How do I troubleshoot authentication errors in the workflow?

Check the `/api/status/pat-info` endpoint on your deployment (or review workflow logs) to verify token status. If the workflow fails with "Bad credentials" or 401 errors, verify that the `PAT_1` secret contains a valid, non-expired token with correct scopes in **Settings → Secrets and variables → Actions**. The endpoint reports whether each configured PAT is valid, exhausted, or returning errors.