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
- 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
- Confirm callers only pass Date/Time/parseable values as start_date so rfc3339_start_date emits a clean RFC3339 timestamp
- Stop persisting pagination cursors across processes — get_paginated already loops internally within one call
- 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
- Only pass Date/Time objects or pre-validated strings as start_date
- Never persist pagination cursors across processes or runs
- Log error.trace_id with every 400 so Brex support can trace rejected requests
- Add contract tests around rfc3339_start_date so date formatting regressions surface before hitting the API
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.