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
/userfor an authenticated user or/repos/OWNER/REPO/issuesfor repository issues. - Credentials only when the endpoint or operation requires them.
- A deliberate
X-GitHub-Api-Versionheader. GitHub documentation currently lists2026-03-10and2022-11-28as supported versions; requests without the header default to2022-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.
Do these 3 things before closing this tab:
1Scan for outdated or missing drivers - takes under a minute2Clear out junk files and repair common Windows errors3Fix the driver behind crashes, sound loss and screen glitches#1 Best Overall
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.
Quick wins for a faster PC:
Repair Windows errors before they cause bigger problemsFix Now →Fix the driver behind crashes, sound loss and screen glitchesFind Drivers →Clear out junk files and repair common Windows errorsFree Scan →Rank #2
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.
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.
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.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
Linkheader until nonextrelation 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.
The Tool Desk
Outbyte PC Repair FREERepair Windows errors before they cause bigger problemsFix Now →Outbyte Driver Updater FREEScan for outdated or missing drivers - takes under a minuteDriver Scan →Best Value
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.
Recommended Free Tools
Quick Recap
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.




