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
- Wait and retry with backoff -- the detail usually states the window (per minute/per day)
- 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
- 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
- Keep the client-side hourly cap below the server-side plan limit so the local gate trips first
- Ensure only one deployment consumes a given Tiingo key
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.