cURL for Developers -- Test Any API From Your Terminal in Minutes
cURL for Developers -- Test Any API From Your Terminal in Minutes
Every developer needs to test APIs. Whether you are debugging a broken endpoint, verifying a deployment, or exploring a third-party API, you need a way to make HTTP requests and inspect the responses.
Postman and Thunder Client are great tools. But cURL is everywhere -- it is installed on every Mac, Linux, and modern Windows machine. It works over SSH. It works in CI/CD pipelines. It works in Docker containers. Learning cURL means you can test APIs anywhere, on any machine, without installing anything.
The Basics
Your First GET Request
> curl https://api.github.com/users/octocat
That is it. cURL makes a GET request by default and prints the response body to your terminal. You just hit the GitHub API without opening a browser.
Pretty-Print JSON Responses
Raw JSON is hard to read. Pipe the output through jq to format it:
> curl -s https://api.github.com/users/octocat | jq .
The -s flag (silent) suppresses the progress bar so only the JSON output goes to jq.
If you do not have jq installed:
- •macOS: brew install jq
- •Ubuntu: sudo apt install jq
- •Python alternative: pipe through python3 -m json.tool
See Response Headers
> curl -i https://api.github.com/users/octocat
The -i flag includes response headers in the output. You can see the status code, content type, rate limit headers, cache headers, and more.
See ONLY Headers (No Body)
> curl -I https://api.github.com/users/octocat
Capital -I sends a HEAD request -- you get headers without downloading the response body. Useful for checking if a URL is valid, what content type it returns, or how big the response is.
Verbose Mode (See Everything)
> curl -v https://api.github.com/users/octocat
The -v flag shows the full conversation: DNS resolution, TCP connection, TLS handshake, request headers, response headers, and response body. This is your best debugging tool when something is not working.
Making Different Types of Requests
POST Request with JSON Body
> curl -X POST https://api.example.com/users \
> -H "Content-Type: application/json" \
> -d '{"name": "Alice", "email": "alice@example.com"}'
Breaking it down:
- •-X POST -- use the POST method
- •-H -- add a header (Content-Type tells the server we are sending JSON)
- •-d -- the request body (data)
PUT Request (Update)
> curl -X PUT https://api.example.com/users/123 \
> -H "Content-Type: application/json" \
> -d '{"name": "Alice Updated"}'
PATCH Request (Partial Update)
> curl -X PATCH https://api.example.com/users/123 \
> -H "Content-Type: application/json" \
> -d '{"email": "newemail@example.com"}'
DELETE Request
> curl -X DELETE https://api.example.com/users/123
Authentication
Bearer Token (Most Common)
> curl https://api.example.com/me \
> -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..."
Basic Auth
> curl -u username:password https://api.example.com/resource
cURL encodes the credentials in Base64 and sends them in the Authorization header automatically.
API Key in Header
> curl https://api.example.com/data \
> -H "X-API-Key: your-api-key-here"
API Key in Query Parameter
> curl "https://api.example.com/data?api_key=your-api-key-here"
Note the quotes around the URL -- without them, the shell might interpret the & character in URLs with multiple query parameters.
Working with Headers
Set Custom Headers
> curl https://api.example.com/data \
> -H "Accept: application/json" \
> -H "X-Request-ID: abc123" \
> -H "Cache-Control: no-cache"
You can add as many -H flags as you need.
Common Headers You Will Use
- •Content-Type: application/json -- telling the server your request body is JSON
- •Accept: application/json -- asking the server to respond with JSON
- •Authorization: Bearer TOKEN -- authentication
- •Cache-Control: no-cache -- bypass caching
- •User-Agent: MyApp/1.0 -- identify your client
File Uploads
Upload a File with multipart/form-data
> curl -X POST https://api.example.com/upload \
> -F "file=@/path/to/photo.jpg" \
> -F "description=My vacation photo"
The -F flag creates a multipart form submission. The @ symbol tells cURL to read from a file rather than sending the literal text.
Upload Raw File Content
> curl -X POST https://api.example.com/upload \
> -H "Content-Type: image/jpeg" \
> --data-binary @/path/to/photo.jpg
--data-binary sends the file contents as the raw request body without any encoding.
Downloading Files
Save Response to a File
> curl -o output.json https://api.example.com/data
> curl -O https://example.com/file.zip
-o lets you specify the filename. -O uses the filename from the URL.
Download with Progress Bar
> curl -# -O https://example.com/large-file.zip
The -# flag shows a progress bar instead of the default progress meter.
Resume a Failed Download
> curl -C - -O https://example.com/large-file.zip
-C - tells cURL to figure out where the download left off and resume from there.
Debugging API Issues
Follow Redirects
By default, cURL does not follow redirects. If you get a 301 or 302, add -L:
> curl -L https://example.com/old-url
Set a Timeout
Prevent cURL from hanging forever on a slow server:
> curl --connect-timeout 5 --max-time 10 https://api.example.com/slow-endpoint
--connect-timeout is how long to wait for the connection. --max-time is the total time allowed for the entire operation.
Send Request Body from a File
For large or complex JSON bodies, save them in a file:
> curl -X POST https://api.example.com/data \
> -H "Content-Type: application/json" \
> -d @request-body.json
The @ prefix reads the file contents as the request body.
Check Response Time
> curl -o /dev/null -s -w "\nHTTP Code: %{http_code}\nTotal Time: %{time_total}s\nDNS Time: %{dns_time}s\nConnect Time: %{time_connect}s\nTLS Time: %{time_appconnect}s\nFirst Byte: %{time_starttransfer}s\n" https://example.com
This outputs timing information without the response body:
- •time_total -- entire request duration
- •time_starttransfer -- time to first byte (TTFB)
- •dns_time -- DNS resolution time
- •time_connect -- TCP connection time
- •time_appconnect -- TLS handshake time
This is incredibly useful for diagnosing slow API responses. Is it DNS? The connection? TLS? Or the server processing?
Real-World Examples
Test a REST API CRUD Flow
> # Create a user
> curl -s -X POST http://localhost:3000/api/users \
> -H "Content-Type: application/json" \
> -d '{"name": "Alice", "email": "alice@example.com"}' | jq .
>
> # Read the user
> curl -s http://localhost:3000/api/users/1 | jq .
>
> # Update the user
> curl -s -X PUT http://localhost:3000/api/users/1 \
> -H "Content-Type: application/json" \
> -d '{"name": "Alice Smith"}' | jq .
>
> # Delete the user
> curl -s -X DELETE http://localhost:3000/api/users/1 | jq .
Check if a Website Is Up
> curl -s -o /dev/null -w "%{http_code}" https://example.com
Returns just the HTTP status code. Perfect for monitoring scripts.
Test Webhook Endpoints
> curl -X POST http://localhost:3000/webhooks/stripe \
> -H "Content-Type: application/json" \
> -d '{"type": "payment_intent.succeeded", "data": {"object": {"id": "pi_123", "amount": 2000}}}'
Download and Inspect Response Headers
> curl -s -D - https://example.com -o /dev/null
-D - dumps headers to stdout while -o /dev/null discards the body.
Send a GraphQL Query
> curl -X POST https://api.example.com/graphql \
> -H "Content-Type: application/json" \
> -H "Authorization: Bearer YOUR_TOKEN" \
> -d '{"query": "{ users { id name email } }"}'
Test CORS Headers
> curl -I -X OPTIONS https://api.example.com/endpoint \
> -H "Origin: https://your-frontend.com" \
> -H "Access-Control-Request-Method: POST"
Check the response for Access-Control-Allow-Origin and Access-Control-Allow-Methods headers.
Saving and Reusing Requests
Use a .curlrc File
Create ~/.curlrc with default options:
> -s
> --connect-timeout 10
> -H "Accept: application/json"
Now every cURL command uses these defaults automatically.
Save Requests as Shell Scripts
For APIs you test frequently, save the commands in a script:
> #!/bin/bash
> # test-api.sh
>
> BASE_URL="http://localhost:3000/api"
> TOKEN="your-dev-token"
>
> echo "=== Health Check ==="
> curl -s "$BASE_URL/health" | jq .
>
> echo "=== Get Users ==="
> curl -s -H "Authorization: Bearer $TOKEN" "$BASE_URL/users" | jq .
>
> echo "=== Create User ==="
> curl -s -X POST "$BASE_URL/users" \
> -H "Authorization: Bearer $TOKEN" \
> -H "Content-Type: application/json" \
> -d '{"name": "Test User", "email": "test@example.com"}' | jq .
Make it executable with chmod +x test-api.sh and run it with ./test-api.sh.
Use Environment Variables for Secrets
Never hardcode tokens in scripts that might be committed to Git:
> export API_TOKEN="your-token-here"
> curl -H "Authorization: Bearer $API_TOKEN" https://api.example.com/me
cURL vs Other Tools
When to Use cURL
- •Quick one-off requests
- •Debugging on remote servers via SSH
- •CI/CD pipelines and shell scripts
- •When you need to see the raw HTTP conversation (-v)
- •When you do not want to install anything
When to Use Postman / Thunder Client
- •Complex APIs with many endpoints to organize
- •Team collaboration (shared collections)
- •Automated test suites with assertions
- •OAuth2 flows with automatic token refresh
- •When you need a visual interface
When to Use HTTPie
If you find cURL syntax too verbose, HTTPie is a friendlier alternative:
> http GET api.example.com/users
> http POST api.example.com/users name=Alice email=alice@example.com
It is more readable but less universal -- cURL is installed everywhere, HTTPie is not.
Quick Reference
| Task | Command |
|---|---|
| GET request | curl URL |
| POST with JSON | curl -X POST -H "Content-Type: application/json" -d '{}' URL |
| Add auth header | curl -H "Authorization: Bearer TOKEN" URL |
| See headers | curl -i URL |
| Follow redirects | curl -L URL |
| Save to file | curl -o file.txt URL |
| Upload file | curl -F "file=@path" URL |
| Verbose debug | curl -v URL |
| Pretty JSON | curl -s URL | jq . |
| Response time | curl -o /dev/null -s -w "%{time_total}" URL |
| Status code only | curl -s -o /dev/null -w "%{http_code}" URL |
cURL is one of those tools that feels clunky at first but becomes second nature after a week of daily use. Once you are comfortable with it, you will reach for it before opening any GUI tool because it is simply faster for most tasks.
For more developer guides and free tools, check out our blog and explore our developer tools.