{"record":{"id":"1b96b2a6610196dc","repo":"we-promise/sure","slug":"access-forbidden","errorCode":"access_forbidden","errorMessage":"Access forbidden - check Brex API token scopes","messagePattern":"Access forbidden - check Brex API token scopes","errorType":"http","errorClass":"Provider::Brex::BrexError","httpStatus":403,"severity":"error","filePath":"app/models/provider/brex.rb","lineNumber":218,"sourceCode":"        \"Accept\" => \"application/json\"\n      }\n    end\n\n    def handle_response(response, path:)\n      trace_id = brex_trace_id(response)\n\n      case response.code\n      when 200\n        parse_json(response.body)\n      when 400\n        Rails.logger.error \"Brex API: bad request for #{path} trace_id=#{trace_id}\"\n        raise BrexError.new(\"Bad request to Brex API\", :bad_request, http_status: 400, trace_id: trace_id)\n      when 401\n        Rails.logger.warn \"Brex API: unauthorized for #{path} trace_id=#{trace_id}\"\n        raise BrexError.new(\"Invalid Brex API token or account permissions\", :unauthorized, http_status: 401, trace_id: trace_id)\n      when 403\n        Rails.logger.warn \"Brex API: access forbidden for #{path} trace_id=#{trace_id}\"\n        raise BrexError.new(\"Access forbidden - check Brex API token scopes\", :access_forbidden, http_status: 403, trace_id: trace_id)\n      when 404\n        Rails.logger.warn \"Brex API: resource not found for #{path} trace_id=#{trace_id}\"\n        raise BrexError.new(\"Brex resource not found\", :not_found, http_status: 404, trace_id: trace_id)\n      when 429\n        Rails.logger.warn \"Brex API: rate limited for #{path} trace_id=#{trace_id}\"\n        raise BrexError.new(\"Brex rate limit exceeded. Please try again later.\", :rate_limited, http_status: 429, trace_id: trace_id)\n      else\n        Rails.logger.error \"Brex API: unexpected response code=#{response.code} path=#{path} trace_id=#{trace_id}\"\n        raise BrexError.new(\"Failed to fetch data from Brex API: HTTP #{response.code}\", :fetch_failed, http_status: response.code, trace_id: trace_id)\n      end\n    end\n\n    def parse_json(body)\n      return {} if body.blank?\n\n      JSON.parse(body, symbolize_names: true)\n    end\n","sourceCodeStart":200,"sourceCodeEnd":236,"githubUrl":"https://github.com/we-promise/sure/blob/e69894adb92547273377398c15f45c979cd9416a/app/models/provider/brex.rb#L200-L236","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"# Probe the cheapest endpoint per resource family before syncing it\ndef brex_scope_available?(client, family)\n  case family\n  when :accounts then !client.get_cash_accounts.empty? || true # 200/403 decides\n  when :transactions then client.get_primary_card_transactions(start_date: 1.day.ago.to_date) && true\n  end\nrescue Provider::Brex::BrexError => e\n  e.error_type != :access_forbidden\nend","typeGuard":"def brex_forbidden?(error)\n  error.is_a?(Provider::Brex::BrexError) && error.error_type == :access_forbidden\nend","tryCatchPattern":"begin\n  client.get_cash_transactions(id)\nrescue Provider::Brex::BrexError => e\n  raise unless e.error_type == :access_forbidden\n  Rails.logger.warn(\"Brex token missing scope for #{id}; skipping account\")\n  next # skip, do not retry — scope grants are external\nend","preventionTips":["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"],"tags":["brex","http-403","authorization","scopes"],"backgroundTag":"http-403-forbidden","analyzedSha":"e69894adb92547273377398c15f45c979cd9416a","analyzedAt":"2026-08-21T18:22:41.165Z","schemaVersion":2},"datasetVersion":"2026-08-21T23:17:16.201Z"}