Build a REST API Client in Bash Using Curl and jq

Build a REST API Client in Bash Using Curl and jq

Build a REST API Client in Bash Using Curl and jq

 

Interacting with RESTful APIs doesn’t always require Python, Node, or Postman. You can harness the simplicity and ubiquity of Bash, combined with the power of curl and jq, to script robust API clients right from the terminal. This article shows you how to do just that—handling authentication, crafting requests, and parsing JSON responses in clean, reusable scripts.

1. Setting Up: The Tools You Need

All you need to build an API client in Bash are two command-line utilities:

  • curl: Sends HTTP requests
  • jq: Parses and manipulates JSON data

Install them via your package manager if you don’t already have them:

# Ubuntu/Debian
sudo apt install curl jq

# macOS (with Homebrew)
brew install curl jq

2. Making Your First GET Request

Let’s begin by making a simple GET request to a publicly accessible API—the GitHub API, for example. We’ll retrieve information about a GitHub user.

#!/bin/bash

USERNAME="octocat"
API_URL="https://api.github.com/users/$USERNAME"

response=$(curl -s "$API_URL")

# Extract and print name using jq
name=$(echo "$response" | jq -r '.name')
echo "Name: $name"

Explanation:

  • curl -s: Silently fetches data from the specified URL.
  • jq -r '.name': Extracts the name field from the JSON response, with -r for raw output.

This kind of interaction is perfect for quick data pulls, piping into other scripts, or monitoring systems.

3. Handling Authentication

Most APIs require some form of authentication. For instance, GitHub accepts personal access tokens via headers. Here’s how to authenticate:

#!/bin/bash

TOKEN="your_github_token_here"
USERNAME="octocat"
API_URL="https://api.github.com/users/$USERNAME"

response=$(curl -s -H "Authorization: token $TOKEN" "$API_URL")
echo "$response" | jq

Other common authentication styles include:

  • Bearer tokens: -H "Authorization: Bearer $TOKEN"
  • API keys in URL: ?apikey=$KEY

Keep tokens secret using environment variables or .env files rather than hardcoding credentials in scripts.

4. Sending POST Requests with JSON

Many APIs expect data to be sent via POST requests. To do this in Bash, you’d serialize your data to JSON and send it with the right Content-Type header.

#!/bin/bash

API_URL="https://jsonplaceholder.typicode.com/posts"

DATA=$(jq -n \
  --arg title "Bash + API" \
  --arg body "This post was made from a bash script" \
  --arg userId "1" \
  '{title: $title, body: $body, userId: $userId}')

response=$(curl -s -X POST "$API_URL" \
  -H "Content-Type: application/json" \
  -d "$DATA")

# Display the created post's ID and title
echo "$response" | jq '.id, .title'

jq -n builds structured JSON on the fly—cleaner and safer than manually concatenating strings.

5. Creating Reusable Bash Functions

For reliability and maintainability, abstracting logic into reusable functions is key. Example: a generic GET function.

#!/bin/bash

API_GET() {
  local url=$1
  local token=$2
  curl -s -H "Authorization: token $token" "$url"
}

API_URL="https://api.github.com/repos/octocat/Hello-World"
TOKEN="your_token_here"

response=$(API_GET "$API_URL" "$TOKEN")
echo "$response" | jq '.full_name, .stargazers_count'

This pattern promotes DRY (Don’t Repeat Yourself) principles and makes testing easier.

6. Error Handling and Status Codes

Always check HTTP response codes to gracefully handle errors:

#!/bin/bash

API_URL="https://api.github.com/repos/octocat/Hello-World"
response=$(curl -s -w "\n%{http_code}" "$API_URL")

# Split the response and the status code
http_body=$(echo "$response" | sed '$d')
status_code=$(echo "$response" | tail -n1)

if [[ "$status_code" -eq 200 ]]; then
  echo "$http_body" | jq
else
  echo "Error: HTTP $status_code"
fi

-w "%{http_code}" appends the status code, which we later extract to check for errors.

7. Pro Tips for Performance and Maintainability

  • Use caching wisely: Save frequent responses to files with jq and reuse them.
  • Pipe safely: Always quote JSON variables and fields in bash scripts to avoid unexpected parsing behavior.
  • Use set -e in production-grade scripts to exit on error.
  • Modularize your code: Put helper functions in separate files and source them when needed.

Conclusion

Bash, curl, and jq form a compact yet powerful toolkit for working with REST APIs directly from the terminal. Whether you’re automating tasks, integrating internal services, or just exploring an API, this stack is light, fast, and surprisingly scalable for many scripting use cases. Happy hacking—and don’t forget to chmod +x your scripts!

 

Useful links: