GitHub's API uses RESTful URL patterns for resources and GraphQL for flexible queries. Understanding these patterns is essential for building GitHub integrations.
Key Takeaways
- 1REST API base URL is https://api.github.com
- 2Resources follow /{owner}/{repo}/{resource} pattern
- 3Pagination uses page and per_page parameters
- 4GraphQL endpoint is https://api.github.com/graphql
- 5Authentication via Bearer token or GitHub App JWT
“The REST API enables you to build integrations, retrieve data, and automate workflows using the GitHub API.”
REST API URL Structure
GitHub's REST API follows predictable URL patterns that map directly to resources. Once you understand the structure, you can construct URLs for any repository, user, or organization without consulting documentation for every endpoint.
# Base URL
https://api.github.com
# User endpoints
GET /user # Authenticated user
GET /users/{username} # Specific user
GET /users/{username}/repos # User's repos
# Repository endpoints
GET /repos/{owner}/{repo} # Repo details
GET /repos/{owner}/{repo}/issues # Repo issues
GET /repos/{owner}/{repo}/pulls # Pull requests
GET /repos/{owner}/{repo}/commits # Commits
# Organization endpoints
GET /orgs/{org} # Org details
GET /orgs/{org}/repos # Org repos
GET /orgs/{org}/members # Org members
# Full URL example
https://api.github.com/repos/facebook/react/issues?state=open&per_page=10The URL hierarchy mirrors GitHub's resource ownership model: users own repos, repos contain issues and pulls, organizations group repos. This consistency means if you know how to access one resource type, you can access related resources by following the same pattern.
The following table provides a quick reference for the most commonly used endpoints along with example query parameters for filtering results.
Common Endpoints
| Endpoint | Description | Example |
|---|---|---|
| /repos/:owner/:repo | Repository info | /repos/vercel/next.js |
| /repos/:owner/:repo/issues | List issues | ?state=open&labels=bug |
| /repos/:owner/:repo/pulls | Pull requests | ?state=all&head=feature |
| /repos/:owner/:repo/contents/:path | File contents | /contents/README.md |
| /search/repositories | Search repos | ?q=language:typescript |
| /search/code | Search code | ?q=filename:package.json |
Notice how query parameters vary by endpoint. Issues support state filtering, while search endpoints use a unified q parameter with GitHub's search syntax. The contents endpoint retrieves file data at specific paths or refs.
Most GitHub API endpoints return paginated results to prevent overwhelming responses. GitHub uses the Link header rather than response body fields to communicate pagination URLs, which requires parsing but ensures consistent behavior across all endpoints.
Pagination Parameters
// GitHub uses Link header for pagination
const response = await fetch('https://api.github.com/repos/facebook/react/issues?per_page=30', {
headers: {
'Authorization': 'Bearer YOUR_TOKEN',
'Accept': 'application/vnd.github+json'
}
});
// Check Link header for pagination URLs
const linkHeader = response.headers.get('Link');
// <https://api.github.com/repos/facebook/react/issues?page=2&per_page=30>; rel="next",
// <https://api.github.com/repos/facebook/react/issues?page=34&per_page=30>; rel="last"
// Parse Link header
function parseLinkHeader(header) {
if (!header) return {};
return header.split(',').reduce((acc, link) => {
const match = link.match(/<([^>]+)>.*rel="([^"]+)"/);
if (match) {
acc[match[2]] = match[1];
}
return acc;
}, {});
}
const links = parseLinkHeader(linkHeader);
console.log(links.next); // URL for next page
console.log(links.last); // URL for last page
// Common pagination parameters
// ?page=2 - Page number (1-indexed)
// ?per_page=100 - Items per page (max 100)
// ?since=2024-01-01 - Items since date (ISO 8601)
// ?until=2024-12-31 - Items until dateThe parseLinkHeader function extracts pagination URLs from the header. Always use the URLs provided rather than manually incrementing page numbers, as GitHub may change pagination strategies. The rel values (next, prev, first, last) tell you which direction each URL leads.
Beyond pagination, most endpoints accept filtering and sorting parameters that can dramatically reduce response sizes and API calls. Building URLs with the right parameters upfront is more efficient than filtering large responses client-side.
Query Parameters
// Issue list with filters
const issueUrl = new URL('https://api.github.com/repos/owner/repo/issues');
issueUrl.searchParams.set('state', 'open'); // open, closed, all
issueUrl.searchParams.set('labels', 'bug,priority'); // comma-separated
issueUrl.searchParams.set('assignee', 'username');
issueUrl.searchParams.set('sort', 'updated'); // created, updated, comments
issueUrl.searchParams.set('direction', 'desc'); // asc, desc
issueUrl.searchParams.set('since', '2024-01-01T00:00:00Z');
issueUrl.searchParams.set('per_page', '50');
// Search repositories
const searchUrl = new URL('https://api.github.com/search/repositories');
searchUrl.searchParams.set('q', 'language:typescript stars:>1000');
searchUrl.searchParams.set('sort', 'stars');
searchUrl.searchParams.set('order', 'desc');
// Search code
const codeSearchUrl = new URL('https://api.github.com/search/code');
codeSearchUrl.searchParams.set('q', 'filename:tsconfig.json repo:microsoft/typescript');
// Pull requests with filters
const prUrl = new URL('https://api.github.com/repos/owner/repo/pulls');
prUrl.searchParams.set('state', 'open');
prUrl.searchParams.set('head', 'owner:branch-name'); // Filter by head branch
prUrl.searchParams.set('base', 'main'); // Filter by base branchThese examples show how to construct URLs for common filtering scenarios. The search endpoints use a query language that supports operators like language:, stars:, and repo:. For pull requests, the head parameter uses the owner:branch format to filter by source branch.
While REST requires multiple requests for nested data, GraphQL lets you request exactly the fields you need in a single call. GitHub's GraphQL endpoint accepts POST requests with your query, returning a JSON response shaped to match your query structure.
GraphQL Endpoint
// GraphQL uses single endpoint with POST
const response = await fetch('https://api.github.com/graphql', {
method: 'POST',
headers: {
'Authorization': 'Bearer YOUR_TOKEN',
'Content-Type': 'application/json'
},
body: JSON.stringify({
query: `
query {
repository(owner: "facebook", name: "react") {
issues(first: 10, states: OPEN) {
nodes {
title
number
createdAt
author {
login
}
}
pageInfo {
hasNextPage
endCursor
}
}
}
}
`
})
});
// GraphQL pagination uses cursors
const nextPageQuery = `
query($cursor: String) {
repository(owner: "facebook", name: "react") {
issues(first: 10, after: $cursor, states: OPEN) {
nodes { title number }
pageInfo { hasNextPage endCursor }
}
}
}
`;
// Variables for cursor-based pagination
const variables = {
cursor: "Y3Vyc29yOnYyOpK5MjAyNC0wMS0xNVQxMDowMDowMC0wODowMM4-xyz"
};GraphQL uses cursor-based pagination rather than page numbers. The pageInfo object tells you if more results exist and provides the cursor value for the next request. Pass the endCursor as the after variable to fetch subsequent pages.
GitHub webhooks push events to your server when things happen in a repository. You configure a payload URL in the repository settings, and GitHub sends POST requests with event data whenever matching events occur.
Webhook URLs
// Configure webhook URL in repository settings
// Settings > Webhooks > Add webhook
// Webhook payload URL receives POST requests
// https://yourapp.com/webhooks/github
const express = require('express');
const crypto = require('crypto');
app.post('/webhooks/github', express.raw({ type: 'application/json' }), (req, res) => {
// Validate signature
const signature = req.headers['x-hub-signature-256'];
const payload = req.body;
const expectedSignature = 'sha256=' + crypto
.createHmac('sha256', process.env.GITHUB_WEBHOOK_SECRET)
.update(payload)
.digest('hex');
if (!crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expectedSignature)
)) {
return res.status(401).send('Invalid signature');
}
const event = req.headers['x-github-event'];
const data = JSON.parse(payload);
switch (event) {
case 'push':
console.log(`Push to ${data.ref} by ${data.pusher.name}`);
break;
case 'pull_request':
console.log(`PR ${data.action}: ${data.pull_request.title}`);
break;
case 'issues':
console.log(`Issue ${data.action}: ${data.issue.title}`);
break;
}
res.sendStatus(200);
});This webhook handler validates the X-Hub-Signature-256 header using HMAC-SHA256 with your webhook secret. The timing-safe comparison prevents timing attacks. The X-GitHub-Event header identifies the event type so you can route to the appropriate handler logic.
Proper authentication is essential for API access, but you must be careful about how you include credentials. Putting tokens in URLs is a common mistake that can expose your credentials in logs, browser history, and referrer headers.
Authentication in URLs
// Never put tokens in URLs! Use headers instead
// ❌ Bad: Token in URL (logged, cached, visible)
fetch('https://api.github.com/user?access_token=ghp_xxxx');
// ✅ Good: Token in header
fetch('https://api.github.com/user', {
headers: {
'Authorization': 'Bearer ghp_xxxx',
'Accept': 'application/vnd.github+json',
'X-GitHub-Api-Version': '2022-11-28'
}
});
// OAuth authorization URL
const authUrl = new URL('https://github.com/login/oauth/authorize');
authUrl.searchParams.set('client_id', 'YOUR_CLIENT_ID');
authUrl.searchParams.set('redirect_uri', 'https://yourapp.com/callback');
authUrl.searchParams.set('scope', 'repo read:user');
authUrl.searchParams.set('state', crypto.randomBytes(16).toString('hex'));
// Token exchange URL (POST, not GET)
// POST https://github.com/login/oauth/access_token
// with code, client_id, client_secret in bodyAlways use the Authorization header for tokens, never URL parameters. The OAuth authorization URL is an exception since it's a user-facing redirect that must pass client_id as a parameter. The actual token exchange happens via POST with credentials in the request body.