cURLParse cURL Converter

cURL Alternatives Compared: HTTPie, xh, wget2, and Native fetch

cURLParse Guides · Updated 2026-10-18 · All guides

curl will never die — it's preinstalled everywhere and speaks every protocol — but "default" and "best" are different words. Here's an honest map of when the alternatives are genuinely better, tested against the same requests.

HTTPie (the friendly one)

http POST api.example.com/v1/jobs model:=small input="hello"
http api.github.com/users/octocat Authorization:'Bearer ghp_...'
  • Wins: the := / = / :=@ typed-data syntax makes JSON bodies pleasant; headers are just Name:value; output is pretty and colorized by default; --print=h reads like English.
  • Losers: Python runtime (slow startup, needs a pip/pipx install); JSON-by-default is wrong for form endpoints and bites you once; not installed on any production server you didn't provision.
  • Honest take: the best interactive tool for API poking. The moment you need the command in a Dockerfile or a colleague's bare server, you're back to curl.

xh (HTTPie's ergonomics, Rust's footprint)

xh post api.example.com/v1/jobs model:=small
xh :8080/health                  # localhost shorthand
  • Wins: same human syntax as HTTPie but a single static binary — no Python, cold start in milliseconds, trivially vendorable. Good TLS defaults and --curl flag that emits the equivalent curl command (the inverse of this site).
  • Losers: young project; no multipart file upload until recent versions matured; fewer auth plugins than HTTPie's ecosystem.
  • Honest take: if you're allowed to install one binary on your team's machines, this is the one. For scripts that must run anywhere, still curl.

wget2 (when downloading is the whole job)

  • Wins: recursion, timestamped resumption (-c), rate limiting (--limit-rate), and content-disposition filename handling that curl needs three flags to approximate. It was born for mirrors.
  • Losers: API work. Custom headers, methods, and auth are clunky compared to curl; no per-request flexibility worth the name.
  • Honest take: wget2 -c URL for flaky-network downloads, curl for anything with a status code you care about. Note classic wget (1.x) and wget2 differ subtly in quoting; see quoting hell for the Windows twist.

Node 18+ native fetch (in code, not the terminal)

  • Wins: zero dependencies, spec-standard Response API, streaming bodies via ReadableStream. For our curl-to-node route, modern fetch output is the default target for a reason.
  • Losers: no timeouts built in — you're assembling AbortSignal.timeout() by hand; redirects preserve curl-like method changes only via redirect: 'manual' gymnastics; error-on-non-2xx must be your own if (!res.ok).
  • Honest take: fine for edge functions and scripts; the missing-defaults tax is exactly what the debugging guide documents from the other side.

Python requests / httpx

  • Wins: sessions with cookie jars (the -b/-c pattern, done right), connection pooling, retries via adapters; httpx adds HTTP/2 and proper timeouts-by-default.
  • Losers: data= vs json= — the form/JSON confusion that generates half the 400s on the internet. Set a timeout or inherit a 0-second patience budget from nobody.
  • Honest take: the right answer inside applications; the wrong answer for the "quick check" that curl was built for.

Comparison table

ToolInstall barrierJSON ergonomicsScripting everywhereTimeouts by default
curlnone (preinstalled)manual flagsyesno
HTTPiePythonbest-in-classnono
xhone binarybest-in-classusuallyyes
wget2packagen/acommonyes
Node fetchNode 18+native objectsinside appsno
requests/httpxpipjson= kwarginside appsrequests: no, httpx: yes

The same request, six ways

One real shape — authenticated JSON POST — in each tool. Read the defaults column in your head while you compare:

# curl: nothing is assumed except POST-from -d
 curl -sS --fail-with-body https://api.example.com/v1/jobs \
  -H 'Authorization: Bearer ***' \
  -H 'Content-Type: application/json' \
  -d '{"model":"small"}'

# HTTPie: JSON by default, Accept: */* unless you say otherwise
http POST https://api.example.com/v1/jobs \
  Authorization:'Bearer ***' model:=small

# xh: same syntax, static binary, and it adds Accept-Encoding for you
xh POST https://api.example.com/v1/jobs \
  Authorization:'Bearer ***' model:=small

What differs on the wire, verified against an echo origin with each tool:

request line detailcurl bare -dHTTPiexh
methodPOST (implied by -d)POST (explicit verb)POST (explicit verb)
Content-Typeapplication/x-www-form-urlencodedapplication/jsonapplication/json
Accept-Encodingabsent unless --compressedgzip, deflate, brgzip, deflate, br
User-Agentcurl/8.xHTTPie/3.xxh/0.2x

Two of those rows are the whole migration story. JSON-by-default means an HTTPie one-liner that works against your JSON API silently sends application/json to a form endpoint that used to accept it — and form endpoints that ignore content-type and parse the body anyway are exactly the ones that break later under load. And the UA row is why "HTTPie gets 403 but curl gets 200" exists: some edges allowlist curl/* and treat everything else as a bot. The debugging guide has the UA-first triage order.

Picking by constraint, not taste

  • "The command has to run on machines I don't control" → curl. Every alternative's install story is a sentence you'll have to defend in a Dockerfile review.
  • "I live in a terminal poking one API all day" → xh if you can install one binary, HTTPie if your team already runs Python everywhere. The typed-data syntax (:= real JSON, = string field) is not cosmetic — it deletes the quoting layer where quoting hell lives.
  • "The job is mirrors and big files" → wget2, no contest. wget2 -c --limit-rate 2M URL is resumption, rate limit, and correct filename handling in one line; the curl equivalent is a paragraph.
  • "It's inside an app" → requests/httpx or fetch, and then convert your working curl with the converter instead of rewriting from memory — the diff between converted code and hand-written code is where the bugs are.

FAQ

Which one should I paste into bug reports? curl, because everyone receiving it can run it. If you found the bug with xh, run xh --curl and attach that.

Does any alternative follow redirects by default? HTTPie and xh do; curl needs -L, and the failure mode is documented in the flags guide. wget follows by default, which is why its authors never needed -L.

Won't using HTTPie at work get me in trouble? It gets you in trouble the first time your one-liner has to run on a prod box with no pip. xh's single-binary story exists for exactly that sentence.

HTTPie gets 403 on the same URL where curl gets 200. Same network, same cert. Almost certainly the User-Agent: some edge rules allowlist curl/* and block tool UAs. Confirm by overriding it (http ... User-Agent:curl/8.5.0), then decide whether you want to keep spoofing it or ask the API owner to fix the rule.

Does xh's --curl output lose anything? Flags with no curl equivalent — xh's --curl emits method, headers, and body, but verify the body encoding by eye: JSON-by-default in xh maps to -d plus a Content-Type header in curl, and a form-field = in xh is not the same as a JSON body wearing a JSON header.

Developer Sponsor / Partner
Copied to clipboard!