Using curl to Make REST API Requests

By 

•

Updated on

•

10 min read

Using curl to make REST API requests from the command line

Sooner or later, you will need to talk to an API without writing any application code. You may want to see what an endpoint returns, reproduce a bug someone reported, or test a token before you put it into a script. curl is the quickest way to do that from the terminal.

A REST API exposes resources over HTTP and usually returns JSON. REST stands for representational state transfer, an architectural style built on a set of constraints for web services. You use the standard HTTP methods to create, view, update, or delete those resources.

Every API request is made up of four parts:

  • Endpoint: The URL the client uses to communicate with the server.
  • HTTP method: Tells the server what action the client wants to perform. The most common methods are GET, POST, PUT, PATCH, and DELETE.
  • Headers: Pass additional information between the client and server, such as authentication tokens and content type.
  • Body: The data sent to the server.

This guide explains how to use curl to interact with RESTful APIs. curl is a command-line utility for transferring data from or to a remote server. It is installed by default on macOS and most Linux distributions.

curl Options

The syntax of the curl command is:

txt
curl [options] [URL...]

The options used in this guide are:

  • -X, --request - The HTTP method to use.
  • -i, --include - Include the response headers in the output.
  • -sS, --silent --show-error - Hide progress output but still show errors.
  • --fail-with-body - Return a non-zero exit code on HTTP errors while still printing the response body.
  • -d, --data - The data to send with the request.
  • -G, --get - Send the -d or --data-urlencode data as query parameters in a GET request.
  • --data-urlencode - URL-encode the data before sending it.
  • -H, --header - An additional header to send.
  • -u, --user - The username and password for Basic authentication.
  • -w, --write-out - Print details about the transfer, such as the status code, after it completes.

The examples use JSONPlaceholder , a free fake API for testing. It accepts every write request and returns a realistic response, but nothing is actually saved on the server, so you can run each command as many times as you like.

HTTP GET

The GET method requests a resource from the server. GET is the default method when making HTTP requests with curl, so you do not need -X.

The following example returns a JSON list of all posts:

Terminal
curl https://jsonplaceholder.typicode.com/posts

To filter results, append query parameters to the URL. Wrap the URL in quotes, because shells treat ? and & as special characters. In zsh, the default shell on macOS, an unquoted ? fails with no matches found:

Terminal
curl "https://jsonplaceholder.typicode.com/posts?userId=1"
output
[
  {
    "userId": 1,
    "id": 1,
    "title": "sunt aut facere repellat provident occaecati excepturi optio reprehenderit",
    "body": "quia et suscipit\nsuscipit recusandae consequuntur expedita..."
  },
  ...
]

The server returns only the posts that belong to user 1.

When a parameter value contains spaces or other special characters, let curl build the query string for you. The -G option appends the --data-urlencode data to the URL instead of sending it in the body:

Terminal
curl -G --data-urlencode "title=qui est esse" \
    https://jsonplaceholder.typicode.com/posts

curl sends the request as GET /posts?title=qui+est+esse, and the API returns the single post with that title.

Many APIs can respond in more than one format. To ask for JSON explicitly, set the Accept header:

Terminal
curl -H "Accept: application/json" https://jsonplaceholder.typicode.com/posts/1

HTTP POST

The POST method sends data to the server to create a new resource.

The following command sends a POST request with form-encoded data using the -d option:

Terminal
curl -X POST -d "userId=5&title=Hello World&body=Post body." \
    https://jsonplaceholder.typicode.com/posts
output
{
  "userId": "5",
  "title": "Hello World",
  "body": "Post body.",
  "id": 101
}

The API created a new post and assigned it the ID 101. Notice that userId came back as the string "5". Form data has no types, so every value arrives as text.

By default, when -d is used without a Content-Type header, curl sends Content-Type: application/x-www-form-urlencoded. Strictly speaking, -X POST is redundant here because -d already switches the method to POST, but many people keep it for readability.

To send JSON data, set the Content-Type header to application/json:

