we-promise/sure · error · LunchflowError

bad_request

bad_request

Error message

Bad request to Lunch Flow API: #{response.body}

What it means

HTTP 400 branch of Provider::Lunchflow#handle_response (app/models/provider/lunchflow.rb:125-127). Thrown as LunchflowError(:bad_request) whenever the Lunchflow API answers GET endpoints with status 400; the raw response body is embedded in both the Rails log ('Lunch Flow API: Bad request - ...') and the error message, so the server-side validation message is visible.

Source

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

  end

  private

    def auth_headers
      {
        "x-api-key" => api_key,
        "Content-Type" => "application/json",
        "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)

View on GitHub (pinned to e69894adb9)

Solutions

  1. Read the embedded response body in the error message and the 'Lunch Flow API: Bad request' log line — Lunchflow states exactly which parameter failed.
  2. Echo the exact request (path + query string from logs) with curl and adjust the parameters until the server accepts them.
  3. Normalize inputs before calling: Date objects for start/end, trimmed account_id strings, and omit params you do not need.
  4. If the body mentions an unknown parameter, check whether a base_url override points to an outdated API version.

Example fix

// before
client.get_account_transactions(acct_id, start_date: from, end_date: to, include_pending: true)

// after (send only supported, validated params)
params = { start_date: from.to_date.to_s, end_date: to.to_date.to_s }
client.get_account_transactions(acct_id.to_s.strip, **params.reject { |_, v| v.blank? })
Defensive patterns

Strategy: validation

Validate before calling

raise ArgumentError, 'account_id required' if acct_id.blank?
raise ArgumentError, 'start after end' if from && to && from > to
params = { start_date: from&.to_date, end_date: to&.to_date }.compact

Type guard

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

Try / catch

begin
  client.get_account_transactions(acct_id, **params)
rescue Provider::Lunchflow::LunchflowError => e
  raise unless e.error_type == :bad_request
  log_to_debug_entries(e.message) # body contains the server's reason
  skip_account(acct_id)
end

Prevention

When it happens

Trigger: GET /accounts/:id/transactions or /holdings with query parameters the server rejects: unsupported parameter combinations (e.g. include_pending=true on an endpoint that ignores/rejects it), a date range the server deems too wide, or an account id segment that fails server-side validation even after ERB::Util.url_encode. Since the client coerces dates with #to_date, malformed dates usually fail client-side first (surfacing as :request_failed instead), so a 400 here is genuine server-side validation.

Common situations: Provider API version change tightening parameter validation (base_url pinned to an old /api/v1 path); syncing with parameter combos that worked in sandbox but not production; account ids copied from another provider or environment.

Related errors


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