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
- Retry with exponential backoff plus jitter (start ~30s) - the limit resets on a rolling window
- Serialize or throttle Enable Banking calls globally per application_id (a semaphore or queued job with a minimum interval)
- Reduce sync frequency or cache low-churn responses like get_aspsps
- 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
- Throttle all Enable Banking calls per application_id (queue with min interval)
- Cache low-churn responses like get_aspsps for hours/days
- Use a separate application_id per environment so dev load never trips prod limits
- Backoff with jitter - synchronized retries across workers re-trigger the 429
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.