{"record":{"id":"9f0aeb4c53d1272d","repo":"we-promise/sure","slug":"rate-limited-9f0aeb","errorCode":"rate_limited","errorMessage":"Rate limit exceeded. Please try again later.","messagePattern":"Rate limit exceeded\\. Please try again later\\.","errorType":"exception","errorClass":"Provider::Lunchflow::LunchflowError","httpStatus":429,"severity":"warning","filePath":"app/models/provider/lunchflow.rb","lineNumber":135,"sourceCode":"        \"Accept\" => \"application/json\"\n      }\n    end\n\n    def handle_response(response)\n      case response.code\n      when 200\n        JSON.parse(response.body, symbolize_names: true)\n      when 400\n        Rails.logger.error \"Lunch Flow API: Bad request - #{response.body}\"\n        raise LunchflowError.new(\"Bad request to Lunch Flow API: #{response.body}\", :bad_request)\n      when 401\n        raise LunchflowError.new(\"Invalid API key\", :unauthorized)\n      when 403\n        raise LunchflowError.new(\"Access forbidden - check your API key permissions\", :access_forbidden)\n      when 404\n        raise LunchflowError.new(\"Resource not found\", :not_found)\n      when 429\n        raise LunchflowError.new(\"Rate limit exceeded. Please try again later.\", :rate_limited)\n      else\n        Rails.logger.error \"Lunch Flow API: Unexpected response - Code: #{response.code}, Body: #{response.body}\"\n        raise LunchflowError.new(\"Failed to fetch data: #{response.code} #{response.message} - #{response.body}\", :fetch_failed)\n      end\n    end\n\n    class LunchflowError < StandardError\n      attr_reader :error_type\n\n      def initialize(message, error_type = :unknown)\n        super(message)\n        @error_type = error_type\n      end\n    end\nend\n","sourceCodeStart":117,"sourceCodeEnd":151,"githubUrl":"https://github.com/we-promise/sure/blob/e69894adb92547273377398c15f45c979cd9416a/app/models/provider/lunchflow.rb#L117-L151","documentation":"HTTP 429 branch of Provider::Lunchflow#handle_response (app/models/provider/lunchflow.rb:134-135). Thrown as LunchflowError(:rate_limited) with message 'Rate limit exceeded. Please try again later.' when Lunchflow answers 429 — the API key has exceeded its request quota for the current window. The client sets no rate limiter of its own; every public method is one HTTP call, so quota is consumed per call.","triggerScenarios":"Sync loops calling get_account_transactions per account in rapid succession; parallel jobs (Sidekiq threads/processes) sharing one API key; polling get_account_balance at short intervals; backfill scripts iterating wide date ranges account-by-account with no throttling.","commonSituations":"Growing account counts pushing a nightly sync past the provider quota; multiple environments (staging + production, or several users) sharing one Lunchflow key; retries during an outage amplifying request volume.","solutions":["Retry with exponential backoff plus jitter, spacing attempts by seconds — the limit window resets on its own.","Serialize or throttle per-account calls (e.g. a semaphore or a queue with a minimum interval per key).","Cache responses and widen the sync interval so steady-state request volume stays under quota.","If sustained volume genuinely exceeds quota, request a higher tier/key limit from Lunchflow or use one key per user connection."],"exampleFix":"// before\naccounts.each { |a| client.get_account_balance(a[:id]) }\n\n// after\nattempts = 0\nbegin\n  attempts += 1\n  accounts.each { |a| client.get_account_balance(a[:id]) }\nrescue Provider::Lunchflow::LunchflowError => e\n  raise unless e.error_type == :rate_limited && attempts < 5\n  sleep((2**attempts) + rand(3))\n  retry\nend","handlingStrategy":"retry","validationCode":null,"typeGuard":"def rate_limited?(e)\n  e.is_a?(Provider::Lunchflow::LunchflowError) && e.error_type == :rate_limited\nend","tryCatchPattern":"attempts = 0\nbegin\n  attempts += 1\n  client.get_account_balance(acct_id)\nrescue Provider::Lunchflow::LunchflowError => e\n  raise unless e.error_type == :rate_limited && attempts < 5\n  sleep((2**attempts) + rand(3)) # backoff + jitter\n  retry\nend","preventionTips":["Throttle per-key request rate (token bucket) in the sync job.","Cache provider responses between sync runs instead of re-fetching.","Stagger per-account jobs so bursts never overlap.","Alert on sustained 429s — that is a capacity planning signal."],"tags":["lunchflow","http-429","rate-limit","throttling","retry"],"backgroundTag":"rate-limit-exceeded","analyzedSha":"e69894adb92547273377398c15f45c979cd9416a","analyzedAt":"2026-08-21T18:22:41.165Z","schemaVersion":2},"datasetVersion":"2026-08-21T23:17:16.201Z"}