Terminal
curl -X POST -H "Content-Type: application/json" \
    -d '{"userId": 5, "title": "Hello World", "body": "Post body."}' \
    https://jsonplaceholder.typicode.com/posts
output
{
  "userId": 5,
  "title": "Hello World",
  "body": "Post body.",
  "id": 101
}

This time userId is a number, because the server parsed the body as JSON. Most REST APIs expect JSON, so this is the form you will use most often.

For more examples, including the --json shorthand, see How to Use curl to Make POST Requests .

HTTP PUT

The PUT method replaces a resource on the server with the data provided in the request body. Send the complete resource, because many APIs reset any field you leave out to an empty or default value.

The following command replaces the post with ID 5:

Terminal
curl -X PUT -H "Content-Type: application/json" \
    -d '{"userId": 5, "title": "Hello World", "body": "Post body."}' \
    https://jsonplaceholder.typicode.com/posts/5
output
{
  "userId": 5,
  "title": "Hello World",
  "body": "Post body.",
  "id": 5
}

The response contains exactly the fields we sent, plus the id from the URL.

HTTP PATCH

The PATCH method applies partial updates to a resource. Unlike PUT, it only updates the fields included in the request body and leaves all other fields unchanged.

To change only the title of post 5, send just that field:

Terminal
curl -X PATCH -H "Content-Type: application/json" \
    -d '{"title": "Hello Universe"}' \
    https://jsonplaceholder.typicode.com/posts/5
output
{
  "userId": 1,
  "id": 5,
  "title": "Hello Universe",
  "body": "repudiandae veniam quaerat sunt sed..."
}

The title changed, while userId and body kept their original values.

HTTP DELETE

The DELETE method removes the specified resource from the server. There is no confirmation prompt, so double-check the URL before you run it against a real API:

Terminal
curl -X DELETE https://jsonplaceholder.typicode.com/posts/5

JSONPlaceholder returns an empty JSON object ({}) and a 200 status code. Other APIs often reply with 204 No Content, which indicates success without a response body. An empty body alone does not establish success, so check the status code to confirm.

Authentication

Most APIs require authentication. curl supports several common authentication methods.

Bearer Token

Bearer token authentication is used by OAuth 2.0 APIs. Pass the token in the Authorization header:

Terminal
curl -H "Authorization: Bearer {ACCESS_TOKEN}" https://api.example.com/data

Basic Authentication

For APIs that use HTTP Basic authentication, pass the username and password with the -u option:

Terminal
curl -u username:password https://api.example.com/data

curl encodes the credentials and sends them in the Authorization header automatically. If you pass only the username (-u username), curl prompts for the password, which keeps it out of your shell history.

API Key

Some APIs accept an API key as a custom header:

Terminal
curl -H "X-API-Key: {API_KEY}" https://api.example.com/data

Other APIs expect the key as a query parameter:

Terminal
curl "https://api.example.com/data?api_key={API_KEY}"
Warning
Tokens, passwords, and API keys typed on the command line are saved in your shell history. Store them in an environment variable, for example curl -H "Authorization: Bearer $API_TOKEN" ..., and load secrets from a .env file that is listed in .gitignore. Never commit credentials to version control.

Inspecting Responses

Use the -i flag to include the HTTP response status line and headers in the output:

Terminal
curl -i https://jsonplaceholder.typicode.com/posts/1
output
HTTP/2 200
content-type: application/json; charset=utf-8
...

{
  "userId": 1,
  "id": 1,
  "title": "sunt aut facere...",
  "body": "quia et suscipit..."
}

The first line shows the protocol and the status code, followed by the response headers, a blank line, and the body.

To pretty-print a JSON response, pipe the output to python3:

Terminal
curl -s https://jsonplaceholder.typicode.com/posts/1 | python3 -m json.tool

If you have jq installed, it provides color-highlighted formatted output:

Terminal
curl -s https://jsonplaceholder.typicode.com/posts/1 | jq

Checking the Status Code

In scripts, you often care only about whether the request succeeded. The -w option prints selected details after the transfer, and %{http_code} holds the response status:

Terminal
curl -s -o /dev/null -w '%{http_code}\n' https://jsonplaceholder.typicode.com/posts/1
output
200

