GitHub Actions: "API rate limit exceeded for installation"
GitHub refused the request because the token's hourly budget for the REST API is used up. "Installation" means the token belongs to an installed GitHub App. The workflow's GITHUB_TOKEN is one: GitHub's documentation says "The GITHUB_TOKEN secret is a GitHub App installation access token." A token from actions/create-github-app-token is another.
What it looks like
From a release run on November 1, 2026 that had already published to PyPI and npm, and then failed at its last step (gaia #6383):
Run softprops/action-gh-release@v3 ... ⚠️ Unexpected error fetching GitHub release for tag refs/tags/v0.25.4-rc2: HttpError: API rate limit exceeded for installation. ... ##[error]API rate limit exceeded for installation. If you reach out to GitHub Support for help, please include the request ID 5400:96E7A:2806BCA:856BD7E:6AE799AB and timestamp 2026-11-01 19:57:31 UTC. ...
- Some tools print "API rate limit exceeded for installation ID <number>". It is the same limit.
- The response is a 403 (sometimes a 429) with
x-ratelimit-remaining: 0, andx-ratelimit-resetsays when the budget refills, in UTC epoch seconds.
Whose budget it is
GITHUB_TOKEN: GitHub's documentation gives 1,000 requests per hour per repository, or 15,000 for resources that belong to a GitHub Enterprise Cloud account. It is per repository, so every workflow run in the repository draws on it. In semantic-router #5598, 372 runs started in 90 minutes, and the community automation used up the budget while pull requests were being checked.- A GitHub App's installation token: at least 5,000 an hour, more for an installation with over 20 repositories or on an organization with over 20 users (50 an hour for each), up to 12,500; 15,000 on GitHub Enterprise Cloud. Everything that uses that installation shares it.
- Actions that call the API count too. Earlier, a setup-ffmpeg step in the same repository had hit the same limit (gaia #6383).
Seeing the budget in the job
- name: API budget
if: always()
env:
GH_TOKEN: ${{ github.token }}
run: gh api rate_limit --jq '.resources.core | "\(.used)/\(.limit), resets \(.reset | todate)"'
- Calling
rate_limitdoes not count against the primary rate limit. - The documentation calls the
x-ratelimit-*response headers the authoritative source, and says values can differ from one response to the next because requests are served in several regions. Trust the headers of the request that failed over a number printed earlier.
Where the calls go
- A workflow that walks every open pull request on every event. In bainluck #11886, with 74 open pull requests, one run listed them, fetched each one, then read the reviews and files of each, on every event. Work on the pull request the event is about.
- Many runs at once: pushes in quick succession, a matrix, or bots that comment and label. Each run that calls the API takes from the same budget.
- The step that fails is just the one that asked when the budget was empty, often the last step of a long workflow, like the release above.
Instead of failing
Cancel runs that a newer push has replaced, and start fewer of them:
on:
pull_request:
types: [opened, synchronize, ready_for_review]
paths: ['src/**']
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
- Wait for the reset only when it is close. Read
x-ratelimit-reset, and wait and retry once if it is a few minutes away; otherwise fail with a clear message. Retrying at once does not help, andactions/github-scriptdoes not retry a 403 even withretriesset, because 403 is in its defaultretry-exempt-status-codes. - Give the heavy work a token with its own budget: a GitHub App you create and install, used through
actions/create-github-app-token. Its installation budget is separate from the repository'sGITHUB_TOKENbudget (proposed in ksail #7907).
- uses: actions/create-github-app-token@v3
id: app-token
with:
client-id: ${{ vars.APP_CLIENT_ID }}
private-key: ${{ secrets.APP_PRIVATE_KEY }}
- run: gh api "repos/${{ github.repository }}/pulls?state=open&per_page=100" --jq length
env:
GH_TOKEN: ${{ steps.app-token.outputs.token }}
Telling it apart
- "API rate limit exceeded for user ID <number>.": a personal access token. That user has 5,000 requests an hour, shared with apps that act on the user's behalf.
- "API rate limit exceeded for <IP address>. (But here's the good news: Authenticated requests get a higher rate limit. ...)": no token was sent at all, and the limit is 60 an hour. A tool that says to set
GITHUB_TOKENmay still not send it (gooseworks #164). - "You have exceeded a secondary rate limit": too many requests in a short time, not the hourly budget. Wait for
retry-after, or at least a minute. - "Resource not accessible by integration": also a 403, but the token lacks a permission ("Resource not accessible by integration").
See failed jobs from IntelliJ IDEA
Pipeline Inbox for GitHub Actions Workflows, a plugin by Lesstep for JetBrains IDEs 2024.1 or later, lists the runs of all your repositories in an IDE tool window, with their jobs and steps, and your current branch's runs are in the status bar. Reading is free; re-running failed jobs, cancelling, running workflows and reviewing deployments are Pro (US$1.50 a month, with a 30-day trial).
GitHub and GitHub Actions are trademarks of GitHub, Inc.; IntelliJ IDEA is a trademark of JetBrains s.r.o. This page is not affiliated with, sponsored or endorsed by GitHub, Inc. or JetBrains.
More: "Resource not accessible by integration" · GitHub: "API rate limit exceeded", with or without a token · "Input required and not supplied" · When a run will not stop · GitHub Actions plugins for IntelliJ IDEA compared · 日本語