Fix: HTTPie Not Working — JSON Body, Authentication, and Session Errors
Part of: Docker, DevOps & Infrastructure
Quick Answer
How to fix HTTPie errors — installation collision with httpie-cli, JSON body interpretation, multipart form data, OAuth bearer token, persistent sessions, SSL verification skip, and download mode.
The Error
You install HTTPie and run a basic request — it fails to parse JSON correctly:
$ http POST httpbin.org/post name=Alice age=30
# Sent as form data, not JSON
# Body: "name=Alice&age=30"Or sending an integer becomes a string:
$ http POST httpbin.org/post age:=30 active:=true
# Should be: {"age": 30, "active": true}
# Common mistake: omitting := for typed valuesOr session-based auth doesn’t persist:
$ http --auth user:pass GET httpbin.org/basic-auth/user/pass
# OK
$ http GET httpbin.org/get
# Auth not preserved — new session each timeOr HTTPS requests fail behind a corporate proxy:
$ https example.com
SSLError: certificate verify failed: unable to get local issuer certificateOr the http command isn’t found after install:
$ pip install httpie
$ http GET httpbin.org/get
bash: http: command not foundHTTPie is the modern alternative to curl — colorized output, JSON-aware syntax, intuitive headers/parameters. Used heavily for API exploration and quick HTTP debugging. The shorthand syntax (field=value vs field:=value vs field==value) is concise but confuses newcomers — = for strings, := for raw JSON values, == for query parameters. This guide covers each common issue.
Why This Happens
HTTPie’s syntax is designed for terseness — three different separators for three different parameter types. The conciseness saves typing but the rules aren’t immediately obvious. Sessions, authentication, and SSL verification follow standard patterns but the CLI flags differ from curl’s.
Fix 1: Installation and PATH
# Recommended — install via pipx for global isolated CLI
pipx install httpie
# Or pip
pip install --user httpie
# Or uv
uv tool install httpie
# Or platform package managers
brew install httpie # macOS
apt install httpie # Debian/Ubuntu
choco install httpie # Windows ChocolateyVerify install:
http --version
# 3.2.4
which http
# /usr/local/bin/http or ~/.local/bin/httpCommon Mistake: Installing via pip install httpie-cli instead of httpie. The package name is httpie — there’s no httpie-cli on PyPI. The error is “package not found.” Search PyPI for the correct name (the project’s homepage at httpie.io shows the exact install command).
Modern HTTPie also installs https for HTTPS shortcuts:
http GET example.com # http://example.com
https GET example.com # https://example.com — same as http -sPrefer pipx for global installs. It keeps HTTPie and its plugin dependencies isolated from any project virtualenv, so plugin upgrades don’t break your application’s Python environment.
Fix 2: Request Syntax — =, :=, ==, :
The four separators each mean something different:
| Separator | Use | Example | Sends |
|---|---|---|---|
= | JSON field (string) | name=Alice | {"name": "Alice"} |
:= | JSON field (raw) | age:=30 | {"age": 30} |
== | URL query parameter | q==search | ?q=search |
: | HTTP header | X-Token:abc | Header X-Token: abc |
@ | File upload (multipart) | [email protected] | Form field with file |
Examples:
# POST JSON: {"name": "Alice", "age": 30, "active": true}
http POST httpbin.org/post \
name=Alice \
age:=30 \
active:=true
# Query parameters: GET /search?q=foo&limit=10
http GET httpbin.org/get q==foo limit==10
# Headers
http GET httpbin.org/get \
X-API-Key:secret \
Accept:application/json
# Combined
http POST api.example.com/users \
Authorization:"Bearer token" \
User-Agent:my-client \
name=Alice \
age:=30Common Mistake: Sending age=30 (string) when you mean age:=30 (integer). The endpoint sees "age": "30" instead of "age": 30 — fails server-side validation that expects an integer. Always use := for non-string types (numbers, booleans, arrays, objects).
Pro Tip: For complex JSON values, use := with explicit JSON:
http POST api.example.com/users \
name=Alice \
tags:='["admin", "active"]' \
address:='{"city": "NYC", "zip": "10001"}'The single quotes around the JSON keep the shell from interpreting it.
Fix 3: Sending Form Data
For application/x-www-form-urlencoded or multipart/form-data instead of JSON:
# Form-encoded — use --form (or -f)
http --form POST httpbin.org/post name=Alice age=30
# Body: "name=Alice&age=30"
# Content-Type: application/x-www-form-urlencoded
# Multipart — automatic when using file uploads with @
http POST httpbin.org/post \
--form \
name=Alice \
[email protected]
# Content-Type: multipart/form-dataFile upload as JSON body:
# Read file contents into a JSON field
http POST api.example.com/upload \
name=Alice \
data:[email protected] # := and @ — include the JSON file content as a value
# Read entire body from file
http POST api.example.com/upload @body.jsonFix 4: Authentication
# Basic auth
http --auth user:pass GET httpbin.org/basic-auth/user/pass
# Bearer token via header
http GET api.example.com/me Authorization:"Bearer eyJ..."
# Or use --auth-type
http --auth-type bearer --auth eyJ... GET api.example.com/me
# Digest auth
http --auth-type digest --auth user:pass GET httpbin.org/digest-auth/auth/user/pass
# Custom token formats
http GET api.example.com/me X-API-Key:my-secret-keyPlugin auth types (OAuth, AWS Sig, JWT):
# OAuth
pip install httpie-oauth
http --auth-type=oauth1 --auth='client_id:client_secret' GET api.example.com/
# AWS Sig
pip install httpie-aws-auth
http --auth-type=aws4 --auth='AKID:SECRET' GET https://my-bucket.s3.amazonaws.com/Pro Tip: For repeated requests with the same auth, use sessions (covered in next fix). Typing --auth user:pass for every request to your dev API gets old fast.
Fix 5: Persistent Sessions
# Create or use a session named "my-api"
http --session=my-api --auth user:pass GET api.example.com/me
# Subsequent requests in the same session reuse cookies and auth
http --session=my-api GET api.example.com/data
# Auth header automatically included from sessionSessions store:
- Cookies (persist across requests)
- Auth credentials
- Custom headers added with
--header
Session storage — ~/.config/httpie/sessions/<host>/<name>.json:
ls ~/.config/httpie/sessions/api.example.com/
# my-api.json
cat ~/.config/httpie/sessions/api.example.com/my-api.json
# {
# "auth": {"type": "basic", "raw_auth": "user:pass"},
# "cookies": [...],
# "headers": {...}
# }Anonymous sessions for cookies only:
http --session=./local-session.json POST api.example.com/login user=alice password=secret
# Cookies stored in ./local-session.json (instead of global config)
http --session=./local-session.json GET api.example.com/profile
# Cookies sent automaticallyRelative path stores the session as a file you can commit/share/version (useful for shared dev environments).
Common Mistake: Forgetting that sessions are PER-HOST. A session named my-api for api.example.com doesn’t carry over to api.test.com — different host, different storage.
Fix 6: SSL Certificate Issues
# Skip SSL verification (development ONLY)
http --verify=no GET https://self-signed.example.com
# Custom CA bundle (corporate proxies, internal CAs)
http --verify=/path/to/corporate-ca.pem GET https://internal.example.com
# Client certificates (mutual TLS)
http --cert=client.pem --cert-key=client-key.pem GET https://api.example.comCommon error in corporate networks:
SSLError: certificate verify failed: unable to get local issuer certificateCorporate firewalls often intercept HTTPS with their own CA. Either:
- Get the corporate CA bundle from IT, use
--verify=/path/to/bundle.pem - Set
REQUESTS_CA_BUNDLEenv var (HTTPie respects it):
export REQUESTS_CA_BUNDLE=/path/to/corporate-ca.pem
http GET https://internal.example.comNever use --verify=no in scripts — it disables MITM protection. Only for one-off debugging.
For Python requests SSL patterns that also apply to HTTPie, see Python SSL certificate verify failed.
Fix 7: Output Formatting and Download Mode
# Suppress request output, show only response
http GET httpbin.org/get
# Show only response body (no headers)
http -b GET httpbin.org/get
# Show only headers (no body)
http -h GET httpbin.org/get
# Show everything (request + response, headers + body) — useful for debugging
http -v GET httpbin.org/get
# Download mode (saves response to file)
http --download GET https://example.com/file.zip
# Auto-detects filename from Content-Disposition or URL
# Saves to ./file.zip
# Custom output filename
http --download --output ./downloaded.zip GET https://example.com/file.zip
# Resume interrupted download
http --download --continue GET https://example.com/huge-file.zipPretty-print JSON response:
http GET api.example.com/users
# Colorized, indented JSON by default
# Disable colors (for piping/scripting)
http --pretty=none GET api.example.com/users
http --pretty=format GET api.example.com/users # Format but no colorsCommon Mistake: Piping HTTPie output to a file and getting ANSI color codes. Use --pretty=none or --pretty=format when redirecting:
# WRONG — file contains color codes
http GET api.example.com/users > users.json
# CORRECT
http --pretty=none GET api.example.com/users > users.json
# Or just use the body-only mode
http -b GET api.example.com/users > users.jsonFix 8: Proxy and Timeout Configuration
# HTTP proxy
http --proxy=http:http://proxy.example.com:8080 GET httpbin.org/ip
# HTTPS proxy
http --proxy=https:http://proxy.example.com:8080 GET https://httpbin.org/ip
# Both via environment variables (HTTPie respects standard vars)
export HTTP_PROXY=http://proxy.example.com:8080
export HTTPS_PROXY=http://proxy.example.com:8080
http GET httpbin.org/ip
# SOCKS5 proxy (requires httpie[socks])
pip install httpie[socks]
http --proxy=socks5://localhost:1080 GET httpbin.org/ip
# Timeout (seconds)
http --timeout=10 GET httpbin.org/delay/5
# Disable redirects
http --follow=no GET httpbin.org/redirect/3Print effective configuration:
http --debug GET httpbin.org/get
# Shows internal state — useful for debugging "why isn't my flag working?"Production Incident Lens — HTTPie on the On-Call Hot Path
HTTPie is an on-call tool. The blast radius of a broken HTTPie install is the time-to-resolve of every incident that needed an HTTP probe. A bastion host where http is missing, an outdated plugin that breaks JWT auth, a session file with stale credentials — each becomes a multi-minute delay on a page that already has a clock running.
Incident pattern — the bastion that drifted. The team runs HTTPie on a jump host because internal APIs aren’t reachable from laptops. A platform upgrade replaced the OS Python, the global pip install of HTTPie broke silently, and nobody noticed for weeks because nobody had needed it. The first incident that required hitting an internal API to confirm “is the service responding?” cost twenty minutes of re-installing HTTPie before any debugging started.
Mitigations:
- Bake HTTPie into the bastion image. Use
pipx install httpiein the image build and verify in CI thathttp --versionreturns a known version. The image rebuild is cheap; the incident delay is not. - Test the on-call toolkit weekly. A simple GitHub Action that SSHs to a non-prod bastion and runs
http --check-status GET https://internal/healthonce a week catches drift before the next page. - Pin auth plugins separately.
pipx inject httpie httpie-aws-auth==1.0.0makes the version explicit. An unpinned plugin can break overnight when a transitive dep ships a bad release.
Incident pattern — --verify=no in a script. During an incident, someone disabled SSL verification to bypass a corporate proxy. The fix shipped as a temporary workaround. Six months later, that script still has --verify=no and is silently accepting any TLS certificate from any host on the path. The day a real MITM happens, the script keeps quiet.
Pro Tip: Grep your repo for --verify=no and verify=False (Python requests equivalent) monthly. Every instance is either an active security risk or a forgotten TODO. Replace with an explicit CA bundle path and REQUESTS_CA_BUNDLE env var so trust is configurable per-environment, not disabled.
Blast radius checklist for on-call HTTPie use:
- Use a session for each environment (
dev,staging,prod) so you can’t accidentally hit prod with a dev token. - Set
--check-statusin scripts so non-2xx responses fail loudly instead of silently. - Keep
--debugoutput out of shared Slack channels — it leaks headers includingAuthorization. - Rotate session-stored tokens after every incident; you don’t know whose laptop ran what.
- Never
--verify=noin production paths. If your IT team mandates a corporate CA, install it once at the system level.
Common Mistake: Pasting an HTTPie command into a chat channel during an incident. The terse syntax is fine, but Authorization:"Bearer eyJ..." is a credential leak. Strip auth headers before sharing, or use environment variables (Authorization:"Bearer $TOKEN") so the secret isn’t in the shared text.
When credentials leak into chat, rotate the token before closing the incident. “I deleted the message” is not the same as “I rotated the credential.”
Still Not Working?
HTTPie vs curl vs xh
- HTTPie — Human-friendly, colorized, JSON-aware. Best for interactive API exploration.
- curl — Universal, scriptable, available everywhere. Best for scripts and CI.
- xh — Rust-based clone of HTTPie, much faster startup. Best when you use HTTPie heavily and notice the Python startup time.
For exploring APIs interactively, HTTPie wins. For shell scripts running thousands of requests, xh (or curl) reduces overhead. HTTPie 4.0+ has improved startup time but still slower than Rust alternatives.
Scripting with HTTPie
# Capture response in shell variable
USERS=$(http -b GET api.example.com/users)
echo "$USERS" | jq '.[]'
# Send POST body from variable
DATA='{"name": "Alice"}'
http POST api.example.com/users <<<"$DATA"
# Iterate over IDs
for id in 1 2 3; do
http -b GET api.example.com/users/$id
done
# Save status code
STATUS=$(http -h GET api.example.com/health | head -1 | awk '{print $2}')
if [ "$STATUS" = "200" ]; then
echo "OK"
fiFor shell automation patterns, HTTPie pairs cleanly with jq for JSON manipulation.
Custom Headers File
For complex header sets, use a session or a wrapper script:
# ~/.bashrc
function api() {
http --session=my-api "$@"
}
# Now any HTTPie command via `api` reuses the session
api GET httpbin.org/getHTTPie in CI
# .github/workflows/test.yml
- name: Smoke test API
run: |
pip install httpie
http --check-status GET https://api.example.com/health
# --check-status exits non-zero on 4xx/5xx--check-status makes HTTPie behave like a proper CI tool — exit 1 for HTTP errors, exit 0 for success.
Sending Binary Data
# Upload binary file as body
http POST api.example.com/upload @binary.dat
# Force binary output (don't decode response)
http --raw GET https://example.com/image.png > image.pngFor multipart uploads with binary data, see Fix 3.
Testing GraphQL APIs
http POST api.example.com/graphql \
query='{ users { id name } }'Or send the full GraphQL request:
http POST api.example.com/graphql \
[email protected] \
variables:='{"id": 1}'For testing GraphQL APIs, HTTPie’s terse syntax is significantly nicer than curl’s escape-heavy approach.
Integration with FastAPI / Flask Development
# Quick test of FastAPI dev server
uvicorn main:app --reload &
http GET localhost:8000/users
http POST localhost:8000/users name=Alice age:=30
# With auth
http --session=dev --auth admin:secret POST localhost:8000/login
http --session=dev GET localhost:8000/admin/usersFor Uvicorn dev server setup that pairs with this workflow, see Uvicorn not working.
Plugins Worth Knowing
HTTPie has a plugin ecosystem for auth schemes, transports, and formatters:
# OAuth (1.0 and 2.0)
pip install httpie-oauth
# AWS Sig v4
pip install httpie-aws-auth
# JWT helpers
pip install httpie-jwt-auth
# HTTPie Desktop (GUI version) — works with the same syntax
# Available at httpie.io/desktopInstall plugins in the same Python environment as HTTPie. If installed via pipx, run pipx inject httpie httpie-oauth.
Compared to Python requests
HTTPie wraps the requests library — the same SSL, proxy, and connection-pool behavior. For automating HTTP from Python directly, use requests or httpx. HTTPie is for interactive command-line use; once you need scripting, transitioning to Python code is usually cleaner.
For httpx-specific async patterns, see httpx not working. For Python requests patterns, see Python requests timeout.
Print Curl Equivalent
Useful for sharing requests in documentation:
http --print=H GET httpbin.org/get # Just request headers — quick checkFor converting between HTTPie and curl syntax, several online tools exist; the patterns map cleanly once you know the separator rules.
Reusing Common Headers
For APIs that require the same headers on every request, create a wrapper:
# ~/.bashrc
function myapi() {
http \
--auth-type=bearer \
--auth="$API_TOKEN" \
--session=myapi \
"X-Org-Id:my-org" \
"$@"
}
# Usage
myapi GET api.example.com/users
myapi POST api.example.com/users name=AliceThe function injects auth and headers; remaining args pass through unchanged.
Session File Owns Stale Cookies After Token Rotation
You rotated an API token, but http --session=prod GET ... still sends the old Authorization value or stale cookies. HTTPie sessions persist auth alongside cookies; rotating the token in your secret store doesn’t touch the session file.
# Inspect the session
cat ~/.config/httpie/sessions/api.example.com/prod.json
# Wipe and re-create
rm ~/.config/httpie/sessions/api.example.com/prod.json
http --session=prod --auth-type=bearer --auth="$NEW_TOKEN" GET api.example.com/meTreat session files as cached state. After any credential rotation, clear them — otherwise the next on-call hand-off inherits stale auth and debugs phantom 401s.
Empty Body When Piping to jq
http -b GET api.example.com/users | jq '.[]'
# jq: error (at <stdin>:1): null (null) has no keysHTTPie’s default output for empty responses is a single newline, which jq reads as null. Always use --check-status to make HTTP errors loud, and inspect the raw response before pipelining:
http --check-status -b GET api.example.com/users
echo "exit: $?"If the server actually returned 200 with empty body, the bug is upstream. If it returned 4xx/5xx, --check-status made the failure visible instead of letting jq swallow it.
Slow Startup on Every Invocation
HTTPie imports a lot of Python at startup. On older laptops or in tight CI loops calling http thousands of times, the per-invocation cost dominates. Two paths:
- Switch to
xhfor shell loops. Same syntax (mostly), Rust binary, near-instant startup. - Move to Python
requestsorhttpxfor any script that calls the API more than a few times. The HTTPie wrapper is for humans; once a loop forms, native HTTP libraries are the right tool.
The cost is per-process. HTTPie is fine for the on-call session where you type a dozen commands an hour. It becomes painful in a for loop that fires once per second across thousands of items.
Solo developer based in Japan. Every solution is cross-referenced with official documentation and tested before publishing.
Was this article helpful?
Related Articles
Fix: Click Not Working — Group Setup, Context Passing, and Parameter Type Errors
How to fix Click errors — UsageError missing argument, Group has no command, ctx.obj not passing between commands, ParamType validation failed, BadOptionUsage no such option, pass_context required, and lazy loading groups.
Fix: Rich Not Working — Live Display Issues, Color in CI, and Console Configuration
How to fix Rich errors — colors not appearing in CI logs, Live display flickering, progress bar not updating, table column overflow, traceback install conflicts, and Console redirect issues.
Fix: Typer Not Working — Argument Errors, Autocomplete, and Subcommand Issues
How to fix Typer errors — type annotation required error, Optional argument parsing, boolean flag conventions, autocomplete installation failed, nested commands not found, and rich traceback disable.
Fix: Clack Not Working — Prompts Not Displaying, Spinners Stuck, or Cancel Not Handled
How to fix @clack/prompts issues — interactive CLI prompts, spinners, multi-select, confirm dialogs, grouped tasks, cancellation handling, and building CLI tools with beautiful output.