Practice: GitHub API

About GitHub API

Real production API with public and authenticated endpoints.

Setup

Create Personal Access Token

  1. GitHub → Settings → Developer settings → Personal access tokens → Tokens (classic)

  2. Generate new token with scopes: repo, user, read:org

  3. Store in gopass:

gopass edit v3/dev/github
# Content:
---
token: ghp_xxxxxxxxxxxxxxxxxxxx
username: yourusername

Shell Function

gh_api() {
  local endpoint="${1:-user}"
  local jq_filter="${2:-.}"

  curl -sS \
    -H "Authorization: Bearer $(gopass show v3/dev/github | sed '1,/^---$/d' | yq -r '.token')" \
    -H "Accept: application/vnd.github+json" \
    -H "X-GitHub-Api-Version: 2022-11-28" \
    "https://api.github.com/${endpoint}" \
    | jq "$jq_filter"
}

Public Endpoints (No Auth)

User Info

# Public user profile
curl -s https://api.github.com/users/torvalds | jq '{name, company, location, public_repos, followers}'

# Your profile (if token set)
gh_api user

Repositories

# User's public repos
curl -s https://api.github.com/users/torvalds/repos | jq '.[0:5] | .[].name'

# Specific repo
curl -s https://api.github.com/repos/torvalds/linux | jq '{name, stars: .stargazers_count, forks: .forks_count}'

Rate Limit Check

# Check remaining requests
curl -s https://api.github.com/rate_limit | jq '.rate'

Authenticated Endpoints

Your Profile

# Full profile
gh_api user

# Your repos
gh_api "user/repos?per_page=5&sort=updated" '.[].full_name'

# Your stars
gh_api "user/starred?per_page=5" '.[].full_name'

Repository Operations

# List your repos
gh_api "user/repos?per_page=10" '[.[] | {name, private, updated_at}]'

# Get specific repo details
gh_api "repos/YOUR_USER/YOUR_REPO" '{name, default_branch, open_issues}'

# List branches
gh_api "repos/YOUR_USER/YOUR_REPO/branches" '.[].name'

# List commits
gh_api "repos/YOUR_USER/YOUR_REPO/commits?per_page=5" '.[].commit.message'

Star a Repository

# Star (PUT with no body)
curl -s -X PUT \
  -H "Authorization: Bearer $(gopass show v3/dev/github | sed '1,/^---$/d' | yq -r '.token')" \
  -H "Accept: application/vnd.github+json" \
  https://api.github.com/user/starred/owner/repo

# Unstar (DELETE)
curl -s -X DELETE \
  -H "Authorization: Bearer $(gopass show v3/dev/github | sed '1,/^---$/d' | yq -r '.token')" \
  https://api.github.com/user/starred/owner/repo

# Check if starred (204 = yes, 404 = no)
curl -s -o /dev/null -w '%{http_code}' \
  -H "Authorization: Bearer TOKEN" \
  https://api.github.com/user/starred/torvalds/linux

Issues

# List issues in a repo
gh_api "repos/cli/cli/issues?state=open&per_page=5" '[.[] | {number, title, user: .user.login}]'

# Create issue (your repo only)
curl -s -X POST \
  -H "Authorization: Bearer TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"title": "Test Issue", "body": "Created via API"}' \
  https://api.github.com/repos/YOUR_USER/YOUR_REPO/issues | jq

Gists

# List your gists
gh_api gists '[.[] | {id, description, public}]'

# Create gist
curl -s -X POST \
  -H "Authorization: Bearer TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "description": "API Test",
    "public": false,
    "files": {
      "test.txt": {"content": "Hello from API"}
    }
  }' \
  https://api.github.com/gists | jq '{id, html_url}'

Search API

Search Repositories

# Search by language and stars
curl -s "https://api.github.com/search/repositories?q=language:rust+stars:>10000&sort=stars" \
  | jq '.items[:5] | .[] | {name: .full_name, stars: .stargazers_count}'

Search Code

