cURL Advanced
🚀 cURL Advanced
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.
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.
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.
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.
--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 / Pattern | Purpose |
|---|---|
-F "field=@file" | Multipart file upload |
--data @file.json | Load the request body from a file instead of inline text |
-o file | Save the response body to a file instead of printing it |
| jq | Pretty-print and filter a JSON response |
$VAR in a script | Reads a value from the environment rather than hardcoding it |
Putting It Together: A Reusable Upload Script
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."
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.
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.
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.