{"record":{"id":"92be9c2584a9d9e5","repo":"we-promise/sure","slug":"validation-error","errorCode":"validation_error","errorMessage":"Validation error from Enable Banking API: #{response.body}","messagePattern":"Validation error from Enable Banking API: #(.+?)","errorType":"exception","errorClass":"EnableBankingError","httpStatus":422,"severity":"error","filePath":"app/models/provider/enable_banking.rb","lineNumber":296,"sourceCode":"      case response.code\n      when 200, 201\n        parse_response_body(response)\n      when 204\n        {}\n      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?","sourceCodeStart":278,"sourceCodeEnd":314,"githubUrl":"https://github.com/we-promise/sure/blob/e69894adb92547273377398c15f45c979cd9416a/app/models/provider/enable_banking.rb#L278-L314","documentation":"Raised when Enable Banking returns HTTP 422 - the request body/query is semantically invalid. The body is parsed with the strict parser (parse_response_body) and attached as response_data, so field-level API errors are inspectable. In this codebase the dominant 422 is WRONG_TRANSACTIONS_PERIOD: get_account_transactions already auto-retries it once with the ASPSP-suggested date_from, then walks fallback windows of 89/60/30 days; this error only escapes when those retries are exhausted or a different endpoint (POST /auth, POST /sessions) sends a bad payload.","triggerScenarios":"get_account_transactions with date_from older than the PSD2 consent grants (consent start or ~90 days), where even the 30-day fallback window is rejected; start_authorization with a malformed redirect_url or aspsp name/country pair; create_session with an already-consumed authorization code.","commonSituations":"European banks capping transaction history to the consent period (some PT banks return WRONG_TRANSACTIONS_PERIOD without a corrected date_from, the exact case the fallback windows were added for), consent re-authorized so the allowed window moved forward, hand-built aspsps name/country values that don't match get_aspsps output.","solutions":["Inspect e.response_data (e.g. response_data[:message] / error name) to see which field Enable Banking rejected","Clamp date_from to max(consent start, 89 days ago) before calling get_account_transactions so no retry chain is needed","Use e.wrong_transactions_period? / e.corrected_date_from on the raised error instead of string-matching the body","For auth/session 422s, verify redirect_url, state and the aspsp name/country against values returned by get_aspsps"],"exampleFix":"// before\nprovider.get_account_transactions(account_id: id, date_from: account.consent_given_at - 2.years, date_to: Date.current)\n\n// after\nearliest = [account.consent_given_at.to_date, 89.days.ago.to_date].max\nprovider.get_account_transactions(account_id: id, date_from: earliest, date_to: Date.current)","handlingStrategy":"validation","validationCode":"earliest = [account.consent_given_at.to_date, 89.days.ago.to_date].compact.max\nprovider.get_account_transactions(\n  account_id: id,\n  date_from: earliest,\n  date_to: [Date.current, account.consent_expires_at.to_date].compact.min\n)","typeGuard":"def wrong_transactions_period?(error)\n  error.is_a?(Provider::EnableBanking::EnableBankingError) &&\n    (error.wrong_transactions_period? || error.error_type == :validation_error)\nend","tryCatchPattern":"begin\n  provider.get_account_transactions(account_id: id, date_from: from, date_to: to)\nrescue Provider::EnableBanking::EnableBankingError => e\n  raise unless e.wrong_transactions_period?\n  provider.get_account_transactions(account_id: id,\n    date_from: e.corrected_date_from || 30.days.ago.to_date, date_to: to)\nend","preventionTips":["Clamp date_from to the consent start date before the first call","Track consent_given_at per connection and never request earlier than it","For auth/session calls, source aspsp name/country from get_aspsps output rather than hand-typed values","Log e.response_data whenever a 422 escapes - it carries the field-level reason"],"tags":["enable-banking","http-422","validation","transactions-period","psd2","consent"],"backgroundTag":"http-422-validation-failed","analyzedSha":"e69894adb92547273377398c15f45c979cd9416a","analyzedAt":"2026-08-21T18:22:41.165Z","schemaVersion":2},"datasetVersion":"2026-08-21T23:17:16.201Z"}