How to Integrate the GitHub API in Python and Django

Blog / Django · March 6, 2016 · Updated June 10, 2026 · 9 min read
How to Integrate the GitHub API in Python and Django

To integrate the GitHub API in a Python/Django app, register a GitHub OAuth App (or a GitHub App for finer control), send the user to GitHub's OAuth 2.0 authorize URL, exchange the returned code for an access token, then call the REST API v3 at https://api.github.com with an Authorization: Bearer <token> header to read /user, /user/emails, and /user/repos. For a "Login with GitHub" feature, the maintained django-allauth GitHub provider runs this entire OAuth dance for you, so you rarely write the flow by hand.

This guide is a 2026 refresh of our older walkthrough: it drops Python 2 code and the over-broad user scope, uses Authorization: Bearer instead of token-in-query, and covers GitHub Apps, fine-grained tokens, and the GraphQL API. We use this exact pattern shipping Django applications for clients.

Key takeaways

  • Two app types. An OAuth App acts as the user with broad scopes; a GitHub App uses fine-grained, per-resource permissions and short-lived tokens — prefer it for production integrations.
  • OAuth 2.0 web flow. Redirect to /login/oauth/authorize with a CSRF state, then POST the code to /login/oauth/access_token to get a token.
  • Bearer auth. Send Authorization: Bearer <token> and Accept: application/vnd.github+json — never put the token in the query string.
  • Tokens are changing. Classic personal access tokens (PATs) are legacy; fine-grained PATs are the recommended default for scripts and CI.
  • REST or GraphQL. REST API v3 is simplest; GraphQL API v4 lets you fetch exactly the fields you need in one request.
  • Use the right library. PyGithub for ergonomic object access, plain requests for full control, django-allauth for production login.

What are your options for calling the GitHub API from Django?

