we-promise/sure · warning · Provider::Simplefin::SimplefinError

rate_limited

rate_limited

Error message

SimpleFin rate limit exceeded. Please try again later.

What it means

Raised by Provider::Simplefin#get_accounts on HTTP 429, error_type :rate_limited. Important difference from the Redbark client: SimpleFin's with_retries only retries network exceptions (RETRYABLE_ERRORS) — the status-code case statement runs outside the retry block, so a 429 is NOT retried internally and reaches the caller on the first occurrence.

Source

Thrown at app/models/provider/simplefin.rb:97

    # Use retry logic with exponential backoff for transient network failures
    # Use self.class.get to inherit class-level SSL and timeout defaults
    response = with_retries("GET /accounts") do
      self.class.get(accounts_url)
    end

    case response.code
    when 200
      JSON.parse(response.body, symbolize_names: true)
    when 400
      Rails.logger.error "SimpleFin API: Bad request - #{response.body}"
      raise SimplefinError.new("Bad request to SimpleFin API: #{response.body}", :bad_request)
    when 403
      raise SimplefinError.new("Access URL is no longer valid", :access_forbidden)
    when 402
      raise SimplefinError.new("Payment required to access this account", :payment_required)
    when 429
      Rails.logger.warn "SimpleFin API: Rate limited - #{response.body}"
      raise SimplefinError.new("SimpleFin rate limit exceeded. Please try again later.", :rate_limited)
    when 500..599
      Rails.logger.error "SimpleFin API: Server error - Code: #{response.code}, Body: #{response.body}"
      raise SimplefinError.new("SimpleFin server error (#{response.code}). Please try again later.", :server_error)
    else
      Rails.logger.error "SimpleFin API: Unexpected response - Code: #{response.code}, Body: #{response.body}"
      raise SimplefinError.new("Failed to fetch accounts: #{response.code} #{response.message} - #{response.body}", :fetch_failed)
    end
  end

  def get_info(base_url)
    # Use self.class.get to inherit class-level SSL and timeout defaults
    response = self.class.get("#{base_url}/info")

    case response.code
    when 200
      response.body.strip.split("\n")
    else
      raise SimplefinError.new("Failed to get server info: #{response.code} #{response.message}", :info_failed)

View on GitHub (pinned to e69894adb9)

Solutions

  1. Implement caller-side backoff on :rate_limited — the library will not retry it for you
  2. Increase the interval between scheduled SimpleFin syncs
  3. Serialize syncs per bridge instead of hitting several accounts in parallel
  4. Check the response body in logs for the bridge's stated limit/window

Example fix

# before
client.get_accounts(access_url) # no retry on 429

# after
attempt = 0
begin
  client.get_accounts(access_url)
rescue Provider::Simplefin::SimplefinError => e
  raise unless e.error_type == :rate_limited
  raise if (attempt += 1) >= 3
  sleep(30 * attempt + rand(10))
  retry
end
Defensive patterns

Strategy: retry

Type guard

def simplefin_rate_limited?(error)
  error.is_a?(Provider::Simplefin::SimplefinError) && error.error_type == :rate_limited
end

Try / catch

attempt = 0
begin
  client.get_accounts(access_url)
rescue Provider::Simplefin::SimplefinError => e
  raise unless e.error_type == :rate_limited
  raise if (attempt += 1) >= 3
  sleep((2**attempt) + rand(2)) # caller must retry: the client does not retry 429
  retry
end

Prevention

When it happens

Trigger: Polling the same access URL too frequently, several accounts on one bridge syncing concurrently, or scheduled jobs bunching up against the bridge's rate limit.

Common situations: Autosync interval shorter than the bridge tolerates; multiple app instances (staging+prod) sharing one bridge; retry loops at the caller level amplifying request volume.

Related errors


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