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
- Reproduce manually: curl 'https://eodhd.com/api/search/AAPL?api_token=YOUR_TOKEN&fmt=json' and inspect the raw body shape
- Check the EODHD API changelog/status page for announced response-format changes
- 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
- Verify the base_url ENV override (if any) points at the current EODHD API version
- 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
- Pin the EODHD base URL to a specific API version and review changelogs before upgrading
- Log raw (truncated) bodies on shape violations so contract drift is diagnosed in minutes
- Wrap search flows so a malformed response degrades to empty results instead of a 500
- Contract-test the live API shape in a staging ping job to catch drift before users
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
- Unexpected response format from EOD API
- Unexpected Frankfurter response shape
- Malformed Kraken API response
- No prices found for security #{symbol} on date #{date}
- EODHD daily rate limit of #{max_requests_per_day} requests e
AI-assisted analysis of we-promise/sure@e69894adb9 (2026-08-21).
Data as JSON: /api/errors/514351020941f6d6.
Report an issue: GitHub.