we-promise/sure · warning · EnableBankingError

rate_limited

rate_limited

Error message

Rate limit exceeded. Please try again later.

What it means

Raised when Enable Banking returns HTTP 429 - the application exceeded its rate limit for the API. The JWT (kid = application_id) identifies the caller, so limits are enforced per application across every environment that shares that application_id. No Retry-After is parsed into the error; backoff is the caller's responsibility.

Source

Thrown at app/models/provider/enable_banking.rb:298

        parse_response_body(response)
      when 204
        {}
      when 400
        response_data = parse_error_response_body(response)
        raise EnableBankingError.new("Bad request to Enable Banking API: #{response.body}", :bad_request, response_data: response_data)
      when 401
        raise EnableBankingError.new("Invalid credentials or expired JWT", :unauthorized)
      when 403
        raise EnableBankingError.new("Access forbidden - check your application permissions", :access_forbidden)
      when 404
        raise EnableBankingError.new("Resource not found", :not_found)
      when 408
        raise EnableBankingError.new("Request timeout from Enable Banking API", :timeout)
      when 422
        response_data = parse_response_body(response)
        raise EnableBankingError.new("Validation error from Enable Banking API: #{response.body}", :validation_error, response_data: response_data)
      when 429
        raise EnableBankingError.new("Rate limit exceeded. Please try again later.", :rate_limited)
      else
        response_data = parse_error_response_body(response)
        raise EnableBankingError.new("Failed to fetch data: #{response.code} #{response.message} - #{response.body}", :fetch_failed, response_data: response_data)
      end
    end

    def parse_error_response_body(response)
      return {} if response.body.blank?

      JSON.parse(response.body, symbolize_names: true)
    rescue JSON::ParserError
      { raw_body: response.body.to_s }
    end

    def parse_response_body(response)
      return {} if response.body.blank?

      JSON.parse(response.body, symbolize_names: true)

View on GitHub (pinned to e69894adb9)

Solutions

  1. Retry with exponential backoff plus jitter (start ~30s) - the limit resets on a rolling window
  2. Serialize or throttle Enable Banking calls globally per application_id (a semaphore or queued job with a minimum interval)
  3. Reduce sync frequency or cache low-churn responses like get_aspsps
  4. Check the Enable Banking dashboard for the tier's quota and whether dev needs a separate application

Example fix

// before
accounts.map { |a| provider.get_account_transactions(account_id: a.uid, date_from: from) }

// after
accounts.each_slice(5) do |batch|
  batch.map { |a| provider.get_account_transactions(account_id: a.uid, date_from: from) }
  sleep 1 unless batch.equal?(accounts.last)
end
Defensive patterns

Strategy: retry

Type guard

def enable_banking_rate_limited?(error)
  error.is_a?(Provider::EnableBanking::EnableBankingError) && error.error_type == :rate_limited
end

Try / catch

retries = 0
begin
  provider.get_aspsps(country: "ES")
rescue Provider::EnableBanking::EnableBankingError => e
  retries += 1
  retry if e.error_type == :rate_limited && retries <= 5 && sleep(retries**2 + rand(5))
  raise
end

Prevention

When it happens

Trigger: Looping get_account_transactions over dozens of accounts in one sync without throttling; parallel background jobs all minting JWTs for the same application_id; a retry storm after a timeout or 5xx; exceeding the plan's requests-per-second quota on /aspsps during bank-picker rebuilds.

Common situations: Sync scheduler fan-out (all users synced at the top of the hour), dev and prod sharing one Enable Banking application, aggressive polling right after a mass re-consent flow.

Related errors


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