API Rate Limit Exceeded in GitHub Actions: Fixes
Last updated
API rate limit exceeded for installation ID in a GitHub Actions log means a step called the GitHub API with the workflow's GITHUB_TOKEN and used up its hourly allowance. That token is limited to 1,000 requests per hour per repository (15,000 for resources owned by a GitHub Enterprise Cloud account). Fixes are to make fewer calls, cache results, authenticate with a better token, or back off when GitHub tells you to. Numbers are from GitHub's REST API rate limit docs, checked October 2026.
The exact message
HttpError: API rate limit exceeded for installation ID 12345678.
403 API rate limit exceeded for installation. If you reach out to GitHub Support for help, please include the request ID ...
You have exceeded a secondary rate limit. Please wait a few minutes before you try again.
rate limit exceeded
The first form is the primary limit, hit when the budget for the hour is gone. The "secondary rate limit" form is about request patterns, not the hourly total. Related forms: API rate limit exceeded for user ID ... (a personal token) and API rate limit exceeded for <IP> (But here's the good news: Authenticated requests get a higher rate limit) (a call with no token at all).
The limits that apply
| Credential | Limit |
|---|---|
| No token (by IP) | 60 requests per hour |
| Personal access token (user) | 5,000 per hour |
| GitHub App installation | 5,000 per hour, scaling up to 12,500 with repositories and users |
GITHUB_TOKEN in Actions | 1,000 per hour per repository |
GITHUB_TOKEN, Enterprise Cloud account | 15,000 per hour per repository |
The GITHUB_TOKEN limit is shared by every workflow run in that repository in the same hour. A busy repository with many jobs can drain it, even if each job makes only a few dozen calls.
Secondary limits, per the docs: no more than 100 concurrent requests, no more than 900 points per minute on REST endpoints, and in general no more than 80 content-creating requests per minute and 500 per hour. GitHub says these can change without notice.
Causes, most likely first
1. A matrix or many runs calling the API at once
30 matrix cells that each call gh api or a release action 40 times is 1,200 requests. Move the call into one job and share the result.
2. Loops over lists
gh pr list, then one gh api per PR, is the classic. Use a single paginated call, or GraphQL, which batches many fields into one request.
- run: gh pr list --state open --json number,title,labels --limit 200
env:
GH_TOKEN: ${{ github.token }}
3. Tools that download releases or check versions
Version managers and setup actions that query the Releases API for the latest version (asdf, nvm aliases, setup-* actions with latest) use the token. Pin versions, or pass a token, so they use the cache or an authenticated limit.
4. A call with no token
Unauthenticated calls from a runner share the runner's IP limit of 60 per hour. Always pass a token:
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
5. Bots and automations in the same repository
Dependabot, labelers, stale-issue bots and release tools all spend the same repository budget.
6. Secondary rate limit from bursts
Creating many comments, issues or statuses in a loop triggers the secondary limit. Slow down with a short sleep, and never run these calls in parallel.
Fixes
Check what is left
- run: gh api rate_limit --jq '.resources.core'
env:
GH_TOKEN: ${{ github.token }}
Response headers x-ratelimit-remaining and x-ratelimit-reset (epoch seconds) are the reliable source, per the docs.
Use a GitHub App token for heavy automation
A GitHub App installation has its own 5,000 per hour budget (more for large installations), separate from the repository's GITHUB_TOKEN. The create-github-app-token action mints one in the job:
- uses: actions/create-github-app-token@v1
id: app
with:
app-id: ${{ vars.APP_ID }}
private-key: ${{ secrets.APP_PRIVATE_KEY }}
- run: gh api repos/${{ github.repository }}/issues --paginate
env:
GH_TOKEN: ${{ steps.app.outputs.token }}
Note that an installation token's rate limit message also says "installation ID", so confirm which token your step uses.
Back off when limited
For primary limits, wait until x-ratelimit-reset. For secondary limits, GitHub says to honour retry-after if present, otherwise wait at least a minute, then back off exponentially. The docs also warn that continuing to send requests while limited may get an integration banned.
for i in 1 2 3 4; do
gh api repos/OWNER/REPO/issues && break
sleep $((30 * i))
done
Reduce requests
- Use conditional requests with
ETagandIf-None-Match; a 304 response does not count against the primary limit. - Request only what you need with
per_page=100and filters. - Cache API results in
actions/cachewhen they change rarely (see GitHub Actions caching). - Run scheduled jobs less often, and offset cron times (cron builder).
Not the same as Docker or cache limits
If the error mentions toomanyrequests, see Docker pull rate limit. Cache API limits (uploads per minute) are separate from this one; see cache limits. Rate limits apply per token and are unrelated to runner speed, so changing runner provider does not change them. A provider such as compiler.dev still uses your GitHub token for GitHub API calls.
How to prevent it
- Use
GITHUB_TOKENonly for light calls and an App token for heavy automation. - Make API calls in one job, not in every matrix cell.
- Prefer GraphQL or paginated list calls over per-item loops.
- Pin tool versions so setup steps do not query the API.
- Log remaining quota at the end of busy workflows to spot growth.
FAQ
What is the GITHUB_TOKEN rate limit?
1,000 requests per hour per repository, and 15,000 for resources of a GitHub Enterprise Cloud account.
How do I check my rate limit in a workflow?
Run gh api rate_limit with GH_TOKEN set, or read the x-ratelimit-remaining header from any API response.
What is a secondary rate limit?
A limit on request patterns, such as too many concurrent or content-creating requests. It can trigger even when you have hourly quota left.
Does the limit reset every hour?
Yes, the window resets at the time in x-ratelimit-reset.
Related
- Docker Pull Rate Limit in GitHub Actions: FixesFix "toomanyrequests: You have reached your pull rate limit" in GitHub Actions: Docker Hub limits, docker/login-action, mirrors and GHCR alternatives.
- Cache Not Found for Input Keys: Causes and FixesCache not found for input keys in GitHub Actions is a cache miss, not always an error. Why it happens, how key and branch scope work, and fixes.
- Hosted Runner Lost Communication With the ServerFix "The hosted runner lost communication with the server" in GitHub Actions: causes like OOM, disk full and killed processes, with steps to diagnose.
- Job Exceeded Maximum Execution Time: timeout-minutesFix "The job running on runner has exceeded the maximum execution time of 360 minutes" in GitHub Actions. How timeout-minutes works for jobs and steps.