TL;DR: http POST https://jsonplaceholder.typicode.com/users name=alice role=admin sends the JSON, the headers, and the readable output that curl makes you assemble by hand. From a script, the same stdin rule holds — the pipe is still the body. (The flag you'd use in a non-TTY job, --ignore-stdin, appears in the error message when you accidentally mix data via the pipe and request items via the body.)
The curl program is on every machine, used in every doc example and in most scripts that talk to a web service. That's not going to change, and this issue is not an argument against it. It is about the situations where you might want more interactivity and an easier syntax — poke an API by hand, read the response, adjust a header, send it again. The alternative in those cases is HTTPie (http).
1. POST JSON without quoting the values
For example, here is the curl command for the "create a user" request on JSONPlaceholder (the free fake REST API that echoes your POST back):
curl -X POST https://jsonplaceholder.typicode.com/users \
-d '{"name":"alice","role":"admin","active":true}'And here is the equivalent HTTPie version:
http POST https://jsonplaceholder.typicode.com/users \
name=alice \
role=admin \
active:=trueWith http, you get a simpler syntax, where the request items (the key=value arguments after the URL) build the body of the request. You use = for passing a JSON string, with no need to quote the value, and := for a raw JSON value (booleans, numbers, nested objects).
The http form is the right pick when you build small JSON bodies field by field, and the curl quoting (the escaped quotes around every string) has ever cost you a debugging session.
2. Set HTTP headers without -H
With http, you don't need to define the following headers, they are already set on any request:
Accept: */*
Accept-Encoding: gzip, deflate
User-Agent: HTTPie/3.2.4
Host: jsonplaceholder.typicode.comBut they can be overridden; you override a default header with the same syntax you would use to set a new one. Here is an example with the Accept header for a request to api.github.com:
http GET https://api.github.com/users/octocat \
Accept:application/vnd.github+jsonAs you can see, the syntax is Header:Value, with : (not =, which would make the value a JSON string). Here is another example that adds a custom request header the server ignores but the request carries:
http GET https://api.github.com/zen X-Trace-Id:demo-001By the way, the syntax with the curl form has more elements: the -H flag and quotes. Let's come back to the example doing a POST on the fake API, the curl command that sets the Content-Type header is:
curl -X POST https://jsonplaceholder.typicode.com/users \
-H 'Content-Type: application/json' \
-d '{"name":"alice","role":"admin","active":true}'The http equivalent to that command is:
http POST https://jsonplaceholder.typicode.com/users \
name=alice \
role=admin \
active:=true \
Content-Type:application/jsonNext, see how HTTPie allows you to verify the request headers before the request is sent to the server.
3. Simulate the request or the dry-run mode
There are two things http does around the request itself. The first is that the response is pretty-printed and colorized (when the terminal supports it). So a JSON output is readable as it arrives, and there is no need to pipe it to jq.
The other thing is the dry-run capability; using the --offline flag will print the exact request without sending it. For example, using this command...
http --offline GET https://api.github.com/users/octocat \
'Authorization:token my-token-here'... we get this output:
GET /users/octocat HTTP/1.1
Accept: */*
Accept-Encoding: gzip, deflate, zstd
Authorization: token my-token-here
Connection: keep-alive
Host: api.github.com
User-Agent: HTTPie/3.2.4Note that, for the Authorization header set, since the value contains a space, the whole item needs single quotes for http to treat it as valid.
You may already know that curl's -v flag shows the request too, but after it has been sent. http's --offline needs no server at all, which makes it the safe way to check what a command sends before pointing it at production with real credentials.
This is the right trick when you care about what leaves the machine — auth headers, tokens, request shape — as much as what comes back.
4. Pipe data into a request
Say you are at the terminal with a stream of data, and you want it in the body of a request without opening an editor. For example:
jq -n '{"page": 1, "per_page": 100}' | http POST https://jsonplaceholder.typicode.com/postsThe producer of the stream is a pipe and the consumer is the request. This works from a script too, but there is a case when the technique does not work: when piping and passing request items at the same time. In those situations, you will get this error message:
error: Request body (from stdin, --raw or a file) and
request data (key=value) cannot be mixed.
Pass --ignore-stdin to let key/value take priority.This is the right trick when the data is already in a pipe (at the prompt or in a script), and the request body is just where it's going.
Try it
brew install httpie # or: apt install httpie
http GET https://jsonplaceholder.typicode.com/users/1
http POST https://jsonplaceholder.typicode.com/posts \
title=hello \
userId:=1JSONPlaceholder answers instantly and echoes your POST back. From there, take any real API you already poke by hand and run the same request once with curl, once with http. Count what you had to type.
HTTPie is not in the book, but it is a natural addition to The Modern CLI Stack for those who test APIs and web services from the terminal. If you want the full toolkit, The Modern CLI Stack is a free ~50-page PDF + EPUB covering mise, starship, zoxide, fzf, broot, ripgrep, fd, bat, eza, delta, tldr, atuin, lazygit.
