Complete reference for all HTTPower configuration options.
Configuration Levels
HTTPower supports three levels of configuration with clear precedence:
- Per-Request (highest priority) - Options passed to individual requests
- Per-Client (medium priority) - Options in
HTTPower.new() - Global (lowest priority) - Options in
config.exs
Rule: More specific configuration overrides less specific.
Configuration Availability Matrix
This table shows which options are supported at each configuration level:
Adapter Selection
| Option | Global Config | Per-Client | Per-Request | Notes |
|---|---|---|---|---|
adapter | ✅ | ✅ | ✅ | Global: config :httpower, adapter: |
Testing
| Option | Global Config | Per-Client | Per-Request | Notes |
|---|---|---|---|---|
test_mode | ✅ | ❌ | ❌ | Global only |
Retry Configuration
| Option | Global Config | Per-Client | Per-Request | Notes |
|---|---|---|---|---|
max_retries | ✅ | ✅ | ✅ | |
retry_safe | ✅ | ✅ | ✅ | |
base_delay | ✅ | ✅ | ✅ | |
max_delay | ✅ | ✅ | ✅ | |
jitter_factor | ✅ | ✅ | ✅ |
Rate Limiting
| Option | Global Config | Per-Client | Per-Request | Notes |
|---|---|---|---|---|
rate_limit | ✅ | ✅ | ✅ | Global: nested config |
rate_limit_key | ❌ | ✅ | ✅ | Per-client/request only |
Circuit Breaker
| Option | Global Config | Per-Client | Per-Request | Notes |
|---|---|---|---|---|
circuit_breaker | ✅ | ✅ | ✅ | Global: nested config |
circuit_breaker_key | ❌ | ✅ | ✅ | Per-client/request only |
Logging
| Option | Global Config | Per-Client | Per-Request | Notes |
|---|---|---|---|---|
logging | ✅ | ✅ | ✅ | Global: nested config |
Request Options
| Option | Global Config | Per-Client | Per-Request | Notes |
|---|---|---|---|---|
base_url | ❌ | ✅ | ❌ | Per-client only |
headers | ❌ | ✅ | ✅ | Merged across levels |
body | ❌ | ❌ | ✅ | Per-request only (POST/PUT), raw pass-through |
json | ❌ | ❌ | ✅ | Per-request only; encodes body as JSON |
form | ❌ | ❌ | ✅ | Per-request only; encodes body as form-urlencoded |
raw | ❌ | ❌ | ✅ | Per-request only; skips response decoding |
timeout | ❌ | ✅ | ✅ | Per-client/request only |
pool_timeout | ❌ | ✅ | ✅ | Finch adapter only; connection-checkout wait (ms) |
ssl_verify | ❌ | ✅ | ✅ | Per-request on Req only; Finch (default) uses pool-level TLS, Tesla configures it on the client |
proxy | ❌ | ✅ | ✅ | Per-request on Req only; Finch (default) uses pool-level proxy, Tesla configures it on the client |
block_private_ips | ✅ | ✅ | ✅ | SSRF guardrail; blocks private/loopback/link-local IP literals + localhost (default false) |
allowed_hosts | ✅ | ✅ | ✅ | SSRF guardrail; restricts requests to an explicit host allowlist (default: unset = all) |
Global Configuration
Set in config/config.exs, config/prod.exs, etc.
config :httpower,
# Adapter (optional - auto-detects if not specified)
adapter: HTTPower.Adapter.Finch, # or HTTPower.Adapter.Req / HTTPower.Adapter.Tesla
# Retry Configuration
max_retries: 3,
retry_safe: false,
base_delay: 1000,
max_delay: 30000,
jitter_factor: 0.2,
# Rate Limiting
rate_limit: [
enabled: false,
requests: 100,
per: :minute,
strategy: :wait,
max_wait_time: 5000
],
# Circuit Breaker
circuit_breaker: [
enabled: false,
failure_threshold: 5,
failure_threshold_percentage: nil,
window_size: 10,
timeout: 60_000,
half_open_requests: 1
],
# Logging
logging: [
enabled: true,
level: :info
],
# Sanitization (used by both telemetry redaction and the logger)
sanitization: [
sanitize_headers: [], # Additional headers to sanitize (adds to defaults)
sanitize_body_fields: [] # Additional body fields to sanitize (adds to defaults)
],
# Test Mode
test_mode: falseAdapter Configuration
adapter
Type:
module() | {module(), term()}- Default: Auto-detected (prefers Finch, then Req, then Tesla)
- Supported: Global config, per-client, per-request
- Description: HTTP adapter to use. Can be specified globally or overridden per-client/request.
Global Configuration (Simple):
# config/config.exs
config :httpower, adapter: HTTPower.Adapter.Req
# or
config :httpower, adapter: HTTPower.Adapter.TeslaPer-Client (Simple or with Pre-configured Client):
# Simple adapter module
client = HTTPower.new(
adapter: HTTPower.Adapter.Req,
base_url: "https://api.example.com"
)
# Adapter with pre-configured Tesla client
tesla_client = Tesla.client([
Tesla.Middleware.BaseURL.new("https://api.example.com"),
Tesla.Middleware.JSON
])
client = HTTPower.new(
adapter: {HTTPower.Adapter.Tesla, tesla_client}
)Per-Request Override:
# Override the adapter for a specific request
HTTPower.get(url, adapter: HTTPower.Adapter.Tesla)Retry Configuration
max_retries
- Type:
non_neg_integer() - Default:
3 - Description: Maximum number of retry attempts for failed requests.
- Example:
config :httpower, max_retries: 5
retry_safe
- Type:
boolean() - Default:
false - Description: Whether to retry on connection resets (
econnreset). Only enable for idempotent operations. - Example:
config :httpower, retry_safe: true
base_delay
- Type:
non_neg_integer()(milliseconds) - Default:
1000 - Description: Base delay for exponential backoff. Actual delay:
base_delay * 2^(attempt-1). - Example:
config :httpower, base_delay: 2000 # Start with 2 second delay
max_delay
- Type:
non_neg_integer()(milliseconds) - Default:
30000 - Description: Maximum delay cap for exponential backoff.
- Example:
config :httpower, max_delay: 60000 # Cap at 60 seconds
jitter_factor
- Type:
float()(0.0 to 1.0) - Default:
0.2 - Description: Randomization factor to prevent thundering herd. Delay multiplied by
(1 - jitter_factor * random()). - Example:
config :httpower, jitter_factor: 0.3 # 30% jitter
Retryable Conditions
HTTPower retries on:
- Status codes: 408, 429, 500, 502, 503, 504
- Errors:
:timeout,:closed,:econnrefused,:econnreset(ifretry_safe: true)
Rate Limiting Configuration
rate_limit.enabled
- Type:
boolean() - Default:
false - Description: Enable/disable rate limiting globally.
- Example:
config :httpower, rate_limit: [enabled: true]
rate_limit.requests
- Type:
pos_integer() - Default:
100 - Description: Maximum number of requests allowed per time window.
- Example:
config :httpower, rate_limit: [requests: 60]
rate_limit.per
Type:
:second | :minute | :hour- Default:
:minute - Description: Time window for rate limiting.
- Example:
config :httpower, rate_limit: [per: :second]
rate_limit.strategy
Type:
:wait | :error- Default:
:wait - Description: How to handle rate limit exceeded:
:wait- Block until tokens available (up tomax_wait_time):error- Return{:error, :too_many_requests}immediately
- Example:
config :httpower, rate_limit: [strategy: :error]
rate_limit.max_wait_time
- Type:
non_neg_integer()(milliseconds) - Default:
5000 - Description: Maximum time to wait for rate limit tokens when using
:waitstrategy. - Example:
config :httpower, rate_limit: [max_wait_time: 10000]
Per-Request Rate Limiting
HTTPower.get(url,
rate_limit: [requests: 10, per: :second],
rate_limit_key: "special_endpoint" # Custom bucket key
)Circuit Breaker Configuration
circuit_breaker.enabled
- Type:
boolean() - Default:
false - Description: Enable/disable circuit breaker globally.
- Example:
config :httpower, circuit_breaker: [enabled: true]
circuit_breaker.failure_threshold
- Type:
pos_integer() - Default:
5 - Description: Number of failures in sliding window before opening circuit.
- Example:
config :httpower, circuit_breaker: [failure_threshold: 3]
circuit_breaker.failure_threshold_percentage
- Type:
float()(0.0 to 100.0) ornil - Default:
nil - Description: Alternative to absolute threshold. Opens circuit when failure rate exceeds percentage. Requires
window_sizerequests minimum. - Example:
config :httpower, circuit_breaker: [ failure_threshold_percentage: 50.0, # Open at 50% failure rate window_size: 20 ]
circuit_breaker.window_size
- Type:
pos_integer() - Default:
10 - Description: Number of recent requests to track in sliding window.
- Example:
config :httpower, circuit_breaker: [window_size: 20]
circuit_breaker.timeout
- Type:
pos_integer()(milliseconds) - Default:
60000 - Description: How long circuit stays open before transitioning to half-open.
- Example:
config :httpower, circuit_breaker: [timeout: 30_000] # 30 seconds
circuit_breaker.half_open_requests
- Type:
pos_integer() - Default:
1 - Description: Number of test requests allowed in half-open state. All must succeed to close circuit.
- Example:
config :httpower, circuit_breaker: [half_open_requests: 3]
Per-Request Circuit Breaker
HTTPower.get(url,
circuit_breaker: [failure_threshold: 3, timeout: 30_000],
circuit_breaker_key: "payment_api" # Custom circuit key
)Manual Circuit Control
# Check state
HTTPower.Middleware.CircuitBreaker.get_state("my_api") # => :closed | :open | :half_open | nil
# Manually open
HTTPower.Middleware.CircuitBreaker.open_circuit("my_api")
# Manually close
HTTPower.Middleware.CircuitBreaker.close_circuit("my_api")
# Reset completely
HTTPower.Middleware.CircuitBreaker.reset_circuit("my_api")Logging Configuration
HTTPower uses a telemetry-based logging system that you opt into by attaching the logger.
Enabling Logging
# In your application.ex
def start(_type, _args) do
# Attach logger with options
HTTPower.Logger.attach(
level: :info,
log_headers: true,
log_body: true,
sanitize_headers: ["x-custom-token"],
sanitize_body_fields: ["secret_key"]
)
# ... rest of your supervision tree
endConfiguration Options
All options can be passed to HTTPower.Logger.attach/1 or configured via Application config (when using attach/0):
level
Type:
:debug | :info | :warning | :error- Default:
:info - Description: Log level for HTTP requests.
- Example:
HTTPower.Logger.attach(level: :debug) # Or in config config :httpower, :logging, level: :debug
log_headers
- Type:
boolean() - Default:
true - Description: Whether to include headers in logs.
- Example:
HTTPower.Logger.attach(log_headers: false)
log_body
- Type:
boolean() - Default:
true - Description: Whether to include request/response body in logs.
- Example:
HTTPower.Logger.attach(log_body: false)
sanitize_headers
- Type:
list(String.t()) - Default:
[](adds to built-in defaults) - Description: Additional header names to sanitize (case-insensitive). Additive - adds to defaults.
- Built-in defaults:
["authorization", "api-key", "x-api-key", "api_key", "apikey", "secret", "token", "x-auth-token", "x-csrf-token", "cookie", "set-cookie"](seeHTTPower.Sanitizer.default_sanitize_headers/0) - Example:
HTTPower.Logger.attach(sanitize_headers: ["x-custom-token"]) # Or in config config :httpower, :sanitization, sanitize_headers: ["x-custom-token"]
sanitize_body_fields
- Type:
list(String.t()) - Default:
[](adds to built-in defaults) - Description: Additional body field names to sanitize. Additive - adds to defaults.
- Built-in defaults:
["password", "passwd", "pwd", "secret", "api_key", "apikey", "token", "credit_card", "creditcard", "card_number", "cardnumber", "cvv", "cvv2", "cvc", "pin", "ssn", "social_security"](seeHTTPower.Sanitizer.default_sanitize_body_fields/0) - Example:
HTTPower.Logger.attach(sanitize_body_fields: ["tax_id", "secret"]) # Or in config config :httpower, :sanitization, sanitize_body_fields: ["tax_id", "secret"]
Disabling Logging
# Don't attach the logger
# HTTPower.Logger.attach() # Commented out
# Or detach programmatically
HTTPower.Logger.detach()Request Options
timeout
- Type:
pos_integer()(seconds) - Default:
60 - Description: Request timeout in seconds.
- Example:
HTTPower.get(url, timeout: 30)
headers
- Type:
map() - Default:
%{} - Description: HTTP headers for the request.
- Example:
HTTPower.get(url, headers: %{"authorization" => "Bearer token"})
body
Type:
String.t() | binary()- Default:
"" - Description: Raw request body for POST/PUT requests. No encoding is applied and no Content-Type header is set automatically. Use
json:orform:for automatic encoding. - Example:
HTTPower.post(url, body: "raw payload", headers: %{"content-type" => "text/plain"})
json
Type:
map() | list() | any encodable term- Default: not set
- Description: Encodes the value as JSON and sends it as the request body. Automatically sets
Content-Type: application/jsonandAccept: application/jsonheaders. Response bodies with a JSON Content-Type are automatically decoded byHTTPower.Codec. - Example:
HTTPower.post(url, json: %{name: "John", role: "admin"}) HTTPower.put(url, json: %{status: "active"})
form
Type:
map() | keyword()- Default: not set
- Description: Encodes the value as URL-encoded form data and sends it as the request body. Automatically sets
Content-Type: application/x-www-form-urlencoded. - Example:
HTTPower.post(url, form: %{username: "alice", password: "secret"})
raw
- Type:
boolean() - Default:
false - Description: When
true, skips automatic response body decoding. Thebodyfield in the response will always be a binary string, even if the server returns JSON. Useful when you need the raw bytes (e.g., binary downloads, custom decoding). - Example:
{:ok, response} = HTTPower.get(url, raw: true) # response.body is always a binary string Jason.decode!(response.body)
pool_timeout
- Type:
non_neg_integer()(milliseconds) - Default: Finch's default (5000)
- Adapter: Finch only — maximum time to wait when checking out a pooled connection.
- Example:
HTTPower.get(url, pool_timeout: 2000)
ssl_verify
- Type:
boolean() - Default:
true - Description: Enable SSL certificate verification.
- Adapter-specific: Honored per-request only by the Req adapter. The default Finch
adapter ignores this option per-request — TLS is configured at the pool level via
config :httpower, :finch_poolsand certificates are verified by default (Mint'sverify: :verify_peer). The Tesla adapter configures TLS on its client. - Example:
HTTPower.get(url, ssl_verify: false, adapter: HTTPower.Adapter.Req) # Not recommended for production
proxy
Type:
:system | nil | {scheme, address, port, opts}- Default:
:system - Description: Proxy configuration.
:system(the default) andnilboth mean a direct connection — there is no system-proxy auto-detection. An explicit proxy must be a Mint{scheme, address, port, opts}tuple. - Adapter-specific: Honored per-request only by the Req adapter; the Finch adapter
configures a proxy at the pool level (
config :httpower, :finch_pools), and Tesla configures it on its client. - Example:
HTTPower.get(url, proxy: {:http, "proxy.example.com", 8080, []}, adapter: HTTPower.Adapter.Req)
SSRF Guardrails
Two opt-in options harden HTTPower against Server-Side Request Forgery — coercing
the client into reaching internal services or cloud metadata endpoints. Both are
evaluated before any HTTP call (and before the request even reaches an adapter),
and both can be set globally (config :httpower), per-client, or per-request.
Literal checks only. Host matching does not resolve DNS, so a hostname that resolves to a private IP (e.g.
internal.corp→10.0.0.5) is not caught byblock_private_ips. When you need a strict egress boundary, combine it withallowed_hosts.
block_private_ips
- Type:
boolean() - Default:
false - Description: When
true, rejects requests whose host is an IP literal in a loopback (127.0.0.0/8,::1), private (10/8,172.16/12,192.168/16), link-local (169.254.0.0/16— including the169.254.169.254cloud-metadata address — and IPv6fe80::/10), unique-local (fc00::/7), or unspecified (0.0.0.0/8,::) range, as well as thelocalhosthostname (and any*.localhost). Blocked requests return{:error, %HTTPower.Error{reason: :blocked_private_ip}}. - Example:
# config/runtime.exs config :httpower, block_private_ips: true # or per request HTTPower.get(user_supplied_url, block_private_ips: true)
allowed_hosts
- Type:
[String.t()] - Default: unset (all hosts allowed)
- Description: When set to a non-empty list, only requests whose host matches
an entry (case-insensitive) are permitted; everything else returns
{:error, %HTTPower.Error{reason: :host_not_allowed}}. This is the strongest guardrail — an explicit egress allowlist — and is unaffected by the DNS caveat above, since it constrains the requested host directly. - Example:
HTTPower.get(url, allowed_hosts: ["api.stripe.com", "api.example.com"])
Per-Client Configuration
Create reusable clients with HTTPower.new():
client = HTTPower.new(
base_url: "https://api.example.com",
headers: %{"authorization" => "Bearer #{token}"},
timeout: 30,
max_retries: 5,
circuit_breaker: [failure_threshold: 3],
rate_limit: [requests: 100, per: :minute]
)
# Use the client
HTTPower.get(client, "/users")
HTTPower.post(client, "/users", body: data)Test Mode
test_mode
- Type:
boolean() - Default:
false - Description: When enabled, blocks all real HTTP requests unless they include a
:plugoption. - Example:
# In test_helper.exs Application.put_env(:httpower, :test_mode, true) # In tests Req.Test.stub(HTTPower, fn conn -> Req.Test.json(conn, %{"status" => "ok"}) end) HTTPower.get(url, plug: {Req.Test, HTTPower}) # Allowed HTTPower.get(url) # Blocked with {:error, :network_blocked}
Configuration Priority Examples
Example 1: Override global with per-client
# Global config
config :httpower, max_retries: 3
# Per-client override
client = HTTPower.new(max_retries: 5) # Uses 5, not 3Example 2: Override per-client with per-request
client = HTTPower.new(timeout: 30)
# This request uses 60 second timeout
HTTPower.get(client, "/slow", timeout: 60)
# This request uses client's 30 second timeout
HTTPower.get(client, "/fast")Example 3: Header merging
# Global config
config :httpower, headers: %{"user-agent" => "HTTPower/1.0"}
# Per-client config
client = HTTPower.new(headers: %{"authorization" => "Bearer token"})
# Per-request config
HTTPower.get(client, "/api",
headers: %{"x-request-id" => "123"}
)
# Final headers include all three:
# {
# "user-agent" => "HTTPower/1.0",
# "authorization" => "Bearer token",
# "x-request-id" => "123"
# }Environment-Specific Configuration
Development
# config/dev.exs
config :httpower,
logging: [enabled: true, level: :debug],
circuit_breaker: [enabled: false], # Disabled for easier debugging
rate_limit: [enabled: false]Test
# config/test.exs
config :httpower,
test_mode: true,
logging: [enabled: false],
circuit_breaker: [enabled: false],
rate_limit: [enabled: false]Production
# config/prod.exs
config :httpower,
max_retries: 3,
circuit_breaker: [
enabled: true,
failure_threshold: 5,
timeout: 60_000
],
rate_limit: [
enabled: true,
requests: 100,
per: :minute,
strategy: :wait
],
logging: [
enabled: true,
level: :info
],
sanitization: [
sanitize_headers: ["authorization", "api-key"],
sanitize_body_fields: ["password", "credit_card", "cvv"]
]Next Steps
- Read Production Deployment Guide for production setup
- See Migrating from Tesla or Migrating from Req
- Check
guides/examples/for runnable examples