There are three things to decide before writing code: how you authenticate, which API surface you call, and which library you use.

  • Authentication — a personal access token (fast, for scripts and your own account), the OAuth 2.0 web flow (so your users sign in with GitHub), or a GitHub App (for org-wide, fine-grained, automated access).
  • API surface — the REST API v3 (https://api.github.com/...) for broad, well-documented endpoints, or the GraphQL API v4 (https://api.github.com/graphql) for precise, batched queries.
  • LibraryPyGithub wraps REST in Python objects, requests gives you raw HTTP control, and django-allauth handles social login end to end.

OAuth App vs GitHub App: which should you register?

Both let users authorize your application, but they differ in how much access they grant and how long their tokens live.

Aspect OAuth App GitHub App
Acts as The signed-in user Its own identity, or on behalf of a user
Permissions Broad OAuth scopes Fine-grained, per-resource permissions
Token lifetime Long-lived user token Short-lived tokens (about 1 hour)
Install scope The whole user account Selectable repos or a single org
Rate budget Tied to the user Higher, scales with installations
Best for Simple "Login with GitHub" Production bots, CI, marketplace, org tools

For a plain social-login button, an OAuth App is enough. For anything that acts automatically across an organisation, register a GitHub App and request only the permissions you need.

How do you register a GitHub OAuth App?

  1. Go to Settings → Developer settings → OAuth Apps → New OAuth App (or visit https://github.com/settings/developers).
  2. Set the Application name, Homepage URL, and the Authorization callback URL — this must exactly match the redirect_uri you send later, e.g. https://yourapp.com/accounts/github/login/callback/.
  3. Save, then copy the Client ID and generate a Client secret.
  4. Keep the secret out of source control — load it from environment variables into Django settings.
# settings.py
import os

GITHUB_CLIENT_ID = os.environ["GITHUB_CLIENT_ID"]
GITHUB_CLIENT_SECRET = os.environ["GITHUB_CLIENT_SECRET"]
GITHUB_REDIRECT_URI = "https://yourapp.com/accounts/github/login/callback/"

How does the OAuth 2.0 login flow work in Django?

The web flow has three steps: redirect the user to GitHub, receive a temporary code at your callback, and exchange that code for an access token.

Step 1 — redirect to GitHub's authorize URL. Request the minimal scopes you need (read:user for the profile, user:email for verified email addresses) and include a random state value to defend against CSRF.

# views.py
import secrets
from urllib.parse import urlencode
from django.conf import settings
from django.shortcuts import redirect

def github_login(request):
    state = secrets.token_urlsafe(16)
    request.session["github_oauth_state"] = state
    params = {
        "client_id": settings.GITHUB_CLIENT_ID,
        "redirect_uri": settings.GITHUB_REDIRECT_URI,
        "scope": "read:user user:email",  # minimal scopes, not the broad "user"
        "state": state,
        "allow_signup": "true",
    }
    return redirect("https://github.com/login/oauth/authorize?" + urlencode(params))

Step 2 — exchange the code for an access token. GitHub redirects back to your callback with code and state. Verify the state, then POST the code to the token endpoint. Send Accept: application/json so the response is JSON (the legacy default is URL-encoded, which is why old tutorials used Python 2's urlparse.parse_qsl).

# views.py
import requests
from django.conf import settings
from django.http import HttpResponseForbidden

def github_callback(request):
    if request.GET.get("state") != request.session.pop("github_oauth_state", None):
        return HttpResponseForbidden("Invalid OAuth state")

    token_resp = requests.post(
        "https://github.com/login/oauth/access_token",
        data={
            "client_id": settings.GITHUB_CLIENT_ID,
            "client_secret": settings.GITHUB_CLIENT_SECRET,
            "code": request.GET["code"],
            "redirect_uri": settings.GITHUB_REDIRECT_URI,
        },
        headers={"Accept": "application/json"},
        timeout=10,
    )
    access_token = token_resp.json()["access_token"]
    # store access_token against the user (encrypted) and continue

Step 3 — call the REST API. Send the token in an Authorization: Bearer header, set the Accept header to application/vnd.github+json, and pin the API version. From the responses you get the profile, verified emails, and repositories (public and private, depending on scope).

# views.py
def github_api(access_token, path):
    headers = {
        "Authorization": f"Bearer {access_token}",
        "Accept": "application/vnd.github+json",
        "X-GitHub-Api-Version": "2022-11-28",
    }
    resp = requests.get(f"https://api.github.com{path}", headers=headers, timeout=10)
    resp.raise_for_status()
    return resp.json()

profile = github_api(access_token, "/user")              # login, name, avatar_url, html_url, ...
emails = github_api(access_token, "/user/emails")        # verified + primary email flags
repos = github_api(access_token, "/user/repos?per_page=100&sort=updated")

primary_email = next(e["email"] for e in emails if e["primary"] and e["verified"])

Should you use PyGithub or plain requests?

Both are fine. Use PyGithub when you want typed Python objects and pagination handled for you; use requests when you need raw control or are calling only one or two endpoints. Install with pip install PyGithub. PyGithub's modern Auth API replaces the deprecated Github(token) positional style.

from github import Github, Auth

auth = Auth.Token(access_token)
gh = Github(auth=auth)

me = gh.get_user()
print(me.login, me.name, me.email)

for repo in me.get_repos():          # auto-paginates
    print(repo.full_name, "private" if repo.private else "public")

gh.close()

REST API v3 vs GraphQL API v4: which should you call?

REST is the easiest place to start and has the broadest tooling. GraphQL shines when you would otherwise make several REST calls or only need a few fields from each object.

REST API v3 GraphQL API v4
Endpoint https://api.github.com/... https://api.github.com/graphql
Shape Many fixed endpoints One endpoint; you name the fields
Over/under-fetching Common (multiple round trips) Avoided (one tailored query)
Rate budget 5,000 requests an hour 5,000 points an hour
Best for Simple reads, broad tooling Efficient, nested, batched queries

The same token works for both. Here is the "who am I + my repos" query in GraphQL — one request instead of several REST calls.

import requests

QUERY = """
query {
  viewer {
    login
    name
    repositories(first: 100, orderBy: {field: UPDATED_AT, direction: DESC}) {
      nodes { nameWithOwner isPrivate }
    }
  }
}
"""

resp = requests.post(
    "https://api.github.com/graphql",
    json={"query": QUERY},
    headers={"Authorization": f"Bearer {access_token}"},
    timeout=10,
)
viewer = resp.json()["data"]["viewer"]
print(viewer["login"], len(viewer["repositories"]["nodes"]), "repos")

Classic vs fine-grained personal access tokens

If you are calling the API as yourself (a script, a cron job, CI) rather than logging users in, use a personal access token instead of the OAuth flow. GitHub now offers two kinds, and the fine-grained type is the recommended default.

Classic PAT Fine-grained PAT
Scope model Broad scopes (repo, user) Per-repository, per-permission
Org control Limited Admins can require and approve
Expiry Optional Required (up to one year)
Status Legacy, still supported Recommended default

Whichever you use, the call is identical — set Authorization: Bearer <token>. Store the token in an environment variable, never in code.

import os, requests

headers = {
    "Authorization": f"Bearer {os.environ['GITHUB_TOKEN']}",
    "Accept": "application/vnd.github+json",
    "X-GitHub-Api-Version": "2022-11-28",
}
r = requests.get("https://api.github.com/user/repos", headers=headers, timeout=10)
r.raise_for_status()

What are GitHub's API rate limits?

Authentication is what raises your limits, so always send a token even for public data.

  • Authenticated REST requests: up to 5,000 requests an hour for a user or OAuth App token; GitHub Apps scale higher with more installations.
  • Unauthenticated REST requests: only 60 requests an hour, counted by IP address.
  • GraphQL: a 5,000-point budget an hour, where each query has a calculated point cost.

Read the X-RateLimit-Remaining and X-RateLimit-Reset response headers, back off when you are close to the limit, and cache responses you do not need to refresh on every call.

What is the production-ready path? django-allauth

For real "Sign in with GitHub", do not hand-roll the flow — use django-allauth, which manages the redirect, callback, state, token storage, email verification, and account linking. Install it (pip install django-allauth), add the apps, and configure the GitHub provider with your Client ID and secret (via a SocialApp record or settings).

# settings.py
INSTALLED_APPS += [
    "django.contrib.sites",
    "allauth",
    "allauth.account",
    "allauth.socialaccount",
    "allauth.socialaccount.providers.github",
]
SITE_ID = 1

SOCIALACCOUNT_PROVIDERS = {
    "github": {
        "SCOPE": ["read:user", "user:email"],
    }
}

# urls.py
# path("accounts/", include("allauth.urls"))
# then link to /accounts/github/login/

The same OAuth 2.0 pattern applies to other providers. See our companion guides on integrating the LinkedIn API in Python Django and GitLab authentication with the GitLab API in Django. If you need to support many identity providers behind one login, consider single sign-on with Auth0 in a Django application.

Frequently Asked Questions

Is the classic GitHub personal access token deprecated?

Classic PATs are not removed, but they are now considered legacy. GitHub recommends fine-grained personal access tokens, which scope access to specific repositories and individual permissions and require an expiry date. For new scripts, CI jobs, and integrations, create a fine-grained token; reach for a classic PAT only when an API or tool you depend on does not yet support fine-grained tokens.

Do I need a GitHub App or is an OAuth App enough?

For a simple "Login with GitHub" button, an OAuth App is enough. Choose a GitHub App when your integration acts on its own (bots, CI, automation), needs fine-grained per-resource permissions, should be installable on selected repositories or a single organisation, or benefits from short-lived tokens and higher rate budgets. GitHub Apps are the recommended path for most production integrations.

What OAuth scopes do I need to read a user's email?

Request read:user to read the public profile and user:email to read the user's email addresses, including the verified primary one returned by GET /user/emails. Avoid the broad user scope unless you genuinely need write access to profile data — always ask for the least privilege your feature requires.

Why not put the access token in the URL query string anymore?

Tokens in query strings leak into server logs, browser history, and the Referer header, so GitHub deprecated that style. Send the token in the request header instead: Authorization: Bearer <token>. Pair it with Accept: application/vnd.github+json and the X-GitHub-Api-Version header to pin the REST API version.

What are GitHub's API rate limits?

Authenticated REST requests allow up to 5,000 requests an hour per user or OAuth token, while unauthenticated requests are capped at 60 an hour by IP address. The GraphQL API uses a 5,000-point budget an hour, charging each query a calculated cost. Always authenticate, watch the X-RateLimit-Remaining header, and cache results to stay within budget.

Should I use PyGithub, requests, or django-allauth?

Use django-allauth when the goal is letting users sign in with GitHub — it handles the whole OAuth flow safely. Use PyGithub when you want ergonomic, typed access to repositories and automatic pagination. Use plain requests when you only call a couple of endpoints, need raw control, or are calling the GraphQL API. They can coexist in the same project.

Share this article