xh: an HTTPie-compatible HTTP client written in Rust
Project brief: Friendly and fast tool for sending HTTP requests. Usage Run xh help or man xh for more detailed information.
At a glance
- What is it?
- xh reimplements HTTPie's request-item syntax in Rust for faster startup and a single static binary. It is a drop-in replacement for most everyday API calls, with a few deliberate differences.
- Who is it for?
- Adopt xh if you already type HTTPie commands and want a single Rust binary that installs through cargo, Homebrew, apt, Scoop or the project's install script. Avoid it if your scripts depend on HTTPie's exact exit-code behaviour, since xh documents that --check-status is not on by default in compatibility mode.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 25 days ago.
- What is it written in?
- Mainly Rust, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on September 29, 2026, and from our analysis. They are not legal advice.
Editorial analysis
What xh replaces, and who it is aimed at
xh is a command-line HTTP client. The README describes it as a reimplementation of "as much as possible of HTTPie's excellent design, with a focus on improved performance." That sentence is the whole pitch: if you already know HTTPie, you already know xh's syntax. The request-item grammar is borrowed wholesale, and the README links to HTTPie's own documentation for it rather than restating the rules.
The audience is narrower than "anyone who calls APIs from a terminal." It is people who liked HTTPie's ergonomics but wanted a compiled binary, faster startup, or an install path that does not require a Python runtime. The README's install table lists Cargo, Homebrew, Nixpkgs, MacPorts, Scoop, Chocolatey, Winget, Pacman, apk, apt, dnf, pkg, pkgin and several version managers. That breadth is the real argument: xh is meant to be available wherever you already are.
What it is not is a curl replacement for scripting. curl's flag surface is enormous and its behaviour is baked into countless shell scripts. xh targets interactive and semi-interactive use, where typing `xh httpbin.org/post name=ahmed age:=24` is faster than assembling a JSON body by hand.
How the request-item syntax maps to an HTTP request
Every argument after the URL is a request item, and the operator inside it decides where the value goes. The README lists the mapping directly: `=` and `:=` set body fields (string and non-string JSON respectively), `==` adds a query parameter, `@` attaches a file to a multipart request, `:` adds or removes a header, and `;` includes a header with an empty value.
That last pair is worth pausing on. `connection:keep-alive` adds a header, while `connection:` with nothing after the colon removes one. The same character does opposite things depending on whether a value follows. It is compact, and it is also the kind of rule you will get wrong once.
Nested JSON is built with a bracketed path as the key, so `app[container][0][id]=090-5` produces a nested object rather than a flat key. The README points to HTTPie's nested-JSON page for the full grammar instead of documenting it inline, which is a reasonable choice for a compatibility-focused tool but means xh's own manual is not self-contained on this point.
An `@` prefix reads a value from a file, so `x-api-key:@api-key.txt` pulls the header value off disk. That is a small feature with an outsized effect on shell history hygiene: the secret never appears in the command line.
Installing xh and sending a first request
The README gives a one-line cURL installer for Linux and macOS. It pipes a script from the master branch of the repository into a shell, which is convenient and also means you are trusting whatever is on master at the moment you run it. If that bothers you, the package-manager route is the alternative.
curl -sfL https://raw.githubusercontent.com/ducaale/xh/master/install.sh | shOn Windows the equivalent is a PowerShell invocation of install.ps1 from the same repository. For a language-managed install, Cargo works anywhere with a recent Rust toolchain; the Cargo.toml in the repository sets `rust-version = "1.85.0"`, and the README footnote repeats that requirement.
cargo install xh --lockedOnce installed, the README's first example is a plain GET. Run it and you should see the JSON body httpbin returns, pretty-printed.
xh httpbin.org/jsonA POST with a typed body shows the `=` versus `:=` distinction in practice. The README's example produces `{"name": "ahmed", "age": 24}`, where `age` is a number rather than the string `"24"`.
xh httpbin.org/post name=ahmed age:=24Headers and query strings go in the same command. This one sends a GET with `id=5&sort=true` in the query string and an `x-api-key` header, with no body at all.
xh get httpbin.org/json id==5 sort==true x-api-key:12345For local development, the shorthand saves typing. `:3000/users` resolves to `localhost:3000/users`, and `:/users` resolves to `localhost/users` on port 80. A bare `example.com` becomes `http://example.com`, not HTTPS.
The xhs trick and what it costs you
Scheme selection in xh depends on the name of the binary you invoke. If the binary is called `xhs`, `https`, or `xhttps`, xh defaults to HTTPS. Called as plain `xh`, it defaults to HTTP. The README states that package-manager installs should provide both `xh` and `xhs`; otherwise you create the second name yourself with a symlink.
cd /path/to/xh && ln -s ./xh ./xhs
xh httpbin.org/get # resolves to http://httpbin.org/get
xhs httpbin.org/get # resolves to https://httpbin.org/getThis is clever and slightly dangerous. The same command text produces different requests depending on a filename. In a shell alias, a Makefile, or a CI step where the binary has been copied and renamed, the scheme can flip without anyone editing the command. The README is explicit about the rule, so the information is there, but it is a design decision that trades predictability for convenience.
There is a related behaviour for HTTPie compatibility. If xh is invoked as `http` or `https`, or if the `XH_HTTPIE_COMPAT_MODE` environment variable is set, it runs in compatibility mode. The README says the only current difference is that `--check-status` is not enabled by default. That single sentence is the most operationally important line in the document, because exit codes are what scripts branch on.
Where xh is the wrong tool
The compatibility mode caveat is the clearest limitation. If you rename the binary to `http` expecting HTTPie's exit-code semantics, you get a client that does not fail the same way on a 4xx or 5xx response unless you pass `--check-status` yourself. Any wrapper script that assumes a non-zero exit on an error status will silently succeed. The README documents the difference; it does not offer a flag to restore HTTPie's default.
TLS is the second constraint, and it is easy to miss because it lives in footnotes. The apk build for Alpine and the apt build for Debian 13 and Ubuntu 25.04 are marked as built with native-tls only. If your environment expects a specific TLS stack, the package you install determines what you get, and the README does not describe how to switch it after installation.
Third, xh is not a general-purpose HTTP debugging proxy. It sends requests and prints responses. There is no documented interception mode, no request rewriting layer, no traffic replay. If your problem is "why is this header being stripped in transit," xh shows you what you sent and what came back, and stops there.
Finally, the version pinning matters. Cargo.toml sets `edition = "2024"` and `rust-version = "1.85.0"`, so `cargo install xh` on an older toolchain fails rather than building something subtly different. The README's Cargo footnote says the same thing.
How xh differs from curl and from HTTPie itself
curl is the obvious comparison, and the difference is not speed. It is the input format. curl asks you to spell out `-X POST -H 'Content-Type: application/json' -d '{"name":"ahmed"}'`. xh asks for `name=ahmed` and infers the content type from the fact that you used `=`. That inference is the product. It also means xh's behaviour depends on parsing your arguments correctly, where curl's depends on you writing the request correctly.
Against HTTPie, the relationship is closer to a port than a competitor. xh reuses HTTPie's request-item syntax, its shorthand URL rules, and its nested-JSON grammar, and links out to HTTPie's documentation for the details. The stated difference is performance, which follows from being a compiled Rust binary rather than a Python program: no interpreter startup on every invocation. For a single request the gap is small in absolute terms; for a loop of a few hundred, or for a shell prompt that calls the tool often, it compounds.
The trade is ecosystem maturity. HTTPie has a plugin system and a longer history of edge cases being reported and fixed. xh's dependency list in Cargo.toml is broad (hyper, reqwest_cookie_store, cookie_store, digest_auth, httpsig-hyper, brotli, flate2, ruzstd, chardetng, encoding_rs), which covers a lot of protocol surface, but the README does not claim parity with HTTPie's plugin ecosystem.
Maintenance, licence and upgrade cost
The repository is not archived, and the most recent push was on 2026-07-26, which coincides with the v0.26.2 release. The two releases before that were v0.26.1 on 2026-06-19 and v0.25.3 on 2025-12-16. That is a cadence of a few releases a year, with a gap of roughly six months between v0.25.3 and v0.26.1. Nothing in the repository layout suggests abandonment, but readers should treat the release notes and CHANGELOG.md as the source of truth for what changed rather than assuming continuous churn.
The licence is MIT, declared in both Cargo.toml and the LICENSE file at the repository root. MIT is permissive: it allows use, modification and redistribution provided the copyright notice and permission notice are retained. That is a statement about the licence text, not legal advice, and anyone embedding xh in a distributed product should read the file themselves.
Upgrade cost is low by design. xh has no server component, no database, no configuration directory mentioned in the README, and no migration path to manage. Upgrading means replacing a binary. The one thing to watch is the Rust version floor: Cargo.toml currently requires 1.85.0, and that number has moved before. A CI image pinned to an older toolchain will fail the build rather than produce an older xh, which is the safer failure mode but still a failure.
Editorial conclusion
Adopt xh if you already type HTTPie commands and want a single Rust binary that installs through cargo, Homebrew, apt, Scoop or the project's install script. Avoid it if your scripts depend on HTTPie's exact exit-code behaviour, since xh documents that --check-status is not on by default in compatibility mode. Before rolling it out across a team, verify two things on your own machines: that the binary name you invoke is the one you want (xhs or https resolves to HTTPS, plain xh does not), and that your package manager's build matches the TLS backend you need, because the apk and apt builds are documented as native-tls only.
Frequently asked questions
What is xh and how is it different from HTTPie?
xh is a command-line HTTP client written in Rust. The README says it reimplements as much as possible of HTTPie's design with a focus on improved performance, and it reuses HTTPie's request-item syntax.
How do I install xh on Ubuntu?
The README lists `sudo apt install xh` for Debian and Ubuntu, available since Debian 13 and Ubuntu 25.04, and notes that build uses native-tls only. Cargo, Homebrew, Nixpkgs and a cURL install script are also listed.
How do I make xh use HTTPS by default?
Invoke it as `xhs`, `https` or `xhttps`. The README states that package-manager installs should provide both `xh` and `xhs`; otherwise you create a symlink to the xh binary under the name xhs.
Does xh work as a drop-in replacement for HTTPie in scripts?
Partly. If xh is invoked as `http` or `https`, or if `XH_HTTPIE_COMPAT_MODE` is set, it runs in compatibility mode. The README says the only current difference is that `--check-status` is not enabled by default, so exit-code behaviour differs unless you pass it.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/ducaale-xh)