we-promise/sure · error · Provider::Brex::BrexError

bad_request

bad_request

Error message

Bad request to Brex API

What it means

Raised by Provider::Brex#handle_response when the Brex API answers HTTP 400 to any request. It means Brex considered the request malformed — invalid query parameters, a bad cursor, an unsupported limit, or a wrongly formatted date — not an auth or outage problem. The error carries http_status 400 and the response's X-Brex-Trace-Id as trace_id, which Brex support can use to locate the rejected request.

Source

Thrown at app/models/provider/brex.rb:212

    end

    def auth_headers
      {
        "Authorization" => "Bearer #{token}",
        "Content-Type" => "application/json",
        "Accept" => "application/json"
      }
    end

    def handle_response(response, path:)
      trace_id = brex_trace_id(response)

      case response.code
      when 200
        parse_json(response.body)
      when 400
        Rails.logger.error "Brex API: bad request for #{path} trace_id=#{trace_id}"
        raise BrexError.new("Bad request to Brex API", :bad_request, http_status: 400, trace_id: trace_id)
      when 401
        Rails.logger.warn "Brex API: unauthorized for #{path} trace_id=#{trace_id}"
        raise BrexError.new("Invalid Brex API token or account permissions", :unauthorized, http_status: 401, trace_id: trace_id)
      when 403
        Rails.logger.warn "Brex API: access forbidden for #{path} trace_id=#{trace_id}"
        raise BrexError.new("Access forbidden - check Brex API token scopes", :access_forbidden, http_status: 403, trace_id: trace_id)
      when 404
        Rails.logger.warn "Brex API: resource not found for #{path} trace_id=#{trace_id}"
        raise BrexError.new("Brex resource not found", :not_found, http_status: 404, trace_id: trace_id)
      when 429
        Rails.logger.warn "Brex API: rate limited for #{path} trace_id=#{trace_id}"
        raise BrexError.new("Brex rate limit exceeded. Please try again later.", :rate_limited, http_status: 429, trace_id: trace_id)
      else
        Rails.logger.error "Brex API: unexpected response code=#{response.code} path=#{path} trace_id=#{trace_id}"
        raise BrexError.new("Failed to fetch data from Brex API: HTTP #{response.code}", :fetch_failed, http_status: response.code, trace_id: trace_id)
      end
    end

View on GitHub (pinned to e69894adb9)

Solutions

  1. Capture error.trace_id from the raised BrexError and check the Rails log line 'Brex API: bad request for <path>' to identify which call was rejected
  2. Confirm callers only pass Date/Time/parseable values as start_date so rfc3339_start_date emits a clean RFC3339 timestamp
  3. Stop persisting pagination cursors across processes — get_paginated already loops internally within one call
  4. If the cause is unclear, reproduce the exact request with curl and escalate to Brex support with the trace_id

Example fix

# before
client.get_cash_transactions(account_id, start_date: params[:from]) # raw string from user

# after
from = Date.parse(params[:from]) rescue nil
client.get_cash_transactions(account_id, start_date: from) # nil skips posted_at_start
Defensive patterns

Strategy: validation

Validate before calling

def valid_brex_start_date?(value)
  return true if value.nil? || value.is_a?(Date) || value.is_a?(Time)
  Time.zone.parse(value.to_s).present?
rescue ArgumentError, TypeError
  false
end

Type guard

def brex_bad_request?(error)
  error.is_a?(Provider::Brex::BrexError) && error.error_type == :bad_request
end

Try / catch

begin
  client.get_cash_transactions(id, start_date: from)
rescue Provider::Brex::BrexError => e
  raise unless e.error_type == :bad_request
  Rails.logger.error("Brex 400 trace_id=#{e.trace_id} — do not retry, fix params")
  raise
end

Prevention

When it happens

Trigger: get_paginated sends a stale or replayed cursor param; get_cash_transactions/get_primary_card_transactions pass a posted_at_start that rfc3339_start_date formats unexpectedly; page_params include an unsupported limit value; or the path itself is malformed (wrong account id encoding).

Common situations: Persisting pagination cursors between sync runs and replaying them after they expired, passing user-supplied date strings straight into start_date, and Brex API version changes that rename or restrict query params.

Related errors


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