we-promise/sure · warning · Provider::Tiingo::RateLimitError

detail

Error message

detail

What it means

check_api_error! inspects every parsed Tiingo response; when the payload is a Hash whose 'detail' string mentions 'rate limit' or 'too many' (case-insensitive), it raises RateLimitError with that detail verbatim. Unlike the client-side gates (hourly counter, monthly symbol budget), this is the SERVER telling you the key/plan limit was hit -- the message you see is Tiingo's own text (e.g. 'You have exceeded the maximum number of requests per day' or 'Too many requests').

Source

Thrown at app/models/provider/tiingo.rb:339

    # and a CA entry). The /tiingo/daily price endpoints this resolves
    # currency for are US-centric (also confirmed live: a Canadian ticker
    # like VFV has search metadata but no daily price history at all), so
    # when a US entry exists for the ticker, that's the one actually backing
    # the price data. Fall back to the first match otherwise.
    def best_match_for_ticker(results, ticker)
      return nil if ticker.blank?

      matches = results.select { |s| s["ticker"]&.upcase == ticker.upcase }
      matches.find { |s| s["countryCode"] == "US" } || matches.first
    end

    def check_api_error!(parsed)
      return unless parsed.is_a?(Hash) && parsed["detail"].present?

      detail = parsed["detail"]

      if detail.downcase.include?("rate limit") || detail.downcase.include?("too many")
        raise RateLimitError, detail
      end

      raise Error, "API error: #{detail}"
    end
end

View on GitHub (pinned to e69894adb9)

Solutions

  1. Wait and retry with backoff -- the detail usually states the window (per minute/per day)
  2. Check the key's actual quota on tiingo.com and align client caps: lower TIINGO_MAX_REQUESTS_PER_HOUR so you stop before the server limit
  3. Ensure no other deployment/tool shares this API key; if it does, split keys or add an external limiter

Example fix

# before
begin
  provider.fetch_security_prices(symbol: sym, start_date: from, end_date: to)
rescue Provider::Tiingo::RateLimitError
  raise
end

# after
begin
  provider.fetch_security_prices(symbol: sym, start_date: from, end_date: to)
rescue Provider::Tiingo::RateLimitError => e
  SyncRetryJob.set(wait: 15.minutes).perform_later(sym, from, to)
  Rails.logger.warn("Tiingo rate limited: #{e.message}")
end
Defensive patterns

Strategy: retry

Type guard

def tiingo_rate_limited?(err)
  err.is_a?(Provider::Tiingo::RateLimitError)
end

Try / catch

begin
  provider.fetch_security_prices(...)
rescue Provider::Tiingo::RateLimitError => e
  SyncRetryJob.set(wait: backoff_for(e.message)).perform_later(...) # server-side limit: wait it out
end

Prevention

When it happens

Trigger: Any Tiingo request whose response body is {'detail': '...rate limit...'} or '...too many...': daily request cap on the free key, burst throttling, or multiple app instances hammering the same key from outside this app's own counters (e.g. another deployment sharing the key).

Common situations: Self-hosted instance sharing a Tiingo API key with another app whose usage is invisible to this app's Redis counters; free-tier 50 requests/day-style limits on some endpoints; upstream tightening throttles while TIINGO_MAX_REQUESTS_PER_HOUR stays high.

Related errors


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