{"record":{"id":"7f7896da4ed0086f","repo":"we-promise/sure","slug":"access-forbidden-7f7896","errorCode":"access_forbidden","errorMessage":"Access forbidden - check your API key permissions","messagePattern":"Access forbidden - check your API key permissions","errorType":"exception","errorClass":"LunchflowError","httpStatus":403,"severity":"error","filePath":"app/models/provider/lunchflow.rb","lineNumber":131,"sourceCode":"    def auth_headers\n      {\n        \"x-api-key\" => api_key,\n        \"Content-Type\" => \"application/json\",\n        \"Accept\" => \"application/json\"\n      }\n    end\n\n    def handle_response(response)\n      case response.code\n      when 200\n        JSON.parse(response.body, symbolize_names: true)\n      when 400\n        Rails.logger.error \"Lunch Flow API: Bad request - #{response.body}\"\n        raise LunchflowError.new(\"Bad request to Lunch Flow API: #{response.body}\", :bad_request)\n      when 401\n        raise LunchflowError.new(\"Invalid API key\", :unauthorized)\n      when 403\n        raise LunchflowError.new(\"Access forbidden - check your API key permissions\", :access_forbidden)\n      when 404\n        raise LunchflowError.new(\"Resource not found\", :not_found)\n      when 429\n        raise LunchflowError.new(\"Rate limit exceeded. Please try again later.\", :rate_limited)\n      else\n        Rails.logger.error \"Lunch Flow API: Unexpected response - Code: #{response.code}, Body: #{response.body}\"\n        raise LunchflowError.new(\"Failed to fetch data: #{response.code} #{response.message} - #{response.body}\", :fetch_failed)\n      end\n    end\n\n    class LunchflowError < StandardError\n      attr_reader :error_type\n\n      def initialize(message, error_type = :unknown)\n        super(message)\n        @error_type = error_type\n      end\n    end","sourceCodeStart":113,"sourceCodeEnd":149,"githubUrl":"https://github.com/we-promise/sure/blob/e69894adb92547273377398c15f45c979cd9416a/app/models/provider/lunchflow.rb#L113-L149","documentation":"HTTP 403 branch of Provider::Lunchflow#handle_response (app/models/provider/lunchflow.rb:130-131). Thrown as LunchflowError(:access_forbidden) with message 'Access forbidden - check your API key permissions' when Lunchflow returns 403: the x-api-key authenticated successfully but is not permitted to access the specific resource. Distinct from 401 — the key is valid, its authorization scope is not.","triggerScenarios":"A read-only or transactions-only key calling get_account_holdings or get_account_balance on accounts outside its granted scope; per-account ACLs in Lunchflow excluding the key; workspace/plan-level restrictions on an endpoint (e.g. holdings reserved for paid tiers); server-side IP allowlist rejecting the app host.","commonSituations":"Keys provisioned for a narrower scope than the sync job assumes; provider changing entitlements (holding endpoints gated by plan); account shared between workspaces where the key only covers one.","solutions":["In the Lunchflow dashboard, check the key's permissions/scopes and grant access to the endpoints being called (accounts, transactions, balance, holdings).","Confirm the account_id belongs to a workspace the key can reach.","If the provider gates the endpoint by plan, upgrade or stop calling that endpoint for this connection.","Handle :access_forbidden by disabling the affected sync feature for this provider rather than retrying — 403 is not transient."],"exampleFix":"// before\nholdings = client.get_account_holdings(acct_id) rescue nil\n\n// after\nbegin\n  holdings = client.get_account_holdings(acct_id)\nrescue Provider::Lunchflow::LunchflowError => e\n  raise unless e.error_type == :access_forbidden\n  disable_holdings_sync_for!(provider_connection) # permanent config issue\nend","handlingStrategy":"try-catch","validationCode":null,"typeGuard":"def access_forbidden?(e)\n  e.is_a?(Provider::Lunchflow::LunchflowError) && e.error_type == :access_forbidden\nend","tryCatchPattern":"begin\n  client.get_account_holdings(acct_id)\nrescue Provider::Lunchflow::LunchflowError => e\n  raise unless e.error_type == :access_forbidden\n  connection.update!(holdings_enabled: false)\n  inform_user_to_widen_key_scopes(connection)\nend","preventionTips":["Provision keys with the exact scopes your sync uses (accounts, transactions, balance, holdings).","Check key permissions during onboarding, not during nightly syncs.","Treat 403 as permanent — disable the feature rather than retrying."],"tags":["lunchflow","http-403","permissions","authorization","api-key"],"backgroundTag":"api-permission-denied","analyzedSha":"e69894adb92547273377398c15f45c979cd9416a","analyzedAt":"2026-08-21T18:22:41.165Z","schemaVersion":2},"datasetVersion":"2026-08-21T23:17:16.201Z"}