Day #6 - Shell Scripting Project: GitHub API Integration

ArnabArnab
11 min read

Hey folks! It’s been a little while since my last blog, hasn’t it? I know, I know — I kind of left you hanging. But here’s the thing: I decided to hit the pause button for a bit. Between self-study sessions and making sure I didn’t slide into full-on tech fatigue mode, I figured a small break was necessary. After all, what good is learning if you burn yourself out halfway through, right?

Since the last blog we discussed about Git and GitHub, I thought it would be fun to take things one step further this time. Instead of just clicking around or typing commands, let’s see how we can actually talk to GitHub using its API. I’ve also added the complete script to my GitHub repo (look for list-users.sh) so you can clone it and try the commands yourself while following along.

So now that I’m back, recharged, I thought — why not tackle something that sounds intimidating at first but is actually a lot of fun once you break it down? That’s right: GitHub API integration.

We’ll walk through it step by step, the same way we did in the earlier blogs.

Why bother with GitHub API integration?

Imagine this:

  • You own a repository and someone asks you, “How many people have access to this repo?”
    The usual way is to go to Repo → Settings → Collaborators and maybe even take a screenshot to share.

  • Or, someone in your org resigns and you need to double-check if they have access to certain repos.
    Again, you’d log in, dig through settings, and manually revoke access.

  • Or maybe you want to check if someone has read/write access.
    Yep — more logging in, more clicking, more manual work.

For one-off checks, this is fine. But imagine doing this every day or across dozens of repos. That’s… well, painful.

This is where automation comes to the rescue. With a little bit of scripting and GitHub API integration, you can query this information automatically instead of babysitting the UI.

Ways to talk to an app: API vs CLI

Whenever we want to talk to an application programmatically, there are usually two ways:

  1. CLI (Command-Line Interface):
    For example, AWS provides the aws CLI. You run commands like aws s3 ls and it talks to AWS for you.

  2. API (Application Programming Interface):
    This is like talking directly to the application’s backend. Instead of typing commands, you hit URLs (endpoints) that return structured data (usually JSON).

And yes — APIs sound scarier than they are. Here’s a simple analogy:

Think of API as a menu card at a restaurant.

  • The restaurant is GitHub.

  • The menu is the API documentation (it tells you what you can ask for).

  • The waiter is the API request you send.

  • And the kitchen is GitHub’s backend, which prepares your order and returns the result.

As DevOps engineers, we usually don’t need to write APIs from scratch — our job is to consume them. For example, when we work with AWS in Python, we don’t reinvent the wheel — we use Boto3, which is just a Python library that wraps AWS’s APIs.

Reading GitHub API Docs

Let’s take a small example.
Suppose we want to see all the pull requests in a repo.

  • In the UI: we’d click the Pull requests tab.

  • But in a script: we need to know the right API URL to hit.

So how do we know the URL?
Simple — go to the GitHub API documentation and search for pull request.

Here’s what it shows:
GET /repos/{owner}/{repo}/pulls

That means if I replace {owner} with the repo owner and {repo} with the repository name, I’ll get the list of PRs.

For example, using curl:

curl -L \
  -H "Accept: application/vnd.github+json" \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "X-GitHub-Api-Version: 2022-11-28" \
  https://api.github.com/repos/OWNER/REPO/pulls

You’ll notice:

  • -H flags are just adding headers (like saying “hey waiter, I want my food in a certain format”).

  • We provide an Authorization token instead of username/password.

  • The result comes back in JSON format.

And that’s the general recipe:
Find the endpoint in the docs → Replace placeholders → Run it with curl (or any script).

Generating a GitHub Personal Access Token

Since we’re not using our username/password directly, we need a Personal Access Token (PAT) to authenticate.

Steps:

  1. Go to GitHub → Profile → Settings

  2. Navigate to Developer Settings → Personal Access Tokens → Tokens (classic)

  3. Click Generate new token

  4. Give it a name and select scopes/permissions you need (or all, depending on your use case)

  5. Generate and copy it somewhere safe (⚠️ if someone gets this token, they can access your repos!).

Once you have it, you can export it as environment variables:

export username="your-username"
export token="your-token"

This way, we don’t hardcode sensitive values into our scripts.

Our Automation Goal

Now let’s get back to our main scenario:
We want to list all the users who have access to a repository.

