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

  1. Raise ALPHA_VANTAGE_MAX_REQUESTS_PER_DAY if you have a paid key with a higher quota.
  2. Reduce call volume: cache prices, batch work, or move to a provider with a higher limit.
  3. Check provider.usage (used vs limit) before starting a batch and skip when utilization is high.
  4. 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

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


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