Error Medic

Resolving GitHub API Rate Limit (403/429) & Authentication (401) Errors

Fix GitHub API rate limits (403/429), timeouts, and 401 errors. Learn how to diagnose API limits, implement backoff strategies, and authenticate requests.

Last updated:
Last verified:
1,466 words
Key Takeaways
  • Unauthenticated requests are strictly limited to 60 per hour; authenticate using a Personal Access Token (PAT) or GitHub App for up to 5,000 to 15,000 requests/hour.
  • A 403 Forbidden or 429 Too Many Requests status typically indicates you have triggered GitHub's primary limits or secondary abuse detection mechanisms.
  • Check the X-RateLimit-Remaining and Retry-After HTTP response headers to dynamically manage API pacing and retry logic.
  • A 401 Unauthorized error means your PAT is missing, expired, incorrectly formatted in the header, or lacks SAML SSO authorization for the target organization.
  • 502 Server Errors and connection timeouts usually occur when querying massive datasets; switch to GraphQL or implement strict pagination to resolve them.
API Limit Mitigation Strategies Compared
MethodWhen to UseTime to ImplementRisk / Impact
Add Personal Access TokenHitting the default unauthenticated 60/hr limit5 minutesLow (ensure token is securely stored)
Conditional Requests (ETag)Polling data frequently that rarely changes30 minutesLow (saves massive API quota)
Exponential BackoffEncountering secondary rate limits (429/403)1-2 hoursLow (improves overall pipeline stability)
Migrate to GitHub AppsEnterprise automation requiring >5k requests/hour2-4 hoursMedium (requires App installation and lifecycle management)
GraphQL API MigrationExperiencing 502s or timeouts on large REST endpoints1-2 daysMedium (requires query rewriting, high reward)

Understanding GitHub API Errors

When automating CI/CD pipelines, gathering repository metrics, or managing pull requests, encountering GitHub API errors is practically a rite of passage for DevOps and Site Reliability Engineers. The most disruptive errors revolve around rate limits (403 and 429), authentication failures (401), and upstream timeouts (502). Let's break down why these happen and how to build resilient systems to handle them.

1. The 403 and 429 Rate Limit Errors

GitHub aggressively protects its infrastructure from noisy neighbors. If you execute a script without authentication, you are severely capped at 60 requests per hour per IP address. Once exceeded, GitHub intercepts the request and responds with:

HTTP/2 403 Forbidden
{
  "message": "API rate limit exceeded for xxx.xxx.xxx.xxx.",
  "documentation_url": "https://docs.github.com/rest/overview/resources-in-the-rest-api#rate-limiting"
}

However, even with a Personal Access Token (PAT) yielding 5,000 requests per hour, your scripts might still fail. Concurrent bursts or expensive requests trigger Secondary Rate Limits. In these scenarios, GitHub typically returns a 429 Too Many Requests or a 403 Forbidden with a warning about abuse detection mechanisms. This happens if you make too many concurrent requests, rapidly mutate repository state (e.g., creating 50 branches in 10 seconds), or consume too much CPU time on GitHub's backend.

2. The 401 Unauthorized Error

A 401 Bad credentials error implies your authentication mechanism was entirely rejected. Common root causes include:

  • Token Expiration: The PAT has reached its predefined expiration date.
  • Malformed Headers: Passing the token as token ghp_xyz instead of the required Bearer ghp_xyz format in the Authorization header.
  • SAML SSO Restrictions: The target repository is owned by an Organization enforcing SAML Single Sign-On, but your token hasn't been explicitly authorized for that organization.

3. Timeouts and 502 Bad Gateway Errors

These errors (HTTP 502 Server Error or standard TCP timeouts) occur when GitHub's upstream servers take too long to compile the response. Common culprits include querying the Search API with overly broad parameters, requesting massive monolithic diffs, or hitting the GET /pulls endpoint on massive monorepos without proper pagination constraints.


Step-by-Step Troubleshooting and Resolution

Step 1: Diagnose Your Current Rate Limit Status

Before modifying automation code, determine exactly which limit you have hit. GitHub provides a dedicated rate limit endpoint that does not consume your quota.