Why?

  • To keep track of collaborators.

  • To quickly revoke access if someone leaves the org.

  • To avoid manual digging in repo settings.

We’ll write a shell script (list-users.sh) that uses the GitHub API to fetch and display the list of users.

Breaking Down the Script — Step by Step

Starting with the Shebang

#!/bin/bash

This funny-looking line at the very top is called a shebang. It simply tells the computer,

“Hey, this script should be run with bash (the shell we’re using).”

Think of it like choosing the language for the script. Just like if you’re reading instructions, you want to know if they’re written in English or Spanish. Here, we’re saying “this script is in bash.”

Setting the Base GitHub API URL

API_URL="https://api.github.com"

This line is us declaring:

“Every request we make is going to GitHub’s API, and the starting point (base URL) is this.”

Instead of typing the whole URL everywhere in the script, we just define it once and reuse it.

Adding Credentials (But Safely!)

USERNAME=$username
TOKEN=$token

Here’s what’s happening:

  • Earlier, we exported our GitHub username and personal access token as environment variables.

  • This script now just grabs those values and stores them in USERNAME and TOKEN.

Why? Because hardcoding sensitive info like your token in the script is a big ❌ security risk. If someone saw your script, they’d also see your token. By exporting them separately, we keep the script clean and safe.

Accepting Repo Details as Arguments

REPO_OWNER=$1
REPO_NAME=$2

When we run the script, we’ll give it two inputs (arguments):

  • $1 → The owner of the repo (could be your GitHub username or your org name).

  • $2 → The repo name.

So if I run:

./list-users.sh kubernetes kubernetes

It means:

  • Repo owner = kubernetes

  • Repo name = kubernetes

This way, the same script can work for any repo — we don’t need to hardcode repo names inside.

Function to Talk to GitHub

We’ve written a small function in our script, and at first glance it might look scary — but trust me, it’s doing something very simple. Here’s the idea:

function github_api_get {
  local endpoint="$1"
  local url="${API_URL}/${endpoint}"

  curl -s -u "${USERNAME}:${TOKEN}" "$url"
}
  1. Taking your request
    First, you tell the function what you want to look at. For example:

    • repos/OWNER/REPO/collaborators → means “show me the collaborators of this repository.”
      This little piece of text is called an endpoint. Think of it as a “door” to a specific section of GitHub’s huge building of data.
  2. Building the full address
    GitHub’s main API lives at:

     https://api.github.com/
    

    The function simply attaches your endpoint to this main address.
    So if your endpoint was repos/octocat/hello-world/collaborators, the full link becomes:

     https://api.github.com/repos/octocat/hello-world/collaborators
    

    This is like taking a street name (endpoint) and attaching it to the city address (API URL) so you have the exact location.

  3. Knocking on the door with curl
    Now comes the fun part: the function uses a tool called curl.

    • Think of curl as your delivery guy. You give it an address, and it goes there, asks for the data, and brings it back to you.

    • Unlike your web browser, which shows you a pretty webpage, curl brings the raw response directly to your terminal.

Breaking down the curl command

Here’s what’s inside that curl call, step by step:

  • curl → This is the tool itself. It’s basically saying: “Hey, I want to fetch something from the internet.”

  • -s → This means silent mode. Without this, curl shows extra info like download progress or connection details. With -s, it just quietly fetches the data and shows you the result. Cleaner, less noisy.

  • -u "${USERNAME}:${TOKEN}" → This is the authentication part.
    GitHub won’t let strangers walk in and take data — you have to prove who you are. Normally, websites ask for a username and password. But with APIs, it’s better (and safer) to use a Personal Access Token instead of your real password.

    • ${USERNAME} is your GitHub username.

    • ${TOKEN} is like a special key or entry pass.

Think of it this way: imagine you’re entering a private library. You don’t just walk in and grab books. You show your library card (the token) along with your name. Once verified, the librarian (GitHub) gives you access.

So when this function runs, here’s what’s really happening in plain English:

“Take the endpoint I asked for, attach it to GitHub’s main API address, log me in using my username and token, and then quietly fetch the data for me.”

Function to List Collaborators

Now comes the fun part — the function that actually pulls out the list of collaborators from a GitHub repository.

Here’s the function we’ll be working with:

