Hardware FixRecommendedDevice not working? Your driver may be the problemCheck updates for common hardware issues.Fix DriversOctober DealsAmazon USOctober deal check: compare before you payAmazon US: current deals, useful picks and tech finds.Check DealsSlow PC?RecommendedPC slow today? Run a repair scan before it gets worseResolve common Windows issues and optimize system performance.Scan Now×
Skip to content
API authentication

How to Use the GitHub API in Python: Authentication, Pagination, Rate Limits, and Reliable Scripts

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Use the GitHub REST API from Python by making HTTPS requests to an endpoint, sending the right authentication and version headers, checking the status code, parsing JSON, and following pagination links. Start with a direct HTTP request so you can see exactly what GitHub returns. Add a client library such as PyGithub only when its abstraction fits your project.

What you need before writing code

  • Python 3 and a network connection.
  • A GitHub REST endpoint, such as /user for an authenticated user or /repos/OWNER/REPO/issues for repository issues.
  • Credentials only when the endpoint or operation requires them.
  • A deliberate X-GitHub-Api-Version header. GitHub documentation currently lists 2026-03-10 and 2022-11-28 as supported versions; requests without the header default to 2022-11-28. Check the version documentation before fixing a version in a long-lived integration.

GitHub describes the REST API as a way to create integrations, retrieve data, and automate workflows. Your Python program is simply an HTTPS client for those endpoints.

Choose authentication with the smallest necessary scope

Public, unauthenticated requests

You can request public data without a token. This is useful for a quick experiment, but unauthenticated requests generally have a primary limit of 60 requests per hour. The limit is conditional: endpoint, authentication type, and GitHub policy can change it.

Personal access token for personal work

For scripts acting as you, load a personal access token from an environment variable or secret manager. Do not paste it into source code, a public repository, a notebook shared with others, or browser-side JavaScript. Grant only the permissions required by the endpoint; a read-only operation should not receive broad write access.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

GitHub App for organization or another user’s work

GitHub identifies GitHub Apps as the appropriate model when an integration acts on behalf of an organization or another user. App authentication and installation permissions are more involved, but they provide narrower, installation-specific access than treating a personal token as a shared service credential.

GITHUB_TOKEN in Actions

Inside a GitHub Actions workflow, use the built-in GITHUB_TOKEN where it satisfies the job’s needs. Configure the workflow’s permissions explicitly and keep them no broader than necessary.

Make a transparent request with Python’s standard library

This example calls the authenticated-user endpoint. It keeps the token outside the file, sends an explicit API version, prints useful headers, and fails with the response body instead of silently accepting an error.

import json
import os
from urllib.error import HTTPError, URLError
from urllib.request import Request, urlopen

API_URL = "https://api.github.com/user"
TOKEN = os.environ.get("GITHUB_TOKEN")

headers = {
    "Accept": "application/vnd.github+json",
    "X-GitHub-Api-Version": "2026-03-10",
    "User-Agent": "python-github-api-example",
}
if TOKEN:
    headers["Authorization"] = f"Bearer {TOKEN}"

request = Request(API_URL, headers=headers, method="GET")
try:
    with urlopen(request, timeout=30) as response:
        payload = json.load(response)
        print("status:", response.status)
        print("login:", payload.get("login"))
        print("rate remaining:", response.headers.get("x-ratelimit-remaining"))
except HTTPError as error:
    detail = error.read().decode("utf-8", errors="replace")
    print(f"GitHub returned HTTP {error.code}: {detail}")
except URLError as error:
    print(f"Network error: {error.reason}")

Set the variable before running it. On macOS or Linux, use export GITHUB_TOKEN='your-token'; in PowerShell, use $env:GITHUB_TOKEN='your-token'. Never include the real value in a command that will be saved in shell history or pasted into a ticket.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

If the endpoint is public and you intentionally want an unauthenticated call, omit GITHUB_TOKEN. The same code then has the lower general limit and cannot access private data.

