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

  1. Verify the symbol resolves on Tiingo: run search_securities(symbol) first and use the returned normalized ticker
  2. Log the raw response body to identify which key Tiingo used for the error, then extend check_api_error! to map it
  3. 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

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


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