You should always inspect the HTTP headers returned by GitHub API calls. Critical headers include:

  • x-ratelimit-limit: Your total quota for the current window.
  • x-ratelimit-remaining: Requests left in the current window.
  • x-ratelimit-reset: The exact epoch timestamp when your quota fully resets.
  • retry-after: The number of seconds you must wait before retrying (absolutely crucial for recovering from secondary limits).

Step 2: Implement Robust Authentication (Fixing 401s)

If you are unauthenticated, generate a fine-grained PAT or classic token. Always inject it via the Authorization header rather than via URL parameters (which is deprecated and insecure).

If you still receive a 401 error:

  1. Navigate to GitHub Developer Settings and verify the token is active.
  2. If accessing an enterprise org repo, click "Configure SSO" next to the active token and click "Authorize" for your specific organization.
  3. Validate your HTTP request formatting. It must look exactly like: Authorization: Bearer <YOUR_TOKEN>.

Step 3: Implement Exponential Backoff and Jitter (Fixing 429s/403s)

To survive secondary rate limits gracefully, your HTTP client must be capable of handling 429 and 403 responses by parsing the Retry-After header. If the header is missing, you must fall back to exponential backoff.

A resilient retry loop in Python or Go should intercept the 403/429 status code, calculate the sleep duration specified in the headers (plus a small random jitter to avoid thundering herd problems), sleep, and retry. Avoid firing concurrent requests from multiple threads; GitHub's abuse mechanisms explicitly penalize parallel mutations.

Step 4: Utilize Conditional Requests to Save Quota

To drastically reduce your API consumption for polling workloads, use Conditional Requests. When you successfully fetch a resource, GitHub returns an ETag header and a Last-Modified header. Store these values locally or in your database.

On your next polling cycle, send these headers in your request:

  • If-None-Match: <ETag_Value>
  • If-Modified-Since: <Timestamp>

If the resource hasn't changed, GitHub returns a HTTP 304 Not Modified with an empty body. Crucially, 304 responses do not count against your primary API rate limit, allowing you to poll aggressively without exhausting your 5,000/hour limit.

Step 5: Mitigate 502s and Timeouts

When a 502 Server Error or a timeout occurs, GitHub failed to compile the response in time. To fix this:

  • Reduce Page Sizes: Decrease your per_page query parameter from the maximum 100 down to 30 or fewer.
  • Scope Queries: Add sort, direction, and since parameters to limit the dataset.
  • Migrate to GraphQL: If you are hitting timeouts fetching deeply nested REST resources, switch to the GitHub GraphQL API (v4). GraphQL allows you to traverse relationships and fetch exactly the fields you need in a single HTTP request, massively reducing upstream processing time and preventing query timeouts.

Frequently Asked Questions

bash
#!/bin/bash
# Diagnostic script to check GitHub API Rate Limit and Headers

TOKEN="your_personal_access_token_here"

echo "=== Checking Core Rate Limit Status ==="
curl -s -H "Accept: application/vnd.github+json" \
     -H "Authorization: Bearer $TOKEN" \
     -H "X-GitHub-Api-Version: 2022-11-28" \
     https://api.github.com/rate_limit | grep -A 5 '"core"'

echo -e "\n=== Making a test request and dumping headers ==="
# We use -i to include HTTP headers in the output
curl -s -i -H "Accept: application/vnd.github+json" \
     -H "Authorization: Bearer $TOKEN" \
     -H "X-GitHub-Api-Version: 2022-11-28" \
     https://api.github.com/repos/octocat/hello-world | grep -iE "(HTTP/|x-ratelimit|retry-after|x-github-request-id)"

echo -e "\nNote: If 'x-ratelimit-remaining' is 0, wait until the epoch timestamp in 'x-ratelimit-reset'."
echo "If 'retry-after' is present, you MUST pause execution for that many seconds to avoid abuse bans."
D

DevOps Troubleshooting Editorial

Our editorial team consists of Senior DevOps and Site Reliability Engineers dedicated to demystifying cloud infrastructure, CI/CD pipelines, and API integrations.

Sources

Related Guides