we-promise/sure · warning · Provider::AlphaVantage::RateLimitError
Alpha Vantage daily request limit reached (#{max_requests_pe
Error message
Alpha Vantage daily request limit reached (#{max_requests_per_day} per day) What it means
Provider::AlphaVantage#throttle_request raises RateLimitError after a Redis(Rails.cache)-backed per-day counter (key alpha_vantage:daily:YYYY-MM-DD, atomic increment-then-check) exceeds max_requests_per_day — default 25 (free tier), overridable via ALPHA_VANTAGE_MAX_REQUESTS_PER_DAY. This is a local quota guard on top of the interval throttle; it fires before the HTTP call is made, so no request is wasted.
Source
Thrown at app/models/provider/alpha_vantage.rb:251
faraday.request :json
faraday.response :raise_error
faraday.params["apikey"] = api_key
end
end
# Adds daily request counter on top of the interval throttle from RateLimitable.
def throttle_request
super
# Global per-day request counter via cache (Redis).
# Atomic increment-then-check avoids the TOCTOU of read-check-increment.
day_key = "alpha_vantage:daily:#{Date.current}"
new_count = Rails.cache.increment(day_key, 1, expires_in: 24.hours).to_i
if new_count > max_requests_per_day
Rails.logger.warn("AlphaVantage: daily request limit reached (#{new_count}/#{max_requests_per_day})")
raise RateLimitError, "Alpha Vantage daily request limit reached (#{max_requests_per_day} per day)"
end
end
def max_requests_per_day
ENV.fetch("ALPHA_VANTAGE_MAX_REQUESTS_PER_DAY", MAX_REQUESTS_PER_DAY).to_i
end
# Converts a symbol + MIC code to Alpha Vantage's ticker format
def to_av_symbol(symbol, exchange_operating_mic)
return symbol if exchange_operating_mic.blank?
suffix = MIC_TO_AV_SUFFIX[exchange_operating_mic]
return symbol if suffix.nil?
return symbol if suffix.empty?
# Avoid double-suffixing if the symbol already has the correct suffix
return symbol if symbol.end_with?(suffix)
View on GitHub (pinned to e69894adb9)
Solutions
- Raise ALPHA_VANTAGE_MAX_REQUESTS_PER_DAY if you have a paid key with a higher quota.
- Reduce call volume: cache prices, batch work, or move to a provider with a higher limit.
- Check provider.usage (used vs limit) before starting a batch and skip when utilization is high.
- Rescue Provider::AlphaVantage::RateLimitError and reschedule the job for the next day (counter key rolls at midnight UTC per Date.current).
Example fix
# before provider.fetch_security_prices(symbol: s, start_date: from, end_date: to) # after usage = provider.usage if usage.utilization < 90 provider.fetch_security_prices(symbol: s, start_date: from, end_date: to) else PriceSyncJob.set(wait_until: Date.tomorrow.midnight + 5.minutes).perform_later(s) end
Defensive patterns
Strategy: retry
Validate before calling
usage = provider.usage usage.used < usage.limit # cheap pre-check; counter key is alpha_vantage:daily:<date>
Try / catch
begin provider.fetch_security_prices(symbol: s, start_date: a, end_date: b) rescue Provider::AlphaVantage::RateLimitError PriceSyncJob.set(wait_until: Date.tomorrow.midnight).perform_later(s) # counter rolls daily end
Prevention
- Check provider.usage before starting batches; stop before exhausting the quota.
- Cache aggressively — every AV call counts, even failed lookups.
- Tune ALPHA_VANTAGE_MAX_REQUESTS_PER_DAY to the actual plan quota.
When it happens
Trigger: More than max_requests_per_day provider calls in a UTC day across the whole app (the counter is global, not per-user): bulk price refreshes, security searches for many tickers, retry storms.
Common situations: Scheduled jobs syncing many securities; multiple families each triggering refreshes; a shared cache/Redis reset changing counts; default 25/day exhausted by mid-morning.
Related errors
- parsed["Note"]
- No data returned from search endpoint
- No metadata returned for symbol #{av_symbol}
- No prices found for security #{symbol} on date #{date}
- No time series data returned for symbol #{av_symbol}
AI-assisted analysis of we-promise/sure@e69894adb9 (2026-08-21).
Data as JSON: /api/errors/e2f61c05afe28053.
Report an issue: GitHub.