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

  1. 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)
  2. Re-issue the token with the missing scopes (e.g. transactions:read) and update the stored credential
  3. Confirm the Brex user behind the token still belongs to the entity that owns the account
  4. 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

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

Related errors


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