curl is a masterpiece of engineering and a crime against muscle memory. xh keeps the power and fixes the syntax.
xh is a Rust rewrite of HTTPie’s UX with curl’s portability. You get the same verb-then-URL structure (xh GET host/path), the same key=value body syntax for JSON, the same readable output with headers and pretty-printed JSON bodies, but as a single static binary that installs with brew/cargo and starts in milliseconds. It’s not a curl replacement so much as a curl front-end for humans: xh POST api.example.com/users name=Ada role=admin is the whole request, no -H ’Content-Type: application/json’ liturgy, no quoting hell. There’s a session feature (xh --session=name) that persists cookies and base-URL shorthand, and a --curl flag that prints the equivalent curl command — which makes it the best documentation tool for curl that curl never shipped.
install
# macOS
brew install xh
# Debian/Ubuntu
sudo apt install xh
# Fedora sudo dnf install xh # -- Arch sudo pacman -S xh # or: cargo install xh
examples
# GET with pretty-printed JSON by default
xh GET https://api.github.com/repos/ducaale/xh
# POST a JSON body — bare key=value pairs become the payload, Content-Type set for you
xh POST https://httpbin.org/post name=Ada role=admin
# Custom headers use the Key:Value syntax; query params use ==
xh GET https://api.github.com/search/repositories q==jq per_page==1 Authorization:Bearer $TOKEN
# Print the equivalent curl command — for pasting into docs or sharing
xh --curl POST https://httpbin.org/post name=test
# Download with progress, resumable, to a file
xh -d https://example.com/bigfile.iso
# Check an endpoint's status only — silent, exit code reflects the HTTP status
xh --check-status -o /dev/null GET https://example.com/health
pro tip
--curl is the killer feature nobody talks about: build the request interactively with xh’s forgiving syntax, then xh --curl ... hands you the curl invocation for the README, CI script, or a colleague who hasn’t installed xh yet. Sessions (xh --session=api) persist cookies and a default host, so a multi-step login flow stops being a copy-paste exercise.
honest limits
xh speaks HTTP only — no FTP or the rest of curl’s protocol zoo. Its JSON body syntax has rules to learn: key=value makes strings, key:=3 sends raw JSON (numbers, booleans), and getting that wrong silently changes your payload’s types. Don’t ship scripts that depend on xh to machines that may not have it — curl is the universal donor there. Auth is client-side only: --cert/--key cover mTLS, but there’s no built-in OAuth flow. If you need a full agent, write Python. If the rest of your team speaks curl, use curl.