we-promise/sure · warning · Provider::Mercury::MercuryError
rate_limited
rate_limited
Error message
Rate limit exceeded. Please try again later.
What it means
Raised by Provider::Mercury#handle_response when Mercury returns HTTP 429 — you exceeded Mercury's API rate limit. Mercury enforces per-token request quotas (documented on the order of hundreds of requests per minute), and bursts such as paginating transactions across many accounts can trip it.
Source
Thrown at app/models/provider/mercury.rb:126
end
def handle_response(response)
case response.code
when 200
JSON.parse(response.body, symbolize_names: true)
when 400
Rails.logger.error "Mercury API: Bad request - #{response.body}"
raise MercuryError.new("Bad request to Mercury API: #{response.body}", :bad_request)
when 401
# Parse the error response for more specific messages
error_message = parse_error_message(response.body)
raise MercuryError.new(error_message, :unauthorized)
when 403
raise MercuryError.new("Access forbidden - check your API token permissions", :access_forbidden)
when 404
raise MercuryError.new("Resource not found", :not_found)
when 429
raise MercuryError.new("Rate limit exceeded. Please try again later.", :rate_limited)
else
Rails.logger.error "Mercury API: Unexpected response - Code: #{response.code}, Body: #{response.body}"
raise MercuryError.new("Failed to fetch data: #{response.code} #{response.message} - #{response.body}", :fetch_failed)
end
end
def parse_error_message(body)
parsed = JSON.parse(body, symbolize_names: true)
errors = parsed[:errors] || {}
case errors[:errorCode]
when "ipNotWhitelisted"
ip = errors[:ip] || "unknown"
"IP address not whitelisted (#{ip}). Add your IP to the API token's whitelist in Mercury dashboard."
when "noTokenInDBButMaybeMalformed"
"Invalid token format. Make sure to include the 'secret-token:' prefix."
else
errors[:message] || "Invalid API token"View on GitHub (pinned to e69894adb9)
Solutions
- Wait and retry with backoff — Mercury does not surface a Retry-After through this client, so start with tens of seconds and scale (e.g. 30s, 60s, 120s).
- Page transactions with the largest limit Mercury allows instead of many small pages, reducing request count.
- Serialize per-token syncs (a per-token lock or single worker lane) so concurrent jobs cannot burst.
- Add spacing between accounts in a multi-account sync loop (sleep or a rate limiter like the MIN_REQUEST_INTERVAL pattern used by other providers here).
Example fix
# before
loop do
page = provider.get_account_transactions(id, offset: off, limit: 10)
break if page[:transactions].empty?
off += 10
end
# after
retry_on_rate_limit = ->(attempt) { sleep(30 * (2**attempt)) }
attempt = 0
begin
loop do
page = provider.get_account_transactions(id, offset: off, limit: 100)
break if page[:transactions].empty?
off += 100
end
rescue Provider::Mercury::MercuryError => e
raise if e.error_type != :rate_limited || (attempt += 1) > 3
retry_on_rate_limit.call(attempt)
retry
end Defensive patterns
Strategy: retry
Try / catch
attempts = 0 begin provider.get_accounts rescue Provider::Mercury::MercuryError => e raise if e.error_type != :rate_limited || (attempts += 1) >= 4 sleep(30 * (2 ** (attempts - 1))) retry end
Prevention
- Page with the largest limit Mercury allows rather than many small requests.
- Serialize syncs that share a token and leave a small gap between per-account calls.
- Budget the expected request count per sync and stay under Mercury's per-minute quota.
When it happens
Trigger: Tight loops calling get_account_transactions with small limit/offset pages for one account; syncing many accounts concurrently with no throttle; a background job plus a manual sync running at the same time with the same token.
Common situations: Backfill jobs paginating transactions page-by-page with limit=10; parallel Sidekiq workers each hitting Mercury; retry storms after a transient 5xx that re-issue requests immediately; adding a new household with many Mercury accounts and syncing them all at once.
Related errors
- rate_limited
- Rate limit exceeded. Please try again later.
- rate_limited
- Rate limit exceeded
- Rate limit exceeded
AI-assisted analysis of we-promise/sure@e69894adb9 (2026-08-21).
Data as JSON: /api/errors/629d3f655e47b1ff.
Report an issue: GitHub.