Here -s hides the progress meter and -o /dev/null discards the body, so the status code is the only thing printed. Request a post that does not exist, such as /posts/9999, and the same command prints 404.

By default, curl exits with status 0 even when the server returns an error such as 404 or 500. For automation scripts and CI checks, add --fail-with-body to make those failures explicit:

Terminal
curl -sS --fail-with-body https://jsonplaceholder.typicode.com/posts/9999
output
curl: (22) The requested URL returned error: 404
{}

curl now exits with code 22 and still prints the error body, which often explains what went wrong. The --fail-with-body option requires curl 7.76.0 or newer. On older systems such as RHEL 8, use --fail instead, which fails the same way but discards the body.

When you need to see everything, including the request headers curl sends, use -v for verbose output.

Quick Reference

For a printable quick reference, see the curl cheatsheet .

CommandDescription
curl URLGET request
curl -G --data-urlencode "key=a b" URLGET with encoded query parameters
curl -X POST -d "data" URLPOST with form data
curl -X POST -H "Content-Type: application/json" -d '{}' URLPOST with JSON body
curl -X PUT -H "Content-Type: application/json" -d '{}' URLPUT (full replace)
curl -X PATCH -H "Content-Type: application/json" -d '{}' URLPATCH (partial update)
curl -X DELETE URLDELETE request
curl -H "Authorization: Bearer TOKEN" URLBearer token auth
curl -u user:pass URLBasic auth
curl -i URLInclude response headers
curl -s -o /dev/null -w '%{http_code}\n' URLPrint only the status code
curl -sS --fail-with-body URLFail on HTTP errors and print error body
curl -s URL | python3 -m json.toolPretty-print JSON response

Troubleshooting

SSL certificate problem: unable to get local issuer certificate
The server is using a self-signed or untrusted certificate. In a controlled test environment where you trust the host, add -k to skip certificate verification. Do not use -k against untrusted or production servers.

401 Unauthorized or 403 Forbidden
Your authentication credentials are missing, incorrect, or expired. Verify the token or API key and check the API documentation for the correct header name and format.

Connection refused
The server is not reachable on the specified host and port. Confirm the endpoint URL is correct and that the server is running.

Response is not valid JSON
The server may be returning an error page in HTML. Use -i to inspect the full response including the status code and headers to diagnose the issue.

zsh: no matches found
The URL contains ? or * and is not quoted, so zsh tries to expand it as a filename pattern. Wrap the URL in double quotes.

curl: (6) Could not resolve host
The hostname cannot be resolved. Check the URL for typos and verify the host is reachable.

FAQ

What is the difference between PUT and PATCH?
PUT replaces the entire resource with the data in the request body. Send the complete resource, because the API determines how omitted fields are handled, and many APIs reset them to empty or default values. PATCH applies partial updates. Use PATCH when you want to change specific fields without replacing the entire resource.

Do I always need to specify -X GET?
No. GET is the default method in curl. Options such as -d select POST automatically. Use -X to explicitly select methods such as PUT, PATCH, or DELETE.

How do I send multiple headers?
Repeat the -H flag for each header:

Terminal
curl -H "Authorization: Bearer TOKEN" -H "Accept: application/json" URL

How do I save the API response to a file?
Use the -o flag to write the output to a file:

Terminal
curl -o response.json https://jsonplaceholder.typicode.com/posts/1

Conclusion

curl handles every step of working with a REST API, from a quick GET to an authenticated PATCH. When a request does not behave as expected, start with -i or -w '%{http_code}' to see what the server actually returned.

For more curl examples and options, see the curl Command in Linux guide.

Tags

Linuxize Weekly Newsletter

A quick weekly roundup of new tutorials, news, and tips.

About the authors

Dejan Panovski

Dejan Panovski

Dejan Panovski is the founder of Linuxize, an RHCSA-certified Linux system administrator and DevOps engineer based in Skopje, Macedonia. Author of 1000+ Linux tutorials with 20+ years of experience turning complex Linux tasks into clear, reliable guides.

View author page