Request a list and handle pagination

Most list endpoints return only 30 resources by default. A successful first response therefore does not prove that you received every issue, repository, release, or member. Ask for a suitable per_page value (within the endpoint’s documented bounds) and follow the pagination information in the response’s Link header.

The function below follows a next link until GitHub omits it. It keeps the URL supplied by GitHub rather than constructing page numbers yourself.

import json
import os
from urllib.error import HTTPError, URLError
from urllib.parse import urlencode
from urllib.request import Request, urlopen

TOKEN = os.environ.get("GITHUB_TOKEN")
BASE = "https://api.github.com/repos/OWNER/REPO/issues"
HEADERS = {
    "Accept": "application/vnd.github+json",
    "X-GitHub-Api-Version": "2026-03-10",
    "User-Agent": "python-github-pagination-example",
}
if TOKEN:
    HEADERS["Authorization"] = f"Bearer {TOKEN}"

def next_url(link_header):
    if not link_header:
        return None
    for item in link_header.split(","):
        url_part, *parameters = item.split(";")
        relation = next((p.split("=", 1)[1].strip('"')
                         for p in parameters if p.strip().startswith("rel=")), None)
        if relation == "next":
            return url_part.strip().strip("<>")
    return None

def get_all_issues():
    url = BASE + "?" + urlencode({"state": "open", "per_page": 100})
    results = []
    while url:
        request = Request(url, headers=HEADERS, method="GET")
        with urlopen(request, timeout=30) as response:
            page = json.load(response)
            if not isinstance(page, list):
                raise ValueError("Expected a list response")
            results.extend(page)
            url = next_url(response.headers.get("Link"))
    return results

try:
    issues = get_all_issues()
    print(f"received {len(issues)} issues")
except (HTTPError, URLError) as error:
    print(error)

Replace OWNER and REPO. Some endpoints have special pagination behavior or limits, so read that endpoint’s documentation and stop if your integration has a practical maximum.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Read status codes, headers, and JSON deliberately

Result What to do
200, 201, or another documented success Parse the JSON or other documented representation and validate the fields your application needs.
401 Check that the token exists, is valid, and is sent as Authorization: Bearer .... Do not “fix” this by granting more permissions before confirming the credential.
403 Inspect the response body and rate-limit headers. It can indicate insufficient permission, a policy block, or a rate limit.
404 Confirm the owner, repository, endpoint, and visibility. For private resources, an absent permission can appear indistinguishable from not found.
422 Validate required fields, query parameters, and JSON types; GitHub normally explains validation failures in the response.
429 Treat it as a rate-limit response and wait according to the server’s guidance.

Useful headers include x-ratelimit-limit, x-ratelimit-remaining, x-ratelimit-reset, retry-after, and Link. Log status and request identifiers in production, but redact authorization values and personal data.

Rate limits and safe retries

GitHub documents a general primary limit of 60 requests per hour for unauthenticated public-data requests and 5,000 requests per hour for authenticated user requests. These figures are not universal guarantees: GitHub Apps, installations, enterprise policies, and particular endpoints can use different limits.

Primary-limit response

When the remaining allowance is zero, wait until the Unix time in x-ratelimit-reset. Calculate a delay from the current time, add a small safety margin, then try again. Do not continue sending requests while the counter is exhausted.

Secondary-limit response

GitHub documents 403 or 429 responses for primary and secondary limits. If retry-after is present, wait that many seconds. If it is absent, wait at least one minute; if failures continue, use increasingly longer delays. Add jitter so many workers do not retry simultaneously.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Retry only safe operations

Automatic retries are safest for idempotent reads and transient network failures. Be cautious with writes: a timeout may occur after GitHub accepted the operation. Use an idempotency strategy appropriate to the endpoint rather than blindly repeating a create or update request.

Use PyGithub when you want an abstraction

