How to Use GitHub Actions with a Personal Access Token for Private Stats in GitHub Readme Stats
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
repoandread:userscopes - 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. 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:
// 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, 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
- Navigate to GitHub Settings → Developer settings → Personal access tokens
- Generate either a classic token with
repoandread:userscopes, or a fine-grained token with read access to repository contents - 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:
- Open your profile repository (the one containing your README)
- Go to Settings → Secrets and variables → Actions → New repository secret
- Name the secret
PAT_1and paste your token value - 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 that uses the official readme-tools/github-readme-stats-action to generate cards and commit them to your repository.
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 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.


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
repoandread:userscopes to fetch private repository statistics. - The server automatically discovers and rotates through tokens matching the
PAT_<n>pattern using logic insrc/common/retryer.js. - Name your primary secret exactly
PAT_1in Settings → Secrets and variables → Actions. - Use the
readme-tools/github-readme-stats-actionin 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 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.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →