we-promise/sure · error · Provider::Brex::BrexError
access_forbidden
access_forbidden
Error message
Access forbidden - check Brex API token scopes
What it means
Raised by Provider::Brex#handle_response on HTTP 403: Brex authenticated the token successfully but the token's scopes do not cover the requested resource. Unlike :unauthorized, the credential itself is valid — it just lacks permission (e.g. transactions read on a cash account, or card endpoints with a cash-scoped token). http_status 403 and trace_id are attached.
Source
Thrown at app/models/provider/brex.rb:218
"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?
JSON.parse(body, symbolize_names: true)
end
View on GitHub (pinned to e69894adb9)
Solutions
- Check which scopes were granted to the token in the Brex dashboard and compare them against the endpoint being called (accounts vs transactions, cash vs card)
- Re-issue the token with the missing scopes (e.g. transactions:read) and update the stored credential
- Confirm the Brex user behind the token still belongs to the entity that owns the account
- If only one endpoint 403s while others succeed, scope mismatch is confirmed — do not re-issue blindly
Defensive patterns
Strategy: validation
Validate before calling
# Probe the cheapest endpoint per resource family before syncing it def brex_scope_available?(client, family) case family when :accounts then !client.get_cash_accounts.empty? || true # 200/403 decides when :transactions then client.get_primary_card_transactions(start_date: 1.day.ago.to_date) && true end rescue Provider::Brex::BrexError => e e.error_type != :access_forbidden end
Type guard
def brex_forbidden?(error) error.is_a?(Provider::Brex::BrexError) && error.error_type == :access_forbidden end
Try / catch
begin
client.get_cash_transactions(id)
rescue Provider::Brex::BrexError => e
raise unless e.error_type == :access_forbidden
Rails.logger.warn("Brex token missing scope for #{id}; skipping account")
next # skip, do not retry — scope grants are external
end Prevention
- Request all needed scopes (accounts + transactions, cash + card) when issuing the token
- Document the endpoint-to-scope mapping next to each client call
- Verify scopes in staging before production rollout
- Treat 403-on-one-endpoint vs 401-on-all as the scope-vs-token diagnostic
When it happens
Trigger: Calling get_cash_transactions/get_primary_card_transactions with a token that only has accounts:read scope; calling /v2/accounts/card with a token issued for cash accounts only; the Brex user losing access to the entity that owns the accounts.
Common situations: Token created with a minimal scope set during initial integration and never widened when card sync was added; entity membership changes on the Brex side removing the API user's access.
Understand the failure class
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
AI-assisted analysis of we-promise/sure@e69894adb9 (2026-08-21).
Data as JSON: /api/errors/1b96b2a6610196dc.
Report an issue: GitHub.