
cURL for Developers: Stop Googling the Flags and Start Building Commands
π· Pexels / PexelscURL for Developers: Stop Googling the Flags and Start Building Commands
cURL is one of those tools every developer uses but few fully understand. Here's a practical guide to building cURL commands without memorizing every flag.
If you've been a developer for more than a few months, you've used cURL. You've probably also Googled "curl post request json" more times than you'd like to admit. There's no shame in it β cURL has a lot of flags, and the syntax is dense enough that even experienced developers don't keep it all in memory.
What most developers don't realize is that you only need to deeply understand maybe a dozen flags to cover 90% of your daily API testing needs. The rest you can look up when you need them, or use a builder to assemble the command without memorizing the syntax.
This guide focuses on the parts of cURL that actually matter, with real examples you can run today.
Why cURL at All?
Fair question. You've got Postman, Insomnia, VS Code's REST Client extension, browser DevTools β why reach for cURL?
A few reasons:
It's always there. cURL comes pre-installed on macOS and most Linux systems. On a fresh server, in a Docker container, on a colleague's machine β cURL is available. Postman is not.
It's scriptable. You can drop a cURL command into a bash script, a CI pipeline, a cron job, a Makefile. It becomes part of your infrastructure. You can't do that with a GUI tool.
It's shareable. Paste a cURL command into a Slack message or a GitHub issue and anyone can run it immediately. No "here's how to recreate this in Postman" instructions needed.
It's the common language. Most API documentation shows cURL examples. When a colleague says "here's the request that's failing," they'll often send a cURL command. Reading cURL is a skill worth having.
That said, cURL is not always the right tool. Complex multi-step workflows, visual response inspection, or anything involving a lot of form data is often faster to manage in Postman. Use the right tool for the job.
The Flags You'll Actually Use
-X β HTTP Method
By default, cURL sends a GET request. Use -X to change the method:
curl -X POST https://api.example.com/users
curl -X PUT https://api.example.com/users/123
curl -X DELETE https://api.example.com/users/123
curl -X PATCH https://api.example.com/users/123
A common shorthand: you can skip -X POST when you use -d to send a body, because cURL will infer POST. But being explicit is clearer, especially when you're sharing the command with others.
-H β Add a Header
Almost every API request you make will need at least one header. Content-Type for POST/PUT requests, Authorization for authenticated endpoints:
# Set content type
curl -X POST https://api.example.com/users \
-H "Content-Type: application/json"
# Add auth token
curl https://api.example.com/profile \
-H "Authorization: Bearer eyJhbGciOiJSUzI1NiJ9..."
# Multiple headers
curl -X POST https://api.example.com/users \
-H "Content-Type: application/json" \
-H "Authorization: Bearer your-token" \
-H "X-Request-ID: abc123"
You can use -H multiple times in the same command, one flag per header.
-d β Request Body
Send data in the request body. Used with POST, PUT, and PATCH:
# JSON body
curl -X POST https://api.example.com/users \
-H "Content-Type: application/json" \
-d '{"name": "Alice", "email": "alice@example.com"}'
# Form data (URL-encoded)
curl -X POST https://api.example.com/login \
-d "username=alice&password=secret"
A note on the missing Content-Type header: if you send -d with JSON but forget -H "Content-Type: application/json", the server might reject the request or misparse the body. This is one of the most common cURL mistakes. Always set Content-Type when you know what you're sending.
-L β Follow Redirects
By default, if a server returns a 301 or 302 redirect, cURL stops and shows you the redirect response. Usually you want to follow it:
curl -L https://example.com/old-url
This comes up often when working with APIs that redirect HTTP to HTTPS, or when a resource has moved. Without -L, you'll just see 301 Moved Permanently and wonder why your request returned no data.
-i β Include Response Headers
By default, cURL only shows the response body. -i includes the response headers as well:
curl -i https://api.example.com/users/1
Output looks like:
HTTP/2 200
content-type: application/json
x-rate-limit-remaining: 99
...
{"id": 1, "name": "Alice"}
This is useful when you need to inspect status codes, Content-Type, caching headers, or rate limit information. -i is the lightweight alternative to verbose mode when you just want the headers without the full connection debug output.
-v β Verbose Mode
This is the debugging flag. -v shows you everything: the full request headers cURL sent, the TLS handshake, and the full response headers:
curl -v https://api.example.com/users
Sample output (abbreviated):
* Trying 93.184.216.34:443...
* Connected to api.example.com (93.184.216.34) port 443
* SSL connection using TLSv1.3
> GET /users HTTP/2
> Host: api.example.com
> User-Agent: curl/8.1.2
> Accept: */*
>
< HTTP/2 200
< content-type: application/json
<
{"users": [...]}
Lines starting with > are what cURL sent. Lines starting with < are what the server sent back. Lines starting with * are cURL's own status messages. This is invaluable when debugging authentication issues, unexpected headers, or TLS problems.
-u β Basic Authentication
For APIs using HTTP Basic Auth, you could manually construct the Authorization header, but -u does it automatically:
curl -u username:password https://api.example.com/protected
cURL encodes the credentials as Base64 and sends the proper Authorization: Basic ... header. One caveat: if you're pasting this into a terminal, your password will appear in your shell history. Either clear the history afterward, or use an environment variable:
curl -u "alice:$API_PASSWORD" https://api.example.com/protected
-k β Skip SSL Verification
This one deserves a word of caution. -k (or --insecure) tells cURL to ignore SSL certificate errors:
curl -k https://localhost:8443/api/health
This is legitimate for local development when you're using a self-signed certificate. It is not legitimate for production requests. If you're using -k against a real external service, you're bypassing security that exists for good reason. Use it only in development.
-o and -O β Save Response to File
Instead of printing the response to your terminal, save it:
# Save to a specific filename
curl -o output.json https://api.example.com/data
# Save using the remote filename
curl -O https://example.com/files/report.pdf
-o lets you choose the filename, -O uses whatever the URL's last path segment is.
-s β Silent Mode
Suppresses the progress bar and error messages. Useful in scripts where you only want the response body:
curl -s https://api.example.com/status | jq .
Piping to jq is a common pattern for pretty-printing JSON responses. -s keeps the output clean so jq only gets the JSON.
Common Mistakes
Forgetting Content-Type on POST requests
This gets developers every time. You're sending JSON, the server returns 400 Bad Request or 415 Unsupported Media Type, and you spend 20 minutes debugging before realizing the fix is adding one header:
# Missing header - server may reject this
curl -X POST https://api.example.com/users \
-d '{"name": "Alice"}'
# Correct
curl -X POST https://api.example.com/users \
-H "Content-Type: application/json" \
-d '{"name": "Alice"}'
Quoting issues on Windows
cURL on Windows Command Prompt handles quotes differently from bash. Single quotes don't work in CMD. You need to use double quotes and escape the inner quotes:
# Windows CMD - use double quotes, escape inner quotes
curl -X POST https://api.example.com/users ^
-H "Content-Type: application/json" ^
-d "{\"name\": \"Alice\"}"
In PowerShell, single quotes work but the escaping rules are still different. This is one of the biggest sources of confusion for Windows developers using cURL commands from documentation written for Unix systems. If you're on Windows and cURL commands from docs aren't working, the quoting is usually the culprit.
Sending a file as body instead of using @
When you want to send a file's contents as the request body, you use @filename:
# Send the contents of data.json as the body
curl -X POST https://api.example.com/import \
-H "Content-Type: application/json" \
-d @data.json
Without the @, cURL treats the string as a literal body value, not a filename. You'd be sending the literal text data.json instead of the file's contents.
Using -d with GET requests
-d sends a body. GET requests don't have a body (and servers generally ignore it). If you want to send query parameters with a GET request, put them in the URL:
# Correct for GET with parameters
curl "https://api.example.com/users?page=1&limit=20"
Note the quotes around the URL. Without them, the & in the URL might be interpreted by your shell as a background operator.
Putting It Together: Real-World Examples
Creating a user:
curl -X POST https://api.example.com/users \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
-d '{"name": "Bob", "email": "bob@example.com", "role": "editor"}'
Uploading a file:
curl -X POST https://api.example.com/upload \
-H "Authorization: Bearer $TOKEN" \
-F "file=@/path/to/document.pdf" \
-F "description=Q4 Report"
(Note: -F for multipart form data, not -d)
Checking a webhook with a custom payload:
curl -X POST https://your-app.com/webhooks/github \
-H "Content-Type: application/json" \
-H "X-GitHub-Event: push" \
-H "X-Hub-Signature-256: sha256=abc123" \
-d @sample-push-event.json
Downloading with progress:
curl -L -o large-file.zip https://example.com/downloads/archive.zip
Using a Builder Instead of Memorizing
For anything beyond a basic GET request, assembling the right combination of flags from memory gets old. The ToolPal cURL Command Builder lets you fill in the method, URL, headers, and body through a form and generates the cURL command for you β which you can then copy directly to your terminal or adapt as needed.
This is particularly useful for:
- Building commands with authentication headers you haven't written before
- Getting the syntax right for multipart form uploads
- Generating Windows-compatible commands when you're used to bash
- Sharing commands with teammates who might not know the flag syntax
The generated commands are standard cURL β no vendor lock-in, nothing proprietary. You use the builder as a productivity shortcut, then take the resulting command wherever you need it.
cURL vs. Other Tools
cURL vs. Postman: Postman wins for iterative API exploration, managing collections of requests, and team collaboration. cURL wins for scripting, quick one-off requests, and shareability. They're not competitors β most developers use both.
cURL vs. HTTPie: HTTPie is a newer command-line HTTP client with a friendlier interface and prettier output. If you're open to installing it, it's worth a look. cURL has the advantage of being available everywhere without installation.
cURL vs. browser DevTools: For inspecting what your browser is actually sending (cookies, session tokens, complex headers), DevTools is unbeatable. You can even right-click a request in the Network tab and copy it as a cURL command β a useful way to reproduce browser requests from the command line.
cURL rewards the time you put into learning it. Even if you never memorize every flag, understanding how the core flags work β -X, -H, -d, -L, -v β gives you a tool that works everywhere, scripts easily, and communicates clearly. That's worth more than any number of memorized syntax patterns.
When in doubt, reach for a builder. When you need to understand what went wrong, reach for -v.