we-promise/sure · warning · Provider::Lunchflow::LunchflowError

rate_limited

rate_limited

Error message

Rate limit exceeded. Please try again later.

What it means

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.

Source

Thrown at app/models/provider/lunchflow.rb:135

        "Accept" => "application/json"
      }
    end

    def handle_response(response)
      case response.code
      when 200
        JSON.parse(response.body, symbolize_names: true)
      when 400
        Rails.logger.error "Lunch Flow API: Bad request - #{response.body}"
        raise LunchflowError.new("Bad request to Lunch Flow API: #{response.body}", :bad_request)
      when 401
        raise LunchflowError.new("Invalid API key", :unauthorized)
      when 403
        raise LunchflowError.new("Access forbidden - check your API key permissions", :access_forbidden)
      when 404
        raise LunchflowError.new("Resource not found", :not_found)
      when 429
        raise LunchflowError.new("Rate limit exceeded. Please try again later.", :rate_limited)
      else
        Rails.logger.error "Lunch Flow API: Unexpected response - Code: #{response.code}, Body: #{response.body}"
        raise LunchflowError.new("Failed to fetch data: #{response.code} #{response.message} - #{response.body}", :fetch_failed)
      end
    end

    class LunchflowError < StandardError
      attr_reader :error_type

      def initialize(message, error_type = :unknown)
        super(message)
        @error_type = error_type
      end
    end
end

View on GitHub (pinned to e69894adb9)

Solutions

  1. Retry with exponential backoff plus jitter, spacing attempts by seconds — the limit window resets on its own.
  2. Serialize or throttle per-account calls (e.g. a semaphore or a queue with a minimum interval per key).
  3. Cache responses and widen the sync interval so steady-state request volume stays under quota.
  4. If sustained volume genuinely exceeds quota, request a higher tier/key limit from Lunchflow or use one key per user connection.

Example fix

// before
accounts.each { |a| client.get_account_balance(a[:id]) }

// after
attempts = 0
begin
  attempts += 1
  accounts.each { |a| client.get_account_balance(a[:id]) }
rescue Provider::Lunchflow::LunchflowError => e
  raise unless e.error_type == :rate_limited && attempts < 5
  sleep((2**attempts) + rand(3))
  retry
end
Defensive patterns

Strategy: retry

Type guard

def rate_limited?(e)
  e.is_a?(Provider::Lunchflow::LunchflowError) && e.error_type == :rate_limited
end

Try / catch

attempts = 0
begin
  attempts += 1
  client.get_account_balance(acct_id)
rescue Provider::Lunchflow::LunchflowError => e
  raise unless e.error_type == :rate_limited && attempts < 5
  sleep((2**attempts) + rand(3)) # backoff + jitter
  retry
end

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


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