we-promise/sure · error · Provider::Eodhd::Error

Unexpected response format from search API

Error message

Unexpected response format from search API

What it means

Raised as Provider::Eodhd::Error when the EODHD search endpoint ('GET /api/search/<symbol>') returns a JSON body that is not a top-level Array. EODHD's search API contract is a bare JSON array of security objects; a Hash body means the API changed shape, returned an error envelope that check_api_error! did not recognize (its 'error' key was absent/blank), or an HTML/XML error page was parsed into an unexpected structure. This is a hard schema violation — the code refuses to guess and raises instead of returning partial data.

Source

Thrown at app/models/provider/eodhd.rb:131

  # ================================
  #           Securities
  # ================================

  def search_securities(symbol, country_code: nil, exchange_operating_mic: nil)
    with_provider_response do
      enforce_daily_limit!
      throttle_request

      response = client.get("#{base_url}/api/search/#{CGI.escape(symbol)}") do |req|
        req.params["api_token"] = api_key
        req.params["fmt"] = "json"
      end

      parsed = JSON.parse(response.body)
      check_api_error!(parsed)

      unless parsed.is_a?(Array)
        raise Error, "Unexpected response format from search API"
      end

      parsed.first(25).map do |security|
        eodhd_exchange = security.dig("Exchange")
        mic = EODHD_EXCHANGE_TO_MIC[eodhd_exchange] || eodhd_exchange
        country = EODHD_COUNTRY_TO_CODE[security.dig("Country")]
        code = security.dig("Code")
        currency = security.dig("Currency")

        # Cache the API-returned currency so fetch_security_prices can use it
        if currency.present? && mic.present?
          cache_key = "eodhd:currency:#{code.upcase}:#{mic}"
          Rails.cache.write(cache_key, currency, expires_in: 24.hours)
        end

        Security.new(
          symbol: code,
          name: security.dig("Name"),

View on GitHub (pinned to e69894adb9)

Solutions

  1. Reproduce manually: curl 'https://eodhd.com/api/search/AAPL?api_token=YOUR_TOKEN&fmt=json' and inspect the raw body shape
  2. Check the EODHD API changelog/status page for announced response-format changes
  3. If the body is an error envelope without an 'error' key, extend check_api_error! to recognize that key and raise a precise API error instead
  4. Verify the base_url ENV override (if any) points at the current EODHD API version
  5. Report the format change upstream if the body is valid but structurally different, and pin to the documented v1 shape until fixed

Example fix

# before: only a Hash with 'error' is treated as an API error
parsed = JSON.parse(response.body)
check_api_error!(parsed)
raise Error, "Unexpected response format from search API" unless parsed.is_a?(Array)

# after: surface the actual body so the failure is diagnosable
raise Error, "Unexpected response format from search API: #{parsed.class} #{parsed.to_s.truncate(200)}" unless parsed.is_a?(Array)
Defensive patterns

Strategy: try-catch

Type guard

# Ruby shape guard for EODHD search responses
module EodhdSearchGuard
  def self.valid?(parsed)
    parsed.is_a?(Array) && parsed.all? { |s| s.is_a?(Hash) && s["Code"].present? }
  end
end

parsed = JSON.parse(raw_body)
return fallback_search(parsed) if EodhdSearchGuard.valid?(parsed)

Try / catch

begin
  securities = provider.search_securities(query)
rescue Provider::Eodhd::Error => e
  if e.message.include?("Unexpected response format")
    Rails.logger.error("EODHD search shape changed: #{e.message}")
    [] # degrade gracefully — user sees empty results, app stays up
  else
    raise
  end
end

Prevention

When it happens

Trigger: Calling search_securities (symbol lookup / security search flow) with a valid API token where the response body parses as JSON but is an object (e.g. {"message": "..."}) rather than an array. Happens when EODHD changes its API version, returns an undocumented error envelope without an 'error' key, or a gateway (Cloudflare) interposes a JSON error object.

Common situations: EODHD deploying a breaking API change, free-tier accounts hitting an undocumented quota response, stale base URL ENV override pointing at an old API version, or intermediaries rewriting responses. Typically surfaces suddenly across all symbol searches for every EODHD user of the app.

Understand the failure class

Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.

Related errors


AI-assisted analysis of we-promise/sure@e69894adb9 (2026-08-21). Data as JSON: /api/errors/514351020941f6d6. Report an issue: GitHub.