function list_users_with_read_access {
  local endpoint="repos/${REPO_OWNER}/${REPO_NAME}/collaborators"

  collaborators="$(github_api_get "$endpoint" | jq -r '.[] | select(.permissions.pull == true) | .login')"

  if [[ -z "$collaborators" ]]; then
    echo "No users with read access found for ${REPO_OWNER}/${REPO_NAME}."
  else
    echo "Users with read access to ${REPO_OWNER}/${REPO_NAME}:"
    echo "$collaborators"
  fi
}

Let’s break it down step by step

1. Defining the API endpoint

local endpoint="repos/${REPO_OWNER}/${REPO_NAME}/collaborators"

This is basically saying:
“Go to GitHub’s /repos/OWNER/REPO/collaborators endpoint, where OWNER is the repository owner and REPO is the repo name.”

That’s the special URL path the GitHub API uses to tell us who has access to a repo.

2. Calling our earlier helper function

collaborators="$(github_api_get "$endpoint" | jq -r '.[] | select(.permissions.pull == true) | .login')"
  • First, we call our helper function github_api_get (the one we wrote earlier that knows how to talk to the API with curl).

  • GitHub responds with JSON data. Think of JSON as a giant “dictionary” or “spreadsheet” in text form.

For example, the response might look like this:

[
  {
    "login": "alice",
    "permissions": {
      "pull": true,
      "push": false,
      "admin": false
    }
  },
  {
    "login": "bob",
    "permissions": {
      "pull": true,
      "push": true,
      "admin": false
    }
  }
]

That’s cool, but it’s messy. We don’t need all that — we just want the usernames of people who can at least read the repo.

3. Using jq to clean things up

This is where jq comes in — a tool for slicing and dicing JSON data in the terminal.

Let’s decode this part:

jq -r '.[] | select(.permissions.pull == true) | .login'
  • .[] → Go through each user in the list.

  • select(.permissions.pull == true) → Keep only users who have “pull” access (that’s GitHub’s word for read-only access).

  • .login → Print just the username.

So instead of the big JSON blob, you now get a neat list:

alice
bob

Much nicer, right?

4. Handling empty results

The final part is an if block:

if [[ -z "$collaborators" ]]; then
  echo "No users with read access found for ${REPO_OWNER}/${REPO_NAME}."
else
  echo "Users with read access to ${REPO_OWNER}/${REPO_NAME}:"
  echo "$collaborators"
fi
  • If there are no collaborators → it prints a friendly message.

  • If there are → it prints the list of usernames clearly.

This way, the script feels a bit more human and helpful, instead of just spitting out blank lines.

Putting it all together

So in plain English, this function does:

“Go to the GitHub API, grab the list of collaborators, filter out just the people with read access, and then print their usernames (or say none found).”

That’s why I called it the star of the show — because this is the function that takes all our setup and actually turns it into useful, readable results.

Running the Script

Now that we understand the moving parts, let’s run it:

  1. Make the script executable:

     chmod +x list-users.sh
    
  2. Run with repo details:

     ./list-users.sh kubernetes kubernetes
    
  3. Output might look like this:

     Listing users with read access to kubernetes/kubernetes...
     alice
     bob
    

Wrapping Up

And that’s a wrap! The cool part is, this is only the beginning. GitHub’s API can do way more than just list repositories — you can automate issues, pull requests, workflows, and even manage entire organizations. But the best way to learn is to start small, experiment with simple scripts like the one we built today, and slowly expand from there.

Also, remember what I said at the start: breaks are important. Tech can feel overwhelming sometimes, and it’s perfectly fine to step back, recharge, and then come back excited to build again. That’s how learning sticks.

So next time you’re on GitHub, don’t just think of it as a place to push code — think of it as a place you can interact with programmatically. With a little creativity and some Python (or any language of your choice), you can make GitHub work for you.

Until next time, keep building, keep growing, and most importantly — take care of yourself outside the terminal too 🫶

0
Subscribe to my newsletter

Read articles from Arnab directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Arnab
Arnab

Self-taught DevOps learner building in public. Sharing real-time progress, roadblocks, and the random “aha” moments. Hoping my notes help someone else feel less stuck. "Stop waiting to feel ready, stop waiting for the perfect moment, start messy, start scared and start before you feel qualified"