cURL Advanced

API Testing & Tooling
Chapter 7 ยท cURL Advanced

๐Ÿš€ cURL Advanced

Chapter 6 covered the flags. This chapter covers the patterns that make cURL genuinely usable day to day: real auth handling, file uploads, readable JSON output, and turning a one-off command into something saved, shared, and re-run.

Bearer Tokens & API Keys, in Practice

Chapter 6's basic auth example (-u) is fine for that specific auth style, but most real APIs use a bearer token or an API key instead. Both go in a header โ€” the practical question is where the token's actual value comes from.

# Hardcoding the token directly โ€” works, but see this chapter's closing gotcha curl -H "Authorization: Bearer abc123realtoken" https://api.example.com/me # Reading it from an environment variable instead โ€” the safer default curl -H "Authorization: Bearer $TOKEN" https://api.example.com/me

An API key sometimes goes in a custom header (X-API-Key: ...) instead of Authorization โ€” check the API's documentation for which it expects. Either way, avoid putting a key in the URL as a query parameter (?api_key=...) if a header option exists: URLs routinely end up in server access logs, browser history, and proxy logs, all places a header's contents typically don't.

File Uploads: -F

Uploading a file needs multipart/form-data, a different body format from -d's JSON or form-urlencoded โ€” that's what -F is for. The @ prefix tells curl to read a field's value from a file on disk rather than treating it as literal text.

curl -X POST https://api.example.com/upload \ -F "file=@photo.jpg" \ -F "caption=Sunset over the harbor"

Mixing -F with -d in the same request doesn't work as expected โ€” they build genuinely different body formats, so a request is either -d's style or -F's multipart style, not both at once.

Piping to jq for Readable JSON

A raw JSON response prints as one dense, hard-to-read line. Piping it through jq โ€” already covered for general shell use in bash_intermediate_07 โ€” pretty-prints it, and can filter down to just the fields that matter.

curl -s https://api.example.com/products/42 | jq # Just the name field: curl -s https://api.example.com/products/42 | jq '.name' # Every item's price, from an array response: curl -s https://api.example.com/orders/55 | jq '.items[] | .price'

The -s (silent) flag suppresses curl's progress meter, which would otherwise clutter the output being piped into jq.

Saving & Replaying Requests as Scripts

A command retyped (or found again in shell history) every time isn't much better than starting from scratch. Saving it as a small shell script makes it reusable and shareable โ€” the cURL equivalent of Chapter 5's .http files.

#!/bin/bash # create-product.sh curl -X POST "$BASE_URL/products" \ -H "Content-Type: application/json" \ -H "Authorization: Bearer $TOKEN" \ --data @product.json

--data @product.json loads the body from a separate file rather than an inline string โ€” useful once a body gets long enough to be awkward as one quoted line. Both $BASE_URL and $TOKEN come from the environment, not hardcoded โ€” set once, reused by any script that needs them.

An Inline Command vs. a Saved Script

Inline, Retyped Each Time

Works once, then has to be found again in shell history or retyped from memory โ€” easy to introduce a small mistake re-copying a long command.

Saved as a Script

chmod +x create-product.sh && ./create-product.sh โ€” reusable, shareable with a teammate, and can be committed to the repo like any other script.

Flag / PatternPurpose
-F "field=@file"Multipart file upload
--data @file.jsonLoad the request body from a file instead of inline text
-o fileSave the response body to a file instead of printing it
| jqPretty-print and filter a JSON response
$VAR in a scriptReads a value from the environment rather than hardcoding it

Putting It Together: A Reusable Upload Script

#!/bin/bash # upload-avatar.sh curl -s -X POST "$BASE_URL/users/me/avatar" \ -H "Authorization: Bearer $TOKEN" \ -F "avatar=@$1" \ | jq '.avatarUrl'

Run as ./upload-avatar.sh photo.jpg โ€” $1 is the shell's first script argument, so the same script uploads any file passed to it, authenticates via an environment variable, and prints just the resulting URL, readably, via jq.

๐Ÿ’ป Coding Challenges

Challenge 1: Fix the Upload Command

A developer writes curl -X POST https://api.example.com/upload -d "file=@photo.jpg" and the file arrives corrupted/unusable server-side. What's wrong, and what's the fix?

Goal: Practice recognizing that -d and -F build genuinely different body formats, not interchangeable syntax for "attach a file."

โ†’ Solution

Challenge 2: Filter a Response With jq

Given a response { "id": 42, "items": [{ "name": "Mouse", "price": 24.99 }, { "name": "Keyboard", "price": 59.99 }] }, write the jq filter to print just the array of item names.

Goal: Practice writing a jq filter over an array field, building on bash_intermediate_07's coverage.

โ†’ Solution

Challenge 3: Make a Script Reusable

Rewrite this hardcoded command as a script using environment variables instead: curl -H "Authorization: Bearer abc123" https://api.example.com/orders, so the same script works for any base URL or token without editing the file.

Goal: Practice the environment-variable substitution pattern this chapter uses for reusable scripts.

โ†’ Solution

โš ๏ธ Gotcha: A Real Token Hardcoded Into a Saved, Committed Script

Saving a script like Chapter 6/7's examples feels like good practice โ€” until the token inside it is a real, working credential, and the script gets committed to a shared Git repository. This is the exact same mistake as a hardcoded database connection string or a committed API key covered elsewhere on this site: once pushed, that token is compromised, regardless of whether the file is later edited or deleted. Every script in this chapter deliberately reads $TOKEN from the environment rather than embedding a literal value โ€” that's not a style preference, it's the difference between a script that's safe to commit and one that isn't.

๐ŸŽฏ What's Next

The next chapter is Browser-Based API Testing โ€” the simplest tool of all: URL query parameters, HTML forms, and reading real requests in the DevTools Network tab.