{"record":{"id":"4de8b0cfbfd84464","repo":"we-promise/sure","slug":"unauthorized-4de8b0","errorCode":"unauthorized","errorMessage":"Invalid Sophtron User ID or Access Key","messagePattern":"Invalid Sophtron User ID or Access Key","errorType":"exception","errorClass":"Provider::Sophtron::Error","httpStatus":401,"severity":"critical","filePath":"app/models/provider/sophtron.rb","lineNumber":359,"sourceCode":"      {\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\n\n    def parse_optional_json(body)","sourceCodeStart":341,"sourceCodeEnd":377,"githubUrl":"https://github.com/we-promise/sure/blob/e69894adb92547273377398c15f45c979cd9416a/app/models/provider/sophtron.rb#L341-L377","documentation":"Raised by Provider::Sophtron's handle_response when the Sophtron API answers HTTP 401: the FIApiAUTH signature/header did not authenticate. The user_id/access_key pair is wrong (signature computed with a mismatched key) or the user ID is unknown - the HMAC signature itself was built fine (a broken key fails earlier as invalid_access_key), but Sophtron rejected it. The response body is attached as details.","triggerScenarios":"Every authenticated Sophtron call fails immediately after credentials rotate: access_key regenerated in the Sophtron dashboard while the app still signs with the old one; user_id typo'd or belonging to a different environment (test key against production base_url); key copied with a missing character so the signature verifies as invalid.","commonSituations":"Key rotation where only one of user_id/access_key was updated; environment crossover (staging credentials against the production base_url); trailing whitespace/newline in a pasted key altering the HMAC; expired or revoked Sophtron API access.","solutions":["Re-copy BOTH user_id and access_key from the Sophtron dashboard - they are a pair and must rotate together","Confirm the base_url matches the credential environment (test vs production) in Provider::Sophtron.new","Check the stored key for trailing whitespace/newlines: Base64.decode64(key).strip comparison catches paste artifacts","If 401 persists with fresh credentials, verify the account's API access is still active with Sophtron support, quoting the details body"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"key = credentials.access_key.to_s.strip\nuser = credentials.user_id.to_s.strip\nraise ArgumentError, \"Sophtron user_id missing\" if user.blank?\nraise ArgumentError, \"Sophtron access_key missing or decodes empty\" if key.blank? || Base64.decode64(key).bytesize.zero?\n# optional smoke test before real work:\nProvider::Sophtron.new(user, key).request(:get, \"/Institution/allInstitutions\")","typeGuard":null,"tryCatchPattern":"begin\n  client.get_accounts\nrescue Provider::Sophtron::Error => e\n  if e.error_type == :unauthorized\n    mark_credentials_stale # surface 'reconnect Sophtron' to the user; do not retry\n  else\n    raise\n  end\nend","preventionTips":["Rotate user_id and access_key together - they are a signing pair","Match credential environment to base_url (test vs production) explicitly in config","Strip whitespace when storing pasted keys; run a cheap authenticated call at link time to fail fast"],"tags":["sophtron","http-401","unauthorized","credentials","hmac"],"backgroundTag":"http-401-unauthorized","analyzedSha":"e69894adb92547273377398c15f45c979cd9416a","analyzedAt":"2026-08-21T18:22:41.165Z","schemaVersion":2},"datasetVersion":"2026-08-21T23:17:16.201Z"}