we-promise/sure · error · Provider::Tiingo::InvalidSecurityPriceError
API error: #{error_message}
Error message
API error: #{error_message} What it means
fetch_security_prices GETs /tiingo/daily/<symbol>/prices and expects a JSON array of candles. check_api_error! runs first and only raises for Hash payloads with a 'detail' key, so reaching the unless Array branch means the payload is a Hash WITHOUT 'detail' -- the code then reads parsed['detail'] (nil here) and falls back to error_message 'Unexpected response format', raising InvalidSecurityPriceError 'API error: Unexpected response format'. This is Tiingo returning an object-shaped error this client does not model, most notably the 404-style response for a symbol unknown to the daily endpoint.
Source
Thrown at app/models/provider/tiingo.rb:184
end
end
def fetch_security_prices(symbol:, exchange_operating_mic: nil, start_date:, end_date:)
with_provider_response do
throttle_request
track_symbol(symbol)
response = client.get("#{base_url}/tiingo/daily/#{CGI.escape(symbol)}/prices") do |req|
req.params["startDate"] = start_date.to_s
req.params["endDate"] = end_date.to_s
end
parsed = JSON.parse(response.body)
check_api_error!(parsed)
unless parsed.is_a?(Array)
error_message = parsed.is_a?(Hash) ? (parsed["detail"] || "Unexpected response format") : "Unexpected response format"
raise InvalidSecurityPriceError, "API error: #{error_message}"
end
# Prefer cached currency from search results to avoid a second API call
cache_key = "tiingo:currency:#{symbol.upcase}"
currency = Rails.cache.read(cache_key) || fetch_currency_for_symbol(symbol)
parsed.map do |resp|
price = resp["close"]
date = resp["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,
date: Date.parse(date),View on GitHub (pinned to e69894adb9)
Solutions
- Verify the symbol resolves on Tiingo: run search_securities(symbol) first and use the returned normalized ticker
- Log the raw response body to identify which key Tiingo used for the error, then extend check_api_error! to map it
- For non-equity asset classes, use the appropriate Tiingo endpoint/provider instead of /tiingo/daily
Example fix
# before
prices = provider.fetch_security_prices(symbol: raw_symbol, start_date: from, end_date: to)
# after
matches = provider.search_securities(raw_symbol)
best = matches.data.find { |s| s.symbol == raw_symbol.upcase } or raise UnknownSecurityError, raw_symbol
prices = provider.fetch_security_prices(symbol: best.symbol, exchange_operating_mic: best.exchange_operating_mic, start_date: from, end_date: to) Defensive patterns
Strategy: validation
Validate before calling
# Resolve/normalize the symbol through search before pricing match = provider.search_securities(raw).data.first or raise UnknownSymbolError, raw symbol = match.symbol
Type guard
def tiingo_daily_symbol?(search_result) search_result.respond_to?(:symbol) && search_result.exchange_operating_mic.present? end
Try / catch
begin
prices = provider.fetch_security_prices(symbol:, start_date:, end_date:)
rescue Provider::Tiingo::InvalidSecurityPriceError => e
Rails.logger.warn("Tiingo price lookup failed for #{symbol}: #{e.message}")
prices = Provider::Response.new(data: [], error: nil)
end Prevention
- Always route user-entered symbols through search_securities before the prices endpoint
- Strip exchange suffixes or map them to Tiingo's expected ticker form
When it happens
Trigger: Calling fetch_security_prices with a ticker that has no Tiingo daily series (misspelled symbol, delisted, mutual fund/forex/crypto ticker on the equities endpoint, or an exchange-suffixed form the provider doesn't strip); or any Tiingo error object lacking the 'detail' field (quota/account notices phrased differently).
Common situations: User-typed or imported symbols never validated against search_securities first; symbols with exchange suffixes (.TO, .L) passed verbatim; upstream A/B changes to Tiingo's error payload; stale symbols after ticker changes/renames.
Related errors
- Unexpected response format from search endpoint
- No prices found for security #{symbol} on date #{date}
- Could not save that passkey or security key. Please try agai
- parse_error
- bad_response
AI-assisted analysis of we-promise/sure@e69894adb9 (2026-08-21).
Data as JSON: /api/errors/af16089455aa6cf4.
Report an issue: GitHub.