we-promise/sure · error · Provider::Brex::BrexError
unauthorized
unauthorized
Error message
Invalid Brex API token or account permissions
What it means
Raised by Provider::Brex#handle_response on HTTP 401: the Bearer token sent in auth_headers was rejected by Brex, so every endpoint call will fail the same way. The token is taken verbatim from the Provider::Brex constructor (stripped of whitespace) and never refreshed by this client. http_status 401 and the X-Brex-Trace-Id are attached to the error.
Source
Thrown at app/models/provider/brex.rb:215
{
"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
def parse_json(body)
return {} if body.blank?
View on GitHub (pinned to e69894adb9)
Solutions
- Issue a fresh API token in the Brex dashboard and update the stored credential
- Verify the token matches the environment: staging tokens only work with base_url https://api-staging.brex.com
- Confirm the token has no surrounding whitespace/newlines (the constructor strips edges, but internal characters must be intact)
- Check the Rails log 'Brex API: unauthorized for <path> trace_id=...' and confirm the same failure across all endpoints (a 401 on only one endpoint is actually a scopes problem, see :access_forbidden)
Example fix
# before Provider::Brex.new(ENV["BREX_TOKEN"].to_s) # nil -> "" silently, fails later with 401 # after token = ENV["BREX_TOKEN"].to_s.strip raise ArgumentError, "BREX_TOKEN is missing or empty" if token.empty? Provider::Brex.new(token)
Defensive patterns
Strategy: validation
Validate before calling
def brex_token_configured?(token) token.is_a?(String) && !token.strip.empty? end
Type guard
def brex_unauthorized?(error) error.is_a?(Provider::Brex::BrexError) && error.error_type == :unauthorized end
Try / catch
begin client.get_cash_accounts rescue Provider::Brex::BrexError => e raise unless e.error_type == :unauthorized connection.update!(status: "reauth_required") # never retry; flag for credential rotation raise end
Prevention
- Validate token presence at client construction instead of failing on first API call
- Match environment: staging token with STAGING_BASE_URL only
- Store tokens encrypted and rotate on a schedule; monitor for revocation
- Run a cheap get_cash_accounts canary after credential changes
When it happens
Trigger: The stored token was revoked or expired in the Brex dashboard; a staging token is used against https://api.brex.com (or vice versa); the token was truncated or mangled when copied; an empty/placeholder token was passed to Provider::Brex.new.
Common situations: Rotating API credentials and forgetting to update the encrypted store, environment mismatch between token and base_url, whitespace or newline contamination from copy-paste or .env handling.
Related errors
AI-assisted analysis of we-promise/sure@e69894adb9 (2026-08-21).
Data as JSON: /api/errors/41a9c1f2b4408266.
Report an issue: GitHub.