{"record":{"id":"1a51300a0d53eb90","repo":"we-promise/sure","slug":"bad-request-1a5130","errorCode":"bad_request","errorMessage":"Bad request to Sophtron API: #{body}","messagePattern":"Bad request to Sophtron API: #(.+?)","errorType":"exception","errorClass":"Provider::Sophtron::Error","httpStatus":400,"severity":"error","filePath":"app/models/provider/sophtron.rb","lineNumber":357,"sourceCode":"\n    def auth_headers(method:, api_path:)\n      {\n        \"Authorization\" => auth_header_for(method, api_path),\n        \"Content-Type\" => \"application/json\",\n        \"Accept\" => \"application/json\"\n      }\n    end\n\n    def handle_response(response, parse_json: true)\n      body = response.body.to_s\n\n      case response.code.to_i\n      when 200, 201, 204\n        return {} if body.strip.blank?\n\n        parse_json ? JSON.parse(body, symbolize_names: true) : parse_optional_json(body)\n      when 400\n        raise Error.new(\"Bad request to Sophtron API: #{body}\", :bad_request, details: body)\n      when 401\n        raise Error.new(\"Invalid Sophtron User ID or Access Key\", :unauthorized, details: body)\n      when 403\n        raise Error.new(\"Access forbidden by Sophtron\", :access_forbidden, details: body)\n      when 404\n        raise Error.new(\"Sophtron resource not found\", :not_found, details: body)\n      when 429\n        raise Error.new(\"Sophtron rate limit exceeded. Please try again later.\", :rate_limited, details: body)\n      else\n        raise Error.new(\n          \"Sophtron API request failed: #{response.code} #{response.message} - #{body}\",\n          :fetch_failed,\n          details: body\n        )\n      end\n    rescue JSON::ParserError => e\n      raise Error.new(\"Invalid JSON response from Sophtron API: #{e.message}\", :invalid_response, details: body)\n    end","sourceCodeStart":339,"sourceCodeEnd":375,"githubUrl":"https://github.com/we-promise/sure/blob/e69894adb92547273377398c15f45c979cd9416a/app/models/provider/sophtron.rb#L339-L375","documentation":"Raised by Provider::Sophtron's handle_response when the Sophtron API answers HTTP 400, meaning the request was well-formed enough to reach validation but the server rejected its parameters or body. The full response body is embedded in both the message and the error's details, since Sophtron's 400 bodies carry the specific validation message.","triggerScenarios":"A POST/GET with invalid parameters: malformed or unknown institution id when requesting account addition, invalid date parameters on a transaction query, a body that fails Sophtron's schema, or a job request referencing a nonexistent resource ID.","commonSituations":"Passing user-supplied institution or account IDs without validating them first; date strings not in the format Sophtron expects after a code change; Sophtron tightening request validation in an API update so previously accepted calls now 400.","solutions":["Read the body in the message/details - it names the exact invalid field per Sophtron's validation","Log and validate request parameters (IDs, dates) at the call site before invoking the client","Reproduce the exact request with curl and the same auth header to iterate quickly on the corrected payload","If calls that used to work now 400 after a Sophtron update, diff the current request body against their latest API docs"],"exampleFix":"# before - passing raw user input straight to the API\nclient.request(:post, \"/Institution/AddAccount\", body: {id: params[:institution_id]})\n\n# after - validate shape before the call\ninst_id = Integer(params[:institution_id], exception: false) or raise ArgumentError, \"bad institution id\"\nclient.request(:post, \"/Institution/AddAccount\", body: {id: inst_id})","handlingStrategy":"validation","validationCode":"def valid_sophtron_request?(method, api_path, body)\n  URI::HTTP === URI.parse(api_path) rescue false\n  body.nil? || body.is_a?(Hash)\nend\nraise ArgumentError, \"invalid Sophtron request\" unless valid_sophtron_request?(method, path, body)","typeGuard":null,"tryCatchPattern":"begin\n  client.request(:post, \"/Institution/AddAccount\", body: payload)\nrescue Provider::Sophtron::Error => e\n  if e.error_type == :bad_request\n    mark_payload_invalid(payload, e.message) # message embeds Sophtron's validation body\n  else\n    raise\n  end\nend","preventionTips":["Validate institution/account IDs and date formats at the call site before invoking the client","Log the embedded response body - Sophtron names the offending field","Add request fixtures per endpoint so parameter regressions surface in tests"],"tags":["sophtron","http-400","validation","bad-request","provider-api"],"backgroundTag":"http-400-bad-request","analyzedSha":"e69894adb92547273377398c15f45c979cd9416a","analyzedAt":"2026-08-21T18:22:41.165Z","schemaVersion":2},"datasetVersion":"2026-08-21T23:17:16.201Z"}