ToolPal
Terminal showing command-line HTTP requests

cURL for Developers: Stop Googling the Flags and Start Building Commands

πŸ“· Pexels / Pexels

cURL 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.

March 28, 202610 min read

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.

Frequently Asked Questions

Share this article

XLinkedIn

Related Posts