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

  1. 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).
  2. Page transactions with the largest limit Mercury allows instead of many small pages, reducing request count.
  3. Serialize per-token syncs (a per-token lock or single worker lane) so concurrent jobs cannot burst.
  4. 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

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


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