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
- 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'
- 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
- Validate the api_token against the user/details endpoint to rule out auth issues
- Check EODHD status/changelog for response-format changes; pin or adapt the parser accordingly
- 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
- Distinguish shape errors ('Unexpected response format') from empty-data errors in handling and alerting
- Include a body excerpt in raised shape errors to make contract drift diagnosable from logs
- Contract-test the EOD endpoint shape on a staging schedule
- Keep the base URL pinned; verify after any EODHD account/plan migration
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
- Unexpected response format from search API
- Unexpected Frankfurter response shape
- Malformed Kraken API response
- No prices found for security #{symbol} on date #{date}
- Unexpected Frankfurter response shape (expected an array)
AI-assisted analysis of we-promise/sure@e69894adb9 (2026-08-21).
Data as JSON: /api/errors/0daee37b781368d2.
Report an issue: GitHub.