we-promise/sure · error · Provider::Mercury::MercuryError

bad_request

bad_request

Error message

Bad request to Mercury API: #{response.body}

What it means

HTTP 400 branch of Provider::Mercury#handle_response (app/models/provider/mercury.rb:114-116). Thrown as MercuryError(:bad_request) whenever Mercury answers with 400; the raw response body is embedded in the error message and logged ('Mercury API: Bad request - ...'). For Mercury this typically means the query string built at lines 63-82 violates API constraints: invalid parameter values or types.

Source

Thrown at app/models/provider/mercury.rb:116

  end

  private

    def auth_headers
      {
        "Authorization" => "Bearer #{token}",
        "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 "Mercury API: Bad request - #{response.body}"
        raise MercuryError.new("Bad request to Mercury API: #{response.body}", :bad_request)
      when 401
        # Parse the error response for more specific messages
        error_message = parse_error_message(response.body)
        raise MercuryError.new(error_message, :unauthorized)
      when 403
        raise MercuryError.new("Access forbidden - check your API token permissions", :access_forbidden)
      when 404
        raise MercuryError.new("Resource not found", :not_found)
      when 429
        raise MercuryError.new("Rate limit exceeded. Please try again later.", :rate_limited)
      else
        Rails.logger.error "Mercury API: Unexpected response - Code: #{response.code}, Body: #{response.body}"
        raise MercuryError.new("Failed to fetch data: #{response.code} #{response.message} - #{response.body}", :fetch_failed)
      end
    end

    def parse_error_message(body)
      parsed = JSON.parse(body, symbolize_names: true)

View on GitHub (pinned to e69894adb9)

Solutions

  1. Read the embedded body in the error message — Mercury states the exact offending parameter.
  2. Clamp pagination params: limit between 1 and 500, offset >= 0, before calling.
  3. Validate the account_id format (Mercury account UUIDs) and refresh via get_accounts if suspect.
  4. Ensure start_date <= end_date and both are real dates.

Example fix

// before
client.get_account_transactions(acct_id, offset: params[:offset], limit: params[:limit])

// after
limit  = [[params[:limit].to_i, 1].max, 500].min
offset = [params[:offset].to_i, 0].max
client.get_account_transactions(acct_id.to_s, offset: offset, limit: limit)
Defensive patterns

Strategy: validation

Validate before calling

limit  = [[limit.to_i, 1].max, 500].min   # Mercury max page size
offset = [offset.to_i, 0].max
raise ArgumentError, 'start after end' if from && to && from > to

Type guard

def bad_request?(e)
  e.is_a?(Provider::Mercury::MercuryError) && e.error_type == :bad_request
end

Try / catch

begin
  client.get_account_transactions(acct_id, offset: offset, limit: limit)
rescue Provider::Mercury::MercuryError => e
  raise unless e.error_type == :bad_request
  DebugLogEntry.capture(category: 'mercury', level: 'error',
                        message: e.message, provider_key: 'mercury')
  skip_account(acct_id)
end

Prevention

When it happens

Trigger: get_account_transactions with limit exceeding Mercury's maximum page size (500), limit/offset zero or negative, dates that survive #to_date but are rejected server-side, or a malformed account UUID in the path; any GET /accounts or /account/{id} call when Mercury changes request validation rules.

Common situations: Hard-coded limit above the API maximum after a provider policy change; offset math bugs producing negatives during pagination; account ids imported from another provider or truncated; date boundaries like start > end.

Related errors


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