# Find code patterns (requires auth)
gh_api "search/code?q=filename:Dockerfile+FROM+rust" '.items[:3] | .[].repository.full_name'

Search Users

# Find users by location
curl -s "https://api.github.com/search/users?q=location:tokyo+followers:>1000" \
  | jq '.items[:5] | .[] | {login, followers}'

Pagination

GitHub uses Link headers for pagination:

# Check pagination headers
curl -sI "https://api.github.com/users/torvalds/repos?per_page=5" | grep -i link

# Fetch all pages
fetch_all_repos() {
  local user="$1"
  local page=1
  local all_repos="[]"

  while true; do
    local response
    response=$(curl -s "https://api.github.com/users/$user/repos?per_page=100&page=$page")
    local count
    count=$(echo "$response" | jq 'length')

    [[ "$count" -eq 0 ]] && break

    all_repos=$(echo "$all_repos" "$response" | jq -s 'add')
    ((page++))
  done

  echo "$all_repos"
}

# Usage
fetch_all_repos torvalds | jq 'length'

GraphQL API (Advanced)

GitHub also has GraphQL:

# GraphQL query
curl -s -X POST \
  -H "Authorization: Bearer TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "query": "query { viewer { login name repositories(first: 5) { nodes { name stargazerCount } } } }"
  }' \
  https://api.github.com/graphql | jq '.data.viewer'

Advanced Challenges

Challenge 1: Repo Statistics

# Get repo stats: commits, contributors, languages
repo="cli/cli"

echo "=== $repo ==="
echo "Stars: $(gh_api "repos/$repo" '.stargazers_count')"
echo "Forks: $(gh_api "repos/$repo" '.forks_count')"
echo "Open Issues: $(gh_api "repos/$repo" '.open_issues_count')"
echo "Languages:"
gh_api "repos/$repo/languages" 'to_entries | .[] | "  \(.key): \(.value)"' -r

Challenge 2: Activity Feed

# Your recent activity
gh_api "users/YOUR_USER/events?per_page=10" '.[] | "\(.type) on \(.repo.name) at \(.created_at)"' -r

Challenge 3: PR Review

# Open PRs needing review
gh_api "repos/cli/cli/pulls?state=open&per_page=5" '[.[] | {
  number,
  title,
  author: .user.login,
  created: .created_at,
  draft: .draft
}]'

Shell Function (Complete)

github() {
  local cmd="${1:-help}"
  shift

  local token
  token=$(gopass show v3/dev/github | sed '1,/^---$/d' | yq -r '.token')

  _gh_curl() {
    curl -sS \
      -H "Authorization: Bearer $token" \
      -H "Accept: application/vnd.github+json" \
      "$@"
  }

  case "$cmd" in
    me)
      _gh_curl https://api.github.com/user | jq '{login, name, public_repos, followers}'
      ;;
    repos)
      _gh_curl "https://api.github.com/user/repos?per_page=${1:-10}&sort=updated" | jq '.[].full_name' -r
      ;;
    repo)
      _gh_curl "https://api.github.com/repos/$1" | jq "${2:-.}"
      ;;
    star)
      _gh_curl -X PUT "https://api.github.com/user/starred/$1" && echo "Starred $1"
      ;;
    unstar)
      _gh_curl -X DELETE "https://api.github.com/user/starred/$1" && echo "Unstarred $1"
      ;;
    search)
      _gh_curl "https://api.github.com/search/repositories?q=$1&per_page=5" | jq '.items[] | {name: .full_name, stars: .stargazers_count}'
      ;;
    *)
      echo "Usage: github {me|repos|repo|star|unstar|search} [args]"
      ;;
  esac
}

Exercises Checklist

  • Get your profile info via API

  • List your 10 most recently updated repos

  • Star and unstar a repository via API

  • Search for Rust repos with 1000+ stars

  • Create a private gist via API

  • Fetch all pages of a user’s repos (handle pagination)

  • Build a function to show repo stats (stars, forks, issues)