Configuration Reference

Copy Markdown View Source

Complete reference for all HTTPower configuration options.

Configuration Levels

HTTPower supports three levels of configuration with clear precedence:

  1. Per-Request (highest priority) - Options passed to individual requests
  2. Per-Client (medium priority) - Options in HTTPower.new()
  3. 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

OptionGlobal ConfigPer-ClientPer-RequestNotes
adapterGlobal: config :httpower, adapter:

Testing

OptionGlobal ConfigPer-ClientPer-RequestNotes
test_modeGlobal only

Retry Configuration

OptionGlobal ConfigPer-ClientPer-RequestNotes
max_retries
retry_safe
base_delay
max_delay
jitter_factor

Rate Limiting

OptionGlobal ConfigPer-ClientPer-RequestNotes
rate_limitGlobal: nested config
rate_limit_keyPer-client/request only

Circuit Breaker

OptionGlobal ConfigPer-ClientPer-RequestNotes
circuit_breakerGlobal: nested config
circuit_breaker_keyPer-client/request only

Logging

OptionGlobal ConfigPer-ClientPer-RequestNotes
loggingGlobal: nested config

Request Options

OptionGlobal ConfigPer-ClientPer-RequestNotes
base_urlPer-client only
headersMerged across levels
bodyPer-request only (POST/PUT), raw pass-through
jsonPer-request only; encodes body as JSON
formPer-request only; encodes body as form-urlencoded
rawPer-request only; skips response decoding
timeoutPer-client/request only
pool_timeoutFinch adapter only; connection-checkout wait (ms)
ssl_verifyPer-request on Req only; Finch (default) uses pool-level TLS, Tesla configures it on the client
proxyPer-request on Req only; Finch (default) uses pool-level proxy, Tesla configures it on the client
block_private_ipsSSRF guardrail; blocks private/loopback/link-local IP literals + localhost (default false)
allowed_hostsSSRF 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: false

Adapter 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.Tesla

Per-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 (if retry_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 to max_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 :wait strategy.
  • 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) or nil
  • Default: nil
  • Description: Alternative to absolute threshold. Opens circuit when failure rate exceeds percentage. Requires window_size requests 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
end

Configuration 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"] (see HTTPower.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"] (see HTTPower.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: or form: 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/json and Accept: application/json headers. Response bodies with a JSON Content-Type are automatically decoded by HTTPower.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. The body field 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_pools and certificates are verified by default (Mint's verify: :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) and nil both 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.corp10.0.0.5) is not caught by block_private_ips. When you need a strict egress boundary, combine it with allowed_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 the 169.254.169.254 cloud-metadata address — and IPv6 fe80::/10), unique-local (fc00::/7), or unspecified (0.0.0.0/8, ::) range, as well as the localhost hostname (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 :plug option.
  • 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 3

Example 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