GitHub’s library directory lists PyGithub under Python and identifies it as a third-party project, not an official Octokit library. Check its current documentation, maintenance, authentication support, and coverage for the endpoint you need before making it a dependency.

import os
from github import Github

client = Github(os.environ["GITHUB_TOKEN"])
repository = client.get_repo("OWNER/REPO")
for issue in repository.get_issues(state="open"):
    print(issue.number, issue.title)

A library can reduce repetitive URL and JSON handling. Direct HTTP remains preferable when you need complete visibility into headers, version selection, pagination, retries, or an endpoint the library does not expose cleanly. Do not assume a client library automatically solves rate limiting or permission errors; inspect its behavior and configure your own operational safeguards.

Equivalent requests with cURL and Node.js

cURL

curl --fail-with-body 
  -H "Accept: application/vnd.github+json" 
  -H "X-GitHub-Api-Version: 2026-03-10" 
  -H "Authorization: Bearer $GITHUB_TOKEN" 
  https://api.github.com/user

Node.js (built-in fetch)

const token = process.env.GITHUB_TOKEN;
const response = await fetch('https://api.github.com/user', {
  headers: {
    'Accept': 'application/vnd.github+json',
    'X-GitHub-Api-Version': '2026-03-10',
    'User-Agent': 'node-github-api-example',
    ...(token ? { 'Authorization': `Bearer ${token}` } : {})
  }
});

const body = await response.json();
if (!response.ok) {
  throw new Error(`GitHub ${response.status}: ${JSON.stringify(body)}`);
}
console.log(body.login);
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Common failures and fixes

  • 401 Bad credentials: confirm the environment variable name, token validity, and Bearer prefix; rotate a leaked token rather than reusing it.
  • Private repository returns 404: verify the repository path and whether the credential can see it.
  • 403 after a burst of requests: read rate-limit headers, pause, and reduce concurrency or add caching.
  • Only the first 30 items appear: follow the Link header until no next relation remains.
  • Version-related behavior changes: send the explicit version header and review GitHub’s current version documentation before upgrading.
  • JSON parsing fails: log the status and content type first; an error page or proxy response is not the JSON shape your code expected.
  • Network timeouts: set a finite timeout, retry only safe requests with backoff, and make the operation observable.

Or skip the browser setup

If your workflow also needs website screenshots—for example, attaching a visual snapshot of a GitHub-powered dashboard—you can call ScreenshotNeo instead of maintaining a browser service. One GET request returns a PNG, JPEG, WebP, or PDF, and the API can accept cookies, custom headers, waits, CSS selectors, and other capture options.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp

See the ScreenshotNeo API documentation for parameters. Cookie banners, newsletter popups, and chat widgets are removed before the shot; bot checks, blank pages, failed loads, timeouts, and cache hits are not billed, and response headers identify the page verdict and billing result. Its MCP server lets Claude, Cursor, and other MCP clients use take_screenshot, get_page_info, and capture_pdf. The Free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000 shots. Create a free ScreenshotNeo account.

FAQ

Should I use REST or GraphQL from Python?

This guide covers the REST API. Choose the interface that matches the endpoint, response shape, and operational controls your integration requires.

Can I put a token in a desktop application?

Not safely if users can inspect the application. Keep privileged credentials on a server or use an authorization design intended for untrusted clients.

Why should the API version be pinned?

GitHub versions REST behavior by release date. A deliberate header makes the behavior your integration expects visible and reviewable instead of relying on a changing default.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

Leave a Reply

Your email address will not be published. Required fields are marked *

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Read next

Recommended PC Tool
Recommended PC Tool
PC Slower Than It Used to Be?Free scan - under a minute
Crashes, No Sound, or Screen Glitches?Free driver scan

Two free Windows tools

One Free Minute Could Fix That PC

Before you go - each of these free tools takes about a minute and tackles what quietly slows a Windows PC down.

Special offer. View Outbyte info, uninstall instructions, EULA, and Privacy Policy.