we-promise/sure · error · Provider::Eodhd::RateLimitError
EODHD daily rate limit of #{max_requests_per_day} requests e
Error message
EODHD daily rate limit of #{max_requests_per_day} requests exhausted What it means
Raised as Provider::Eodhd::RateLimitError when the shared daily counter in Rails.cache ('eodhd:daily:<Date.current>') exceeds max_requests_per_day (default from MAX_REQUESTS_PER_DAY, overridable via EODHD_MAX_REQUESTS_PER_DAY). The counter is incremented atomically before each request (increment-then-check), so concurrent workers cannot undercount. Once the cap is hit, every further EODHD call that day raises immediately without touching the network.
Source
Thrown at app/models/provider/eodhd.rb:291
elsif exchange_operating_mic.present?
"#{symbol}.#{exchange_operating_mic}"
else
"#{symbol}.US"
end
end
# Cache key for tracking daily API usage
def daily_cache_key
"eodhd:daily:#{Date.current}"
end
# Enforces the daily rate limit. Raises RateLimitError if the limit is exhausted.
# Uses atomic increment-then-check to avoid TOCTOU races between concurrent workers.
def enforce_daily_limit!
new_count = Rails.cache.increment(daily_cache_key, 1, expires_in: 24.hours).to_i
if new_count > max_requests_per_day
raise RateLimitError, "EODHD daily rate limit of #{max_requests_per_day} requests exhausted"
end
end
# throttle_request and min_request_interval provided by RateLimitable
def max_requests_per_day
ENV.fetch("EODHD_MAX_REQUESTS_PER_DAY", MAX_REQUESTS_PER_DAY).to_i
end
def check_api_error!(parsed)
return unless parsed.is_a?(Hash) && parsed["error"].present?
raise Error, "API error: #{parsed["error"]}"
end
end
View on GitHub (pinned to e69894adb9)
Solutions
- Wait for the UTC day to roll over — the cache key is date-scoped ('eodhd:daily:<date>') and resets naturally
- Raise the budget if your plan allows: set EODHD_MAX_REQUESTS_PER_DAY in the environment to your plan's real quota
- Check for runaway callers: DebugLogEntry / logs showing unexpected EODHD request volume (each failed retry still increments)
- Ensure Rails.cache is a shared store (Redis/Memcached) in multi-process deployments so all workers see one counter
- Batch or schedule price backfills across days, and cache per-symbol/day results so repeated valuations do not re-query EODHD
Example fix
# before: every valuation day re-fetches all prices, exhausting the daily cap
holdings.each { |h| provider.fetch_security_price(symbol: h.symbol, date: Date.current) }
# after: cache results per symbol/date so the daily budget is spent once
holdings.each do |h|
key = "price:#{h.symbol}:#{Date.current}"
price = Rails.cache.fetch(key, expires_in: 12.hours) do
provider.fetch_security_price(symbol: h.symbol, date: Date.current)
end
end Defensive patterns
Strategy: retry
Validate before calling
# Check remaining budget before starting an expensive backfill
REMAINING = provider.max_requests_per_day - Rails.cache.read("eodhd:daily:#{Date.current}").to_i
BUDGET_NEEDED = symbols.size # one request per symbol window
if REMAINING < BUDGET_NEEDED
ScheduleBackfillJob.set(wait_until: Date.tomorrow.midnight + 5.minutes).perform_later(...)
return
end Try / catch
begin provider.fetch_security_prices(...) rescue Provider::Eodhd::RateLimitError # daily cap is calendar-scoped: reschedule for after UTC midnight, do not retry now SyncJob.set(wait_until: Date.tomorrow.midnight + 5.minutes).perform_later(job.id) end
Prevention
- Set EODHD_MAX_REQUESTS_PER_DAY to your plan's true quota, with headroom
- Use a shared cache store (Redis) in multi-process deployments so the counter is global
- Cache price results per symbol/date to avoid re-spending budget on repeated valuations
- Pre-check remaining budget before launching large backfills and defer them to the next day
When it happens
Trigger: Any EODHD API call (search, fetch_security_prices, fetch_security_price) after the day's counter has already reached the cap — e.g. a large multi-security backfill issuing thousands of EOD requests, multiple Sidekiq workers syncing in parallel, or repeated failed runs each burning increments. The counter is cache-global, not per-account, so all EODHD usage shares the budget.
Common situations: Initial import of a portfolio with many securities, retries of a failing sync each consuming requests, a misconfigured cache store (e.g. memory_store per-process) that under- or over-counts, EODHD_MAX_REQUESTS_PER_DAY left at the free-tier default while the app runs paid-tier volume, and month-end valuation jobs that price hundreds of holdings on one day.
Related errors
- API error: #{parsed["error"]}
- rate_limited
- rate_limited
- Alpha Vantage daily request limit reached (#{max_requests_pe
- parsed["Information"]
AI-assisted analysis of we-promise/sure@e69894adb9 (2026-08-21).
Data as JSON: /api/errors/d13219b8f3778a38.
Report an issue: GitHub.