jnunemaker/httparty · error · ArgumentError

Headers must be an object which responds to #to_hash

Error message

Headers must be an object which responds to #to_hash

What it means

The class-level DSL method `headers` raises ArgumentError when given a value that does not respond to #to_hash. headers(h) merges the argument into default_options[:headers] for every request, so it must be a Hash or a Hash-like object; the guard fires at class-definition time, before any network traffic happens.

Source

Thrown at lib/httparty.rb:243

    # The output stream is passed on to Net::HTTP#set_debug_output.
    #
    #   class Foo
    #     include HTTParty
    #     debug_output $stderr
    #   end
    def debug_output(stream = $stderr)
      default_options[:debug_output] = stream
    end

    # Allows setting HTTP headers to be used for each request.
    #
    #   class Foo
    #     include HTTParty
    #     headers 'Accept' => 'text/html'
    #   end
    def headers(h = nil)
      if h
        raise ArgumentError, 'Headers must be an object which responds to #to_hash' unless h.respond_to?(:to_hash)
        default_options[:headers] ||= {}
        default_options[:headers].merge!(h.to_hash)
      else
        default_options[:headers] || {}
      end
    end

    def cookies(h = {})
      raise ArgumentError, 'Cookies must be an object which responds to #to_hash' unless h.respond_to?(:to_hash)
      default_cookies.add_cookies(h)
    end

    # Proceed to the location header when an HTTP response dictates a redirect.
    # Redirects are always followed by default.
    #
    # @example
    #   class Foo
    #     include HTTParty

View on GitHub (pinned to 8f4a09e343)

Solutions

  1. Pass a Hash with header names as keys: `headers 'Accept' => 'text/html'`.
  2. Parse external header strings before the call: `headers Hash[raw.split(': ').map ...]` or store config as YAML/JSON and parse it to a Hash.
  3. For one-off headers, use the per-request option instead: `Foo.get(url, headers: { 'Accept' => 'text/html' })`.

Example fix

# before
headers 'Accept: application/json'   # String -> ArgumentError

# after
headers 'Accept' => 'application/json'
Defensive patterns

Strategy: validation

Validate before calling

raise ArgumentError, 'headers config must be a Hash' unless h.respond_to?(:to_hash)
headers h.to_hash

Type guard

hash_like = ->(v) { v.respond_to?(:to_hash) }

Try / catch

begin
  headers h
rescue ArgumentError
  raise ConfigError, 'headers must be configured as a Hash, e.g. { "Accept" => "application/json" }'
end

Prevention

When it happens

Trigger: `headers 'Accept: text/html'` (a raw header string instead of a Hash), `headers nil` passed explicitly (h is truthy-checked, but a non-hash truthy value raises), `headers JSON.generate(...)`, or `headers [[:accept, 'text/html']]` inside an HTTParty class.

Common situations: Copy-pasting a curl -H 'X: y' style string into the DSL, feeding headers read from a config file or ENV variable that arrives as a String, and mixing up this setter with the request-level `get url, headers: {...}` option.

Understand the failure class

Background: Invalid argument type errors: "must be of type string", "expected X, got Y", and ERR_INVALID_ARG_TYPE explained — this error's family across 15 libraries.

Related errors


AI-assisted analysis of jnunemaker/httparty@8f4a09e343 (2026-08-21). Data as JSON: /api/errors/89ead00fbcdea5a6. Report an issue: GitHub.