A zero-dependency, colorful command-line HTTP client and developer workflow platform.
Installation β’ Quick Start β’ All Flags β’ Collections β’ Environments β’ History β’ Scripting & CI β’ How It Works
Httli is a fast, terminal-native HTTP client built in Go with zero external runtime dependencies. It goes beyond curl to give you a complete developer workflow β saved requests, environments, history, batch execution, and JSON extraction β all from your terminal.
# Fire a quick request
httli -u https://api.github.com/users/octocat
# POST with auth and body
httli -m POST -u https://api.example.com/login \
-d '{"user":"admin","pass":"secret"}' \
-b mytoken
# Save it, then run it in any environment
httli collection save auth/login -m POST -u {{BASE_URL}}/login -d '{"user":"admin"}'
httli collection run auth/login --env prod
# Run a whole test suite in CI
httli collection run-all auth/ --fail-fast --timeout 10s --format jsongit clone https://github.com/I-invincib1e/Httli.git
cd Httli
go build -o httli ./cmd/httli/main.goBuild with version injection:
go build -ldflags "-X github.com/I-invincib1e/httli/cmd.Version=1.1.0" -o httli ./cmd/httli/main.go# Linux / macOS
sudo mv httli /usr/local/bin/
# Windows (Admin PowerShell)
Move-Item httli.exe C:\Windows\System32\source <(httli completion bash) # Bash
source <(httli completion zsh) # Zsh
httli completion powershell | Invoke-Expression # PowerShell# GET (default)
httli -u https://jsonplaceholder.typicode.com/posts/1
# POST with JSON body
httli -m POST -u https://api.example.com/users \
-d '{"name":"Alice"}' \
-H "Content-Type:application/json"
# With bearer token
httli -u https://api.example.com/me -b eyJhbGc...
# With basic auth
httli -u https://api.example.com/secure -a user:pass
# Read body from file
httli -m POST -u https://api.example.com/data -d @payload.json
# Read body from stdin
echo '{"key":"val"}' | httli -m POST -u https://api.example.com/data -d @-| Command | Description |
|---|---|
httli [flags] |
Quick mode β fires a request directly |
httli request send [flags] |
Explicit request with all options |
httli collection save <name> [flags] |
Save a request to your collection |
httli collection update <name> [flags] |
Update an existing saved request |
httli collection run <name> [flags] |
Run a saved request |
httli collection run-all [prefix] [flags] |
Run all saved requests (batch) |
httli collection list |
List all saved requests |
httli collection describe <name> <text> |
Annotate a saved request |
httli collection export <file> |
Export collections to JSON |
httli collection import <file> |
Import collections from JSON |
httli history |
View request history (last 50) |
httli history show <n> |
Inspect history entry details |
httli history clear |
Clear all history |
httli rerun <n> [flags] |
Re-execute a history entry |
httli env list |
Show loaded environment variables |
httli completion <shell> |
Generate shell autocomplete |
httli version |
Print version |
| Short | Long | Default | Description |
|---|---|---|---|
-m |
--method |
GET |
HTTP method |
-u |
--url |
β | URL to request (required) |
-d |
--data |
β | Request body β JSON string, @- for stdin, @file.json for file |
-f |
--file |
β | Read body from file (alternative to @file) |
-H |
--header |
β | Headers as Key:Value,Key2:Value2 |
-b |
--bearer |
β | Bearer token (sets Authorization: Bearer <token>) |
-a |
--auth |
β | Basic auth as user:pass (sets Authorization: Basic ...) |
-t |
--timeout |
30s |
Request timeout (Go duration: 5s, 1m30s) |
-L |
--follow |
false |
Follow HTTP redirects |
-e |
--env |
β | Load .env.<name> in addition to .env and .env.local |
| Short | Long | Description |
|---|---|---|
-o |
--output |
Save response body to file (always runs, even with --raw) |
-x |
--extract |
Extract a field from JSON response using dot notation (.data.token, .items[0].id) |
--format json |
Output structured JSON (for piping to jq) |
|
--raw |
Print raw body only β no colors, headers, or formatting | |
-s |
--status-only |
Print only the numeric status code |
-q |
--quiet |
Print only the response body |
-S |
--silent |
Suppress all output β exit code only |
-v |
--verbose |
Show request/response headers |
| Short | Long | Description |
|---|---|---|
-F |
--fail |
Exit 22 on HTTP 4xx/5xx (curl convention) |
--fail-fast |
Stop run-all batch on first error |
|
-r |
--retry |
Number of retries on network error or 5xx |
--retry-delay |
Seconds between retries (default: 2) |
|
--retry-backoff |
Opt-in exponential backoff β delay doubles each attempt, capped at 30s | |
--dry-run |
Print resolved request without making a network call | |
--ignore-missing-env |
Don't error on unresolved {{VAR}} placeholders |
Collections let you save, name, and replay HTTP requests β with full fidelity, including auth, timeouts, and headers.
# Save a request (fails if name already exists)
httli collection save auth/login \
-m POST \
-u {{BASE_URL}}/auth/login \
-d '{"user":"admin"}' \
-b mytoken \
--timeout 15s \
--retry 2
# Add a description (shown in list)
httli collection describe auth/login "Authenticate and get session token"
# Update an existing request
httli collection update auth/login -b newtoken
# Run it
httli collection run auth/login --env prod
# Run with runtime overrides
httli collection run auth/login --timeout 5s --format json --verbosehttli collection listSaved Requests:
Ungrouped/
- ping [GET https://api.example.com/ping]
auth/
- login [POST https://{{BASE_URL}}/auth/login] # Authenticate and get session token
- refresh [POST https://{{BASE_URL}}/auth/refresh]
Every field is persisted β nothing is silently dropped:
| Field | Saved? |
|---|---|
| Method, URL | β |
| Headers | β |
| Body | β |
| Bearer token | β |
| Basic auth | β |
| Timeout | β
(stored as "15s") |
| Follow redirects | β |
| Retry count & delay | β |
| Description | β |
| Created / Updated timestamps | β |
Use / in the name to group requests:
httli collection save api/users/list -m GET -u {{BASE_URL}}/users
httli collection save api/users/create -m POST -u {{BASE_URL}}/users -d '{"name":"Bob"}'
httli collection save api/products -m GET -u {{BASE_URL}}/products# Run everything
httli collection run-all
# Run only the auth/ group
httli collection run-all auth/
# Stop immediately on first failure
httli collection run-all --fail-fast
# Apply runtime overrides to all
httli collection run-all --timeout 10s --retry 1 --format jsonState chaining between requests β run-all automatically sets env vars after each step:
| Variable | Value |
|---|---|
HTTLI_LAST_STATUS |
HTTP status code, e.g. 200 |
HTTLI_LAST_BODY_PATH |
Absolute path to temp file with raw body |
HTTLI_LAST_JSON |
Raw JSON body string (if β€ 32KB, valid JSON) |
Use {{HTTLI_LAST_JSON}} in a subsequent request body to chain results:
httli collection save api/order -m POST -u {{BASE_URL}}/order \
-d '{"token":"{{HTTLI_LAST_JSON}}"}'# Export all collections to file
httli collection export my-api.json
# Import (3 modes)
httli collection import my-api.json # merge (default) β add new, skip conflicts
httli collection import my-api.json --overwrite # replace all existing with importedTip: Create a
.httli/directory in your project root to scope collections to that project. Commit.httli/to share collections with your team alongside your code.
Use {{VAR}} placeholders anywhere β URLs, headers, body, auth β and substitute them with .env files.
BASE_URL=https://api.example.com
API_TOKEN=secret123
DB_USER=admin.env β base defaults (committed, shared)
.env.local β local overrides (git-ignored, personal)
.env.<name> β activated via --env flag (e.g., .env.prod)
Safe for CI/CD: Httli never overwrites an environment variable that already exists in the shell. Your CI platform's secrets always win.
# Use base .env only
httli -u {{BASE_URL}}/users -b {{API_TOKEN}}
# Activate a specific environment
httli collection run auth/login --env prod # loads .env + .env.local + .env.prod
httli collection run auth/login --env staginghttli env listActive Environment: prod
Loaded files (in order):
β .env
β .env.local
β .env.prod
β (no other files)
Chaining Variables:
HTTLI_LAST_STATUS = 200
All Environment Variables:
API_TOKEN = secret123
BASE_URL = https://api.example.com
# Strict mode (default) β errors on unresolved {{VAR}}
httli -u {{MISSING_VAR}}/api
# Error: environment variable 'MISSING_VAR' not found
# Permissive mode β leaves placeholder as-is
httli -u {{MISSING_VAR}}/api --ignore-missing-envEvery executed request is automatically recorded. The last 50 entries are kept.
# View history with status and duration
httli historyRequest History:
[1] β GET https://api.example.com/users β 200 (124ms) 2026-04-01T01:00:00+05:30
[2] β POST https://api.example.com/login β 401 (89ms) 2026-04-01T01:01:00+05:30
[3] β GET https://jsonplaceholder.typicode.com/posts/1 β 200 (287ms) 2026-04-01T01:02:00+05:30
# Inspect a full entry (method, URL, headers, body, auth, duration)
httli history show 1
# Re-run entry #1 with overrides β full fidelity (headers, body, auth restored)
httli rerun 1
httli rerun 1 --timeout 5s --format json --verbose
# Clear all history
httli history clearFull-fidelity rerun:
rerunrestores the complete original request β method, URL, headers, body, bearer token, and basic auth β not just the URL.
Httli is designed for headless use in scripts and pipelines.
| Code | Meaning |
|---|---|
0 |
Success |
1 |
Usage error / network error |
22 |
HTTP 4xx/5xx (when --fail is set) |
# Structured JSON output to pipe to jq
httli -u https://api.example.com/users --format json | jq '.body.users[0].name'
# Native extraction (no jq needed)
httli -u https://api.example.com/data --extract .items[0].id
# Dot-notation supports arrays
httli -u https://api.example.com/data --extract .data.users[0].emailcat payload.json | httli -m POST -u https://api.example.com/ingest -d @-# Script only cares about exit code
httli -u https://api.example.com/health --fail --silent
if [ $? -eq 0 ]; then echo "API is up"; fi
# Just print status code
STATUS=$(httli -u https://api.example.com/health --status-only)
echo "Got: $STATUS"# See the fully resolved request (env vars substituted) without firing it
httli -m POST -u {{BASE_URL}}/login -d '{"user":"admin"}' --env prod --dry-run# Fixed delay retry (default)
httli -u https://api.example.com/data --retry 3 --retry-delay 5
# Exponential backoff (2s, 4s, 8s... capped at 30s)
httli -u https://api.example.com/data --retry 4 --retry-delay 2 --retry-backoff
# Verbose retry output to stderr (won't pollute stdout)
httli -u https://api.example.com/data --retry 2 --verbose
# β Retry 1/2 in 2s (HTTP 503)
# β Retry 2/2 in 2s (HTTP 503)#!/bin/bash
# Run full API test suite, fail on first error, timeout per request, exit 22 on any 4xx/5xx
httli collection run-all smoke-tests/ \
--env ci \
--fail-fast \
--fail \
--timeout 10s \
--retry 1 \
--format json \
--silent# --output works with ALL display modes (raw, silent, status-only)
httli -u https://api.example.com/report --output report.json --silent
httli -u https://api.example.com/report --output report.json --rawhttli/
βββ cmd/ # Command layer (CLI entry points)
β βββ httli/main.go # Binary entry point
β βββ root.go # Command dispatcher (recursive, N-depth)
β βββ request.go # httli [flags] / request send
β βββ collection.go # collection save/update/run/list/describe/export/import
β βββ collection_runall.go # collection run-all (batch execution)
β βββ history.go # history / rerun
β βββ env.go # env list
β βββ completion.go # shell autocomplete
β βββ version.go # version
βββ internal/
β βββ client/ # HTTP execution engine
β β βββ client.go # ExecuteRequest, retry loop, backoff, auth
β βββ config/ # Flag parsing and env var system
β β βββ config.go # Config struct, ParseFlags, ApplyOverrides
β β βββ env.go # LoadEnv, Interpolate ({{VAR}} system)
β β βββ global.go # Global config (default env, project-local detection)
β βββ collections/ # Collection storage (save/get/list/import/export)
β β βββ collections.go
β βββ history/ # Request history (record/load/list/show/clear)
β β βββ history.go
β βββ output/ # Response rendering (pretty/raw/json/extract)
β β βββ output.go
β βββ storage/ # Path resolution (project-local vs global)
β β βββ resolver.go
β βββ styles/ # Terminal color styles (lipgloss)
β βββ styles.go
httli -m POST -u {{BASE_URL}}/login -d '{"user":"admin"}' --env prod
β
βΌ
1. ParseFlags() β parse all CLI flags into Config struct
β
βΌ
2. LoadEnv(prod) β load .env, .env.local, .env.prod
β (never overwrites existing shell vars)
βΌ
3. InterpolateAll() β replace {{BASE_URL}} with env var value
β (errors on missing vars unless --ignore-missing-env)
βΌ
4. Validate() β ensure URL is present, format is valid
β
βΌ
5. [dry-run check] β if --dry-run, print request and exit
β
βΌ
6. ExecuteRequest() β make HTTP call
β add Auth headers (bearer β Authorization: Bearer ...)
β JSON body validation
β retry loop (with optional exponential backoff)
β 5xx body always captured before retry
β verbose retry logging to stderr
βΌ
7. history.Record() β save full request + response metadata to history
β
βΌ
8. DisplayResponse() β render output:
--output saves file first (orthogonal to display mode)
then: extract β format json β raw β status-only β quiet β full pretty
Httli uses project-local storage when a .httli/ directory is detected in the current working directory. Otherwise it falls back to a global directory.
| Platform | Global path |
|---|---|
| Windows | %APPDATA%\httli\ |
| macOS | ~/Library/Application Support/httli/ |
| Linux | ~/.config/httli/ |
| File | Contents |
|---|---|
collections.json |
All saved requests |
history.json |
Last 50 executed requests |
ParseFlags()usesflag.ContinueOnErrorβ unknown flags return errors instead of callingos.Exit- All flags have both short (
-m) and long (--method) forms registered in the sameFlagSet InterpolateAll()is not called inParseFlags()β callers invoke it explicitly, preventing double-substitution whencollection runorreruncall it after loading the saved configApplyOverrides(runCfg)merges runtime CLI flags onto a loaded collection config β timeout is only overridden if the user explicitly set a non-default value
Placeholders are matched by the regex \{\{\s*([A-Za-z0-9_]+)\s*\}\} (compiled once at package level). The regex supports optional whitespace ({{ VAR }} or {{VAR}}). Interpolation runs across: URL, body, bearer token, basic auth, and all header values.
attempt 1 β request β 5xx or network error β read body β save lastResp
attempt 2 β sleep(delay) β request β ...
attempt N β sleep(delay) β request β 5xx β return lastResp (NOT an error)
# with --retry-backoff:
delay formula: baseDelay Γ 2^attempt, capped at 30s
attempt 1: 2s, attempt 2: 4s, attempt 3: 8s, attempt 4: 16s, attempt 5+: 30s
On the final attempt, the 5xx
Responseis returned (not an error) so callers can inspectresp.StatusCodeandresp.Bodyβ enabling--failexit code logic and run-all summary tables to work correctly with server error details.
Contributions are welcome! See CONTRIBUTING.md for guidelines.
go test ./...
go vet ./...- All new features belong in
internal/β keepcmd/as a thin dispatch layer ParseFlags()must not callInterpolateAll()β callers do this explicitly- New config fields go in
Configstruct with both short+long flag registration - Exported functions in
internal/packages take*config.Config, not individual params
MIT License β see LICENSE for details.
