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

Unexpected response format from EOD API

Error message

Unexpected response format from EOD API

What it means

Raised as Provider::Eodhd::InvalidSecurityPriceError when the EODHD end-of-day endpoint ('GET /api/eod/<ticker>') returns a JSON body that is not a top-level Array. The EOD contract is a bare array of {date, open, high, low, close, volume} rows; a non-array body means the API returned an unrecognized envelope (no 'error' key, so check_api_error! passed) or the response was rewritten by a gateway. The guard fires before any row mapping, so no partial prices are returned.

Source

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

  def fetch_security_prices(symbol:, exchange_operating_mic: nil, start_date:, end_date:)
    with_provider_response do
      enforce_daily_limit!
      throttle_request

      ticker = eodhd_symbol(symbol, exchange_operating_mic)

      response = client.get("#{base_url}/api/eod/#{CGI.escape(ticker)}") do |req|
        req.params["api_token"] = api_key
        req.params["fmt"] = "json"
        req.params["from"] = start_date.to_s
        req.params["to"] = end_date.to_s
      end

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

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

      # Prefer cached currency from search results; fall back to hardcoded map
      cache_key = "eodhd:currency:#{symbol.upcase}:#{exchange_operating_mic}"
      eodhd_exchange = MIC_TO_EODHD_EXCHANGE[exchange_operating_mic]
      currency = Rails.cache.read(cache_key) || EXCHANGE_CURRENCY[eodhd_exchange]

      parsed.map do |resp|
        price = resp.dig("close")
        date = resp.dig("date")

        if price.nil? || price.to_f <= 0
          Rails.logger.warn("#{self.class.name} returned invalid price data for security #{symbol} on: #{date}.  Price data: #{price.inspect}")
          next
        end

        Price.new(
          symbol: symbol,

View on GitHub (pinned to e69894adb9)

Solutions

  1. curl the endpoint with the same params and token to see the raw body: curl 'https://eodhd.com/api/eod/AAPL.US?api_token=TOKEN&fmt=json&from=2025-01-01&to=2025-01-02'
  2. If the body is an error envelope with a different key, teach check_api_error! to detect that key so users get a real API error message
  3. Validate the api_token against the user/details endpoint to rule out auth issues
  4. Check EODHD status/changelog for response-format changes; pin or adapt the parser accordingly
  5. Include a truncated body sample in the raised message to speed up diagnosis

Example fix

# before: opaque class-level failure
raise InvalidSecurityPriceError, "Unexpected response format from EOD API" unless parsed.is_a?(Array)

# after: include what was actually received
unless parsed.is_a?(Array)
  raise InvalidSecurityPriceError, "Unexpected response format from EOD API (got #{parsed.class}): #{parsed.to_s.truncate(200)}"
end
Defensive patterns

Strategy: try-catch

Type guard

# Ruby shape guard for EODHD EOD rows
def valid_eod_payload?(parsed)
  parsed.is_a?(Array) && parsed.all? do |row|
    row.is_a?(Hash) && row["date"].present? && row["close"].present?
  end
end

Try / catch

begin
  prices = provider.fetch_security_prices(symbol: sym, start_date: from, end_date: to)
rescue Provider::Eodhd::InvalidSecurityPriceError => e
  if e.message.include?("Unexpected response format")
    notify_ops("EODHD EOD response shape drift: #{e.message}")
  end
  raise
end

Prevention

When it happens

Trigger: Calling fetch_security_prices / fetch_security_price when the parsed body is a Hash or String instead of an Array — e.g. EODHD changes response format, an auth/error envelope is returned without an 'error' key, or the api_token is invalid in a way that yields a JSON object message rather than the standard error shape.

Common situations: EODHD API version changes breaking the v1 array contract, expired or suspended API tokens producing non-standard error bodies, third-party base_url mirrors with different envelopes, and gateway-injected JSON error objects. Affects every price fetch app-wide when it happens.

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/0daee37b781368d2. Report an issue: GitHub.