A hip HTTP API client with an ergonomic CLI.
cargo install --path .The simplest way to use httpster is to make ad-hoc requests to any URL.
Note
The sub-command to send a request is request, but you can use the shorter aliases req or r for brevity. It's also possible to use alias h=httpster in your shell to shorten the full command even further.
# Simple GET request (using full sub-command name)
httpster request https://api.example.com/users/123
# POST with inline JSON (using short sub-command name)
httpster r https://api.example.com/users -m POST -t json -b '{"name":"Alice","email":"alice@example.com"}'
# Add custom headers and query parameters
httpster r https://api.example.com/search \
-H 'X-Trace: abc123' \
-q q=alice -q page=2
# POST with body from a file
httpster r https://api.example.com/users -m POST -t json -b @user.jsonThe response body goes to stdout (so you can pipe it), and the HTTP status/headers go to stderr (so you can still see them):
# Capture just the body, see status and headers
httpster r https://api.example.com/users/123 > response.json
# Pipe to jq for formatting
httpster r https://api.example.com/users/123 | jq '.[] | {name, email}'Beyond ad-hoc requests, you can define requests in TOML files to save and reuse them.
Create a TOML file with request details and execute it with the explicit file path:
# my-request.toml
method = "GET"
url = "https://api.example.com/users"Run it with @ prefix to use an explicit path:
httpster r @my-request.tomlThis works for any request file anywhere on your system. You can also add headers, query parameters, and other details:
# my-request.toml
method = "GET"
url = "https://api.example.com/search"
headers.Accept = "application/json"
query.q = "alice"
query.page = "1"httpster uses conventions to locate request files automatically. Create a httpster.toml file at your project root:
project/
httpster.toml
requests/
users/
list.toml
get.toml
create.toml
Run requests by their path under requests/ (minus .toml):
httpster r users/list
httpster r users/get
httpster r users/createA simple request file:
# requests/users/list.toml
method = "GET"
url = "https://api.example.com/users"By default, httpster looks for a httpster.toml file and uses these conventions:
# httpster.toml (defaults if not specified)
[conventions]
requests = "requests"
profiles = "profiles"
contexts = "contexts"You can customize the paths in your httpster.toml to organize files however you prefer:
# httpster.toml - nested structure example
[conventions]
requests = "httpster/requests"
profiles = "httpster/profiles"
contexts = "httpster/contexts"Profiles are reusable sets of request fields that can be referenced by multiple requests. By default, create them under profiles/:
# profiles/api.toml
url = "https://api.example.com"
type = "json"
headers.Accept = "application/json"
headers."Content-Type" = "application/json"
# profiles/auth.toml
auth.type = "bearer"
[auth.bearer]
token = "your-token-here"Reference profiles in your requests:
# requests/users/list.toml
profiles = ["api", "auth"]
method = "GET"
path = "/users"You can also use profiles ad-hoc in CLI commands:
httpster r https://api.example.com/users \
-p api \
-p authOr override the URL for a defined request:
# Use the users/list request but with a different URL
httpster r users/list -u https://api.staging.example.comContexts are files containing variables that get interpolated into requests, making it easy to switch between environments (dev, prod, etc.). By default, create them under contexts/:
# contexts/dev.toml
api_url = "https://api.dev.example.com"
token = "dev-token"
# contexts/prod.toml
api_url = "https://api.example.com"
token = "prod-token"Use variables in profiles and requests:
# profiles/api.toml
url = "{{ api_url }}"
type = "json"
# profiles/auth.toml
auth.type = "bearer"
[auth.bearer]
token = "{{ token }}"Specify a context when running requests:
httpster r users/list -c devSet a default context in httpster.toml:
# httpster.toml
[defaults]
context = "dev"Or use environment variable:
export HTTPSTER_CONTEXT=prod
httpster r users/listYou can also pass variables on the CLI to override context variables:
httpster r users/get -c dev --set id=42By default, httpster looks for config starting at the current directory and walking up. The first directory containing a httpster.toml file is considered the project root.
You can override this behavior:
HTTPSTER_CONFIGenvironment variable--config <FILE>CLI option
Precedence: --config > HTTPSTER_CONFIG > discovered httpster.toml > defaults
- stderr: HTTP status line and response headers (optionally piped through configured header pipeline)
- stdout: Response body (optionally piped based on content-type and pipelines)
This design makes it easy to pipe body to tools or capture it while still seeing status/headers:
# Capture body, see status/headers
httpster r https://api.example.com/users > users.json
# Pipe body through tools
httpster r https://api.example.com/users | jq '.[] | select(.active)'
# Both: see headers AND pipe body
httpster r https://api.example.com/users 2>&1 | tee response.log | jq '.'httpster supports Basic, Bearer, and OAuth2 client credentials. Auth configs are nested: set auth.type, populate the matching table, and optionally add auth.headers for scoped extra headers (e.g., API keys). Scoped headers are applied with auth but never override explicit headers.
# profiles/auth_prod.toml
auth.type = "bearer"
[auth.bearer]
token = "your-token-here"Set a default auth for all requests:
[defaults]
auth.type = "bearer"
[auth.bearer]
token = "your-token-here"CLI override: --bearer-token <TOKEN>.
auth.type = "oauth2-client-credentials"
[auth.oauth2_client_credentials]
token_url = "https://auth.example.com/oauth/token"
client_id = "your-client-id"
client_secret = "your-client-secret"
scopes = ["read", "write"]CLI override: --oauth2-token-url, --oauth2-client-id, --oauth2-client-secret, --oauth2-scope <SCOPE> (repeatable).
auth.type = "basic"
[auth.basic]
username = "user"
password = "pass"CLI override: --basic-username and --basic-password.
Attach additional headers that should be grouped with your auth (useful for API keys that should be redacted):
auth.type = "bearer"
[auth.bearer]
token = "{{ token }}"
[auth.headers]
X-API-Key = "{{ api_key }}"CLI override: --auth-header KEY=VALUE (repeatable). If you only need an API-key-style header and no Authorization header, use regular headers instead (e.g., headers.X-API-Key or -H "X-API-Key: value").
A typical project layout using default conventions:
project/
httpster.toml
contexts/
dev.toml
prod.toml
profiles/
api.toml
auth.toml
format.json.toml
requests/
users/
list.toml
get.toml
create.toml
httpster.toml:
[defaults]
context = "dev"
type = "json"
headers.Accept = "application/json"
headers."Content-Type" = "application/json"
profiles = ["api", "auth"]contexts/dev.toml:
api_url = "https://api.dev.example.com"
token = "dev-token"
user_id = "1"profiles/api.toml:
url = "{{ api_url }}"
type = "json"profiles/auth.toml:
auth.type = "bearer"
[auth.bearer]
token = "{{ token }}"profiles/format.json.toml:
response_body_pipeline = ["jq", "bat -l json --style=numbers --color=always"]requests/users/list.toml:
profiles = ["api", "auth", "format.json"]
method = "GET"
path = "/users"requests/users/get.toml:
profiles = ["api", "auth", "format.json"]
method = "GET"
path = "/users/{{ user_id }}"requests/users/create.toml:
profiles = ["api", "auth"]
method = "POST"
path = "/users"
body = '{"name":"{{ name }}","email":"{{ email }}"}'httpster request <REQUEST_PATH|URL> [...](alias:httpster r) — Execute a predefined request by path or an ad-hoc URLhttpster contexts— List contexts under the configured contexts directory (marks default with*)httpster context— Show the current context (fromHTTPSTER_CONTEXTenv or default)httpster switch <CONTEXT_PATH>— Print an export statement to setHTTPSTER_CONTEXT(use:eval "$(httpster switch prod)")httpster completions <SHELL>— Generate shell completions
Common flags (ad-hoc and request):
--set key=value— Set a variable (overrides context variables, repeatable)-c, --context <PATH>— Use a specific context (convention path or@file)-t, --type <type>— Set content type and auto-configure headers (e.g.,json,xml,form, or any MIME type)-p, --profile <PATH>— Include a profile (repeatable, convention path or@file)-H, --header key:value— Add a header (repeatable)-q, --query key=value— Add a query parameter (repeatable)--bearer-token <TOKEN>— Set bearer auth via CLI--basic-username <USER>/--basic-password <PASS>— Set basic auth via CLI--oauth2-token-url <URL>,--oauth2-client-id <ID>,--oauth2-client-secret <SECRET>,--oauth2-scope <SCOPE>(repeatable) — Set OAuth2 client-credentials auth via CLI--auth-header KEY=VALUE— Add auth-scoped headers (repeatable)
- No default content type is assumed. Use
--type, requesttype = "...", or explicit headers. - Set
HTTPSTER_TYPEto define a session-wide default (e.g.,export HTTPSTER_TYPE=json). - Precedence (content type): CLI headers >
--type> requesttype> profiletype>HTTPSTER_TYPE> none. - Query parameter ordering: Query parameters preserve their order from TOML configuration and CLI arguments. If both the URL and config/CLI provide query parameters with the same key, all values are appended (not overridden).
Response formatting pipelines let you automatically format responses through external tools. Pipelines can be specified as:
- A single string (shorthand):
response_body_pipeline = "jq" - An array of commands:
response_body_pipeline = ["jq", "bat -l json"] - An object with content-type keys:
response_body_pipeline = { "default" = [...], "application/json" = [...] }
Pipelines are specified on profiles or individual requests with this precedence:
- Headers pipeline: request > profile > global defaults
- Body pipeline: request > profile > content-type-specific (request/profile/global) > global defaults
For body pipelines, content-type matching works as follows:
- Try exact match (e.g.,
"application/json; charset=utf-8") - Try base type (e.g.,
"application/json") - Try
"default"key
Build the project:
cargo buildRun tests:
cargo testRun locally:
cargo run -- https://api.example.com/usersFor code coverage reports, install cargo-llvm-cov:
cargo install cargo-llvm-covPrint a summary to the console:
just coverageView HTML reports:
just coverage --html --open