Skip to content
shellhacks.dev
APIs

curl vs httpie vs xh for API Work and Which to Pick

Compare curl, HTTPie and xh for API work: JSON request syntax, output formatting, sessions, speed, availability and which fits scripts.

6 min read
Summarize in AI
Illustration for "curl vs httpie vs xh for API Work and Which to Pick": a terminal session, a loop arrow, an exit status badge.

For many people, API work starts with curl, and ends with a long command with escaped quotes. Two tools make the interactive side friendlier: HTTPie, a Python tool with a concise syntax, and xh, a Rust reimplementation that is a single fast binary. Which to use depends on where the command runs. The output side connects to exploring JSON, and the scripting side to testing an API with curl. The examples assume curl 8.11, HTTPie 3.2 and xh 0.23.

The same requests in three tools

Shell
# GET with a query parameter and header
curl -sS -H 'Accept: application/json' 'https://api.example.test/items?status=open'
http GET https://api.example.test/items status==open Accept:application/json
xh GET https://api.example.test/items status==open Accept:application/json

# POST a JSON body
curl -sS -X POST https://api.example.test/items -H 'Content-Type: application/json' -d '{"name":"alpha","qty":2}'
http POST https://api.example.test/items name=alpha qty:=2
xh POST https://api.example.test/items name=alpha qty:=2

# bearer token
curl -sS -H "Authorization: Bearer $TOKEN" https://api.example.test/me
http -A bearer -a "$TOKEN" https://api.example.test/me
xh -A bearer -a "$TOKEN" https://api.example.test/me

In HTTPie and xh, key=value is a JSON string field, key:=value is raw JSON (a number, boolean or nested structure), key==value is a query parameter, and Name:value is a header. JSON is the default request format; use --form for form data. The shorter form is the main advantage, and the quoting problems of curl's -d '{...}' go away. Be careful with the token: it appears in the command line and shell history, see keeping API keys out of scripts.

Output

Text
$ http https://api.example.test/items/1
HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 41

{
    "id": 1,
    "name": "alpha",
    "qty": 2
}

HTTPie and xh print the status line, headers and a coloured, indented body when output goes to a terminal, and switch to plain output when piped, so http ... | jq works. curl prints just the body; add -i for headers and pipe through jq for formatting. Options: --headers or -h for headers only, --body or -b for the body only, --print=hb to choose parts. Sessions (--session=name) store cookies and auth between calls. Colour and extras make exploration pleasant and are useless noise in scripts.

Differences that matter

Text
                    curl            HTTPie            xh
Installed by default  nearly everywhere  no (pip, brew, apt)  no (cargo, brew, releases)
Implementation      C               Python            Rust
Start-up            few ms          ~100-200 ms       few ms
JSON syntax         manual          built in          built in
Protocols           many (FTP, SMTP, etc.)  HTTP(S)   HTTP(S)
Download/resume    yes (-C -)      limited           limited
HTTP/3, proxies, etc.  broad       moderate          moderate

The start-up and availability rows are the practical ones. HTTPie's Python start-up makes it noticeable in loops; xh starts as fast as curl. curl supports many more protocols and flags (retries with --retry, resumable downloads, precise TLS control, HTTP/3). The timing figures are typical orders of magnitude.

In scripts: use curl

Shell
curl -fsS --max-time 20 --retry 3 --retry-delay 2 \
  -H "Authorization: Bearer $API_TOKEN" \
  -H 'Content-Type: application/json' \
  -d "$(jq -n --arg n "$name" '{name: $n}')" \
  https://api.example.test/items

A CI image, a minimal container or someone else's server has curl, and not necessarily the others, so a script that depends on HTTPie adds an install step. Use jq -n --arg to build the JSON body so values are quoted correctly. Always set -f to fail on HTTP errors, -sS for quiet output that still shows errors, and a timeout, as covered in the timeout command. A script should check the status code, see HTTP status codes.

For exploration: whichever you like

Interactive use is where HTTPie and xh shine, along with a REST client in your editor. The habit that bridges both: develop the request in HTTPie or xh, then translate to curl for the script. xh and HTTPie can print the equivalent curl command with --offline --curl (HTTPie via a plugin; xh has --curl). See HTTPie and xh for API work for more patterns.

What you are really choosing

The three tools do the same thing, send an HTTP request and show the response, and they differ in ergonomics. The interface of a tool shapes what you do with it. A command that takes a dozen characters to send JSON invites quick experiments; one that needs quoted strings and escapes discourages them and tempts you to skip a check. Over a day of API work that difference adds up. Still, experiments are not production, and the choice for a script should follow different rules, availability and stability, which is why curl remains the default there regardless of preference at the keyboard.

It helps to see the tools as a pair of environments. For discovery, you want fast feedback, readable output and an easy way to modify one field and resend. For automation, you want a tool that is already installed, has the options for timeouts, retries and certificates, and behaves the same on every machine and every version you will meet. Use the tool that fits each environment, and translate between them deliberately.

Handling authentication and sessions

HTTPie and xh have named sessions that keep cookies and headers between calls, so a login followed by a few requests needs no copying of tokens. This is convenient and also stores credentials in a file in your home directory, which you should treat as sensitive. curl has cookie jars with -c and -b for the same purpose, and the same warning applies. For bearer tokens, read them from an environment variable or a password manager rather than typing them into the command line, where they land in shell history. All three accept -H "Authorization: Bearer $TOKEN", and a space before the command keeps it out of history in shells configured to ignore such lines.

Be careful with tools that print requests for debugging. A verbose flag shows headers, including the authorization header, and a pasted log in a bug report leaks the token. Redact before sharing, and prefer short-lived tokens for any exploration.

Large bodies, files and streaming

For uploads and downloads, curl is the more complete tool. It resumes interrupted downloads, shows progress meters, supports parallel transfers and handles multipart uploads with explicit control. HTTPie and xh can download with a progress bar and upload a file as the body with a redirect, such as http POST url < body.json, and are fine for modest sizes. For streaming responses, such as server-sent events, check the behaviour of each with a small test; buffering differences can make a stream look stuck in one tool and fine in another.

Moving a request between tools

A frequent task is to take a working call and turn it into a script. Reproduce it exactly in curl, then replace the literal values with variables, add the safety options and wrap the response handling. The conversion is mechanical, and the check that it worked is that both commands return identical bodies and status codes. Keep the HTTPie or xh version in a comment or a documentation file as the readable form, and the curl version as the executable one. Future readers get the best of each: a clear description of intent and a command that runs anywhere.

Where this does not apply

For repeatable API tests with assertions, use a test tool (Newman, Bats with curl, a language test framework) instead of manual requests, see Postman and Newman on the command line. For GraphQL or gRPC, specialised clients exist. Check each tool's version: options and defaults changed between releases. See the curl manual, the HTTPie documentation and the xh repository.

Share

Keep reading

Get new posts by email

One email when a new post is published. No spam.

Unsubscribe in one click. No spam, ever.