Skip to content

Fix: HTTPie Not Working — JSON Body, Authentication, and Session Errors

FixDevs · (Updated: )

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 values

Or 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 time

Or HTTPS requests fail behind a corporate proxy:

$ https example.com
SSLError: certificate verify failed: unable to get local issuer certificate

Or the http command isn’t found after install:

$ pip install httpie
$ http GET httpbin.org/get
bash: http: command not found

HTTPie 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 Chocolatey

Verify install:

http --version
# 3.2.4

which http
# /usr/local/bin/http  or  ~/.local/bin/http

Common 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 -s

Prefer 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:

SeparatorUseExampleSends
=JSON field (string)name=Alice{"name": "Alice"}
:=JSON field (raw)age:=30{"age": 30}
==URL query parameterq==search?q=search
:HTTP headerX-Token:abcHeader 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:=30

Common 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-data

File 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.json

Fix 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-key

Plugin 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 session

Sessions 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 automatically

Relative 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.com

Common error in corporate networks:

SSLError: certificate verify failed: unable to get local issuer certificate

Corporate firewalls often intercept HTTPS with their own CA. Either:

  1. Get the corporate CA bundle from IT, use --verify=/path/to/bundle.pem
  2. Set REQUESTS_CA_BUNDLE env var (HTTPie respects it):
export REQUESTS_CA_BUNDLE=/path/to/corporate-ca.pem
http GET https://internal.example.com

Never 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.zip

Pretty-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 colors

Common 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.json

Fix 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/3

Print 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:

  1. Bake HTTPie into the bastion image. Use pipx install httpie in the image build and verify in CI that http --version returns a known version. The image rebuild is cheap; the incident delay is not.
  2. 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/health once a week catches drift before the next page.
  3. Pin auth plugins separately. pipx inject httpie httpie-aws-auth==1.0.0 makes 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:

  1. Use a session for each environment (dev, staging, prod) so you can’t accidentally hit prod with a dev token.
  2. Set --check-status in scripts so non-2xx responses fail loudly instead of silently.
  3. Keep --debug output out of shared Slack channels — it leaks headers including Authorization.
  4. Rotate session-stored tokens after every incident; you don’t know whose laptop ran what.
  5. Never --verify=no in 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"
fi

For 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/get

HTTPie 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.png

For 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/users

For 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/desktop

Install 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.

Useful for sharing requests in documentation:

http --print=H GET httpbin.org/get   # Just request headers — quick check

For 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=Alice

The 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/me

Treat 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 keys

HTTPie’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 xh for shell loops. Same syntax (mostly), Rust binary, near-instant startup.
  • Move to Python requests or httpx for 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.

F

FixDevs

Solo developer based in Japan. Every solution is cross-referenced with official documentation and tested before publishing.

Was this article helpful?

Related Articles