{"record":{"id":"32e44d34e81f4eb0","repo":"we-promise/sure","slug":"fetch-failed-32e44d","errorCode":"fetch_failed","errorMessage":"Failed to fetch data: #{response.code} #{response.message} - #{response.body}","messagePattern":"Failed to fetch data: #(.+?) #(.+?) - #(.+?)","errorType":"exception","errorClass":"EnableBankingError","httpStatus":null,"severity":"error","filePath":"app/models/provider/enable_banking.rb","lineNumber":301,"sourceCode":"      when 400\n        response_data = parse_error_response_body(response)\n        raise EnableBankingError.new(\"Bad request to Enable Banking API: #{response.body}\", :bad_request, response_data: response_data)\n      when 401\n        raise EnableBankingError.new(\"Invalid credentials or expired JWT\", :unauthorized)\n      when 403\n        raise EnableBankingError.new(\"Access forbidden - check your application permissions\", :access_forbidden)\n      when 404\n        raise EnableBankingError.new(\"Resource not found\", :not_found)\n      when 408\n        raise EnableBankingError.new(\"Request timeout from Enable Banking API\", :timeout)\n      when 422\n        response_data = parse_response_body(response)\n        raise EnableBankingError.new(\"Validation error from Enable Banking API: #{response.body}\", :validation_error, response_data: response_data)\n      when 429\n        raise EnableBankingError.new(\"Rate limit exceeded. Please try again later.\", :rate_limited)\n      else\n        response_data = parse_error_response_body(response)\n        raise EnableBankingError.new(\"Failed to fetch data: #{response.code} #{response.message} - #{response.body}\", :fetch_failed, response_data: response_data)\n      end\n    end\n\n    def parse_error_response_body(response)\n      return {} if response.body.blank?\n\n      JSON.parse(response.body, symbolize_names: true)\n    rescue JSON::ParserError\n      { raw_body: response.body.to_s }\n    end\n\n    def parse_response_body(response)\n      return {} if response.body.blank?\n\n      JSON.parse(response.body, symbolize_names: true)\n    rescue JSON::ParserError => e\n      Rails.logger.error \"Enable Banking API: Failed to parse response: #{e.message}\"\n      raise EnableBankingError.new(\"Failed to parse API response\", :parse_error)","sourceCodeStart":283,"sourceCodeEnd":319,"githubUrl":"https://github.com/we-promise/sure/blob/e69894adb92547273377398c15f45c979cd9416a/app/models/provider/enable_banking.rb#L283-L319","documentation":"The catch-all branch of handle_response: any status not in 204/400/401/403/404/408/422/429 lands here - typically 5xx from the aggregator or upstream bank, but also 3xx redirects and unmapped 4xx (409, 410, 418...). The message embeds response.code, response.message and the raw body; response_data comes from the lenient parser, so a non-JSON body yields { raw_body: \"...\" } instead of a secondary parse error.","triggerScenarios":"Enable Banking gateway returns 500/502/503 when an ASPSP integration is down; an endpoint moves and the gateway 301-redirects; an unmapped 4xx like 409 on a duplicate session creation; a proxy in front of the API returns an odd status.","commonSituations":"Single-bank outages surfacing as aggregator 5xx, deploy windows at Enable Banking, BASE_URL drift if the provider version changes paths, or HTML error pages from an edge proxy.","solutions":["Read the embedded status: parse the leading number in the message or use e.response_data - 5xx means retry later, 3xx/4xx means investigate the request","Treat 5xx occurrences as transient: reschedule the sync, do not invalidate credentials or consent","Capture the full response_data via DebugLogEntry so support can correlate with the aggregator's logs","If every call fails with the same odd status, diff the request path/headers against the current Enable Banking API docs"],"exampleFix":"// before\nrescue Provider::EnableBanking::EnableBankingError => e\n  notify_error(e) # every failure paged on-call\n\n// after\nrescue Provider::EnableBanking::EnableBankingError => e\n  if e.error_type == :fetch_failed && e.message[/\\AFailed to fetch data: (\\d{3})/, 1].to_i >= 500\n    RetryableSync.schedule(account, wait: 10.minutes)\n  else\n    notify_error(e)\n  end\nend","handlingStrategy":"try-catch","validationCode":null,"typeGuard":"def enable_banking_fetch_failed?(error)\n  error.is_a?(Provider::EnableBanking::EnableBankingError) && error.error_type == :fetch_failed\nend\n\ndef fetch_failed_status(error)\n  error.message[/\\AFailed to fetch data: (\\d{3})/, 1].to_i\nend","tryCatchPattern":"begin\n  provider.get_account_balances(account_id: id)\nrescue Provider::EnableBanking::EnableBankingError => e\n  raise unless e.error_type == :fetch_failed\n  status = e.message[/\\AFailed to fetch data: (\\d{3})/, 1].to_i\n  if status >= 500\n    RetryableSync.schedule(account, wait: 15.minutes) # upstream/aggregator outage\n  else\n    DebugLogEntry.capture(category: :enable_banking, level: :error,\n      message: e.message, metadata: e.response_data || {})\n    raise\n  end\nend","preventionTips":["Always branch on the embedded status before deciding retry vs alert","Persist e.response_data (including raw_body) in the debug log for support correlation","Treat 5xx as transient and 3xx as a config/BASE_URL smell","Alert only when the same unmapped status repeats across cycles"],"tags":["enable-banking","catch-all","http-5xx","upstream","unexpected-status"],"backgroundTag":"unexpected-http-status-code","analyzedSha":"e69894adb92547273377398c15f45c979cd9416a","analyzedAt":"2026-08-21T18:22:41.165Z","schemaVersion":2},"datasetVersion":"2026-08-21T23:17:16.201Z"}