Thuta Learning
AdvancedDevOps & Toolsbeginner

Calling APIs with curl and jq

What you'll walk away with

  • Explain the core ideas behind Calling APIs with curl and jq
  • Run the sample code and verify its output
  • Apply the technique correctly to the Tutorial Platform and production scenarios

Build the mental model

Bash scripts often need to talk to a web API — checking a service's health, pulling deployment status, triggering a webhook — and curl is the standard tool for making that HTTP request from the command line. curl -s makes the request quietly, without curl's usual progress meter cluttering the output, but note that -s also silences curl's own error messages, so many scripts pair it with -S (curl -sS) to keep errors visible while hiding the progress bar. You can capture the HTTP status code alongside the response body using -w "%{http_code}", or take a shortcut with -f, which makes curl itself return a nonzero exit status on 4xx/5xx responses — handy because a set -e script then stops automatically on a bad response, though -f also suppresses the body on error, so if you need to see what the server actually said, capturing status and body separately works better. Once you have a JSON body, curl can't do anything more with it — that's where jq comes in. jq '.field' prints a field as JSON, so a string value comes back wrapped in quotes exactly as JSON encodes it; jq -r '.field' prints the same value 'raw', with the quotes stripped, which is what you almost always want when assigning the result into a bash variable, since otherwise those literal quote characters end up baked into the value. jq also indexes into arrays with .roles[0], chains lookups together, and pretty-prints an entire object when given just '.' — and like curl, jq itself exits nonzero on invalid JSON, which a set -e script will also catch.

Connect it to a real scenario

Say you're writing a deploy script that pings a status API before rolling out, extracts the currently deployed version, and only proceeds if it differs from the new version being deployed — skipping unnecessary redeploys. The script uses curl -sf to fetch the JSON status, which fails loudly with a nonzero exit if the API is unreachable or returns an error, letting set -e halt the deploy right there instead of proceeding against a service that might not even be up. Once the JSON body is in hand, jq -r '.version' pulls out just the version string, stripped of its surrounding quotes, so it can be compared directly against the new version with a plain bash string comparison like [ "$current_version" = "$new_version" ]. Without jq, you'd be stuck trying to extract that one field with sed or grep and a regex, which technically works until the API's formatting changes even slightly — a field reordered, extra whitespace added, a nested object introduced — at which point a regex silently breaks or matches the wrong thing, while jq keeps working because it actually parses the JSON structure rather than pattern-matching against its text.

Try the working example

bash
#!/bin/bash
set -euo pipefail

# In a real script you would fetch this with curl, e.g.:
#   response=$(curl -sf -w "\n%{http_code}" "https://api.example.com/users/42")
#   http_code=$(echo "$response" | tail -n1)
#   body=$(echo "$response" | sed '$d')
#   [ "$http_code" = "200" ] || { echo "request failed: $http_code" >&2; exit 1; }
#
# Here we use a literal JSON string standing in for that response body,
# so the example is deterministic and needs no network access.
response='{"id":42,"name":"Ada Lovelace","active":true,"roles":["admin","editor"]}'

echo "Full response:"
echo "$response" | jq '.'

name=$(echo "$response" | jq -r '.name')
echo "Extracted name: $name"

first_role=$(echo "$response" | jq -r '.roles[0]')
echo "First role: $first_role"

is_active=$(echo "$response" | jq '.active')
echo "Active field (raw JSON value): $is_active"
You should see
Running the script above prints:

Full response:
{
  "id": 42,
  "name": "Ada Lovelace",
  "active": true,
  "roles": [
    "admin",
    "editor"
  ]
}
Extracted name: Ada Lovelace
First role: admin
Active field (raw JSON value): true

5-minute try-it

Extend the jq example to pull out the second role from the roles array instead of the first, and add a line that prints how many roles the array contains using jq's length filter.

One important caution

Forgetting -r and assigning jq's quoted JSON string output straight into a bash variable, ending up with literal double quotes baked into the value

Not checking curl's exit status or HTTP code before piping its output into jq — an error page or empty body from a failed request makes jq fail with a confusing 'parse error' far from the real cause

jq ManualBash / Shell Scripting

Easy traps

  • Forgetting -r and assigning jq's quoted JSON string output straight into a bash variable, ending up with literal double quotes baked into the value
  • Not checking curl's exit status or HTTP code before piping its output into jq — an error page or empty body from a failed request makes jq fail with a confusing 'parse error' far from the real cause
  • Validate sample code in a local or test environment before applying it to a production system.

Exercise

Extend the jq example to pull out the second role from the roles array instead of the first, and add a line that prints how many roles the array contains using jq's length filter.

You'll know it worked when: Running the script above prints: Full response: { "id": 42, "name": "Ada Lovelace", "active": true, "roles": [ "admin", "editor" ] } Extracted name: Ada Lovelace First role: admin Active field (raw JSON value): true

Calling APIs with curl and jq | Thuta Learning