{"record":{"id":"eefd59ee88400683","repo":"we-promise/sure","slug":"rate-limited-eefd59","errorCode":"rate_limited","errorMessage":"Rate limit exceeded. Please try again later.","messagePattern":"Rate limit exceeded\\. Please try again later\\.","errorType":"exception","errorClass":"EnableBankingError","httpStatus":429,"severity":"warning","filePath":"app/models/provider/enable_banking.rb","lineNumber":298,"sourceCode":"        parse_response_body(response)\n      when 204\n        {}\n      when 400\n        response_data = parse_error_response_body(response)\n        raise EnableBankingError.new(\"Bad request to Enable Banking API: #{response.body}\", :bad_request, response_data: response_data)\n      when 401\n        raise EnableBankingError.new(\"Invalid credentials or expired JWT\", :unauthorized)\n      when 403\n        raise EnableBankingError.new(\"Access forbidden - check your application permissions\", :access_forbidden)\n      when 404\n        raise EnableBankingError.new(\"Resource not found\", :not_found)\n      when 408\n        raise EnableBankingError.new(\"Request timeout from Enable Banking API\", :timeout)\n      when 422\n        response_data = parse_response_body(response)\n        raise EnableBankingError.new(\"Validation error from Enable Banking API: #{response.body}\", :validation_error, response_data: response_data)\n      when 429\n        raise EnableBankingError.new(\"Rate limit exceeded. Please try again later.\", :rate_limited)\n      else\n        response_data = parse_error_response_body(response)\n        raise EnableBankingError.new(\"Failed to fetch data: #{response.code} #{response.message} - #{response.body}\", :fetch_failed, response_data: response_data)\n      end\n    end\n\n    def parse_error_response_body(response)\n      return {} if response.body.blank?\n\n      JSON.parse(response.body, symbolize_names: true)\n    rescue JSON::ParserError\n      { raw_body: response.body.to_s }\n    end\n\n    def parse_response_body(response)\n      return {} if response.body.blank?\n\n      JSON.parse(response.body, symbolize_names: true)","sourceCodeStart":280,"sourceCodeEnd":316,"githubUrl":"https://github.com/we-promise/sure/blob/e69894adb92547273377398c15f45c979cd9416a/app/models/provider/enable_banking.rb#L280-L316","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\naccounts.map { |a| provider.get_account_transactions(account_id: a.uid, date_from: from) }\n\n// after\naccounts.each_slice(5) do |batch|\n  batch.map { |a| provider.get_account_transactions(account_id: a.uid, date_from: from) }\n  sleep 1 unless batch.equal?(accounts.last)\nend","handlingStrategy":"retry","validationCode":null,"typeGuard":"def enable_banking_rate_limited?(error)\n  error.is_a?(Provider::EnableBanking::EnableBankingError) && error.error_type == :rate_limited\nend","tryCatchPattern":"retries = 0\nbegin\n  provider.get_aspsps(country: \"ES\")\nrescue Provider::EnableBanking::EnableBankingError => e\n  retries += 1\n  retry if e.error_type == :rate_limited && retries <= 5 && sleep(retries**2 + rand(5))\n  raise\nend","preventionTips":["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"],"tags":["enable-banking","http-429","rate-limit","throttling","quota"],"backgroundTag":"http-429-rate-limited","analyzedSha":"e69894adb92547273377398c15f45c979cd9416a","analyzedAt":"2026-08-21T18:22:41.165Z","schemaVersion":2},"datasetVersion":"2026-08-21T23:17:16.201Z"}