{"record":{"id":"e86ca5ffc75fedb7","repo":"we-promise/sure","slug":"access-forbidden-e86ca5","errorCode":"access_forbidden","errorMessage":"Access forbidden by Sophtron","messagePattern":"Access forbidden by Sophtron","errorType":"exception","errorClass":"Provider::Sophtron::Error","httpStatus":403,"severity":"error","filePath":"app/models/provider/sophtron.rb","lineNumber":361,"sourceCode":"        \"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)\n      JSON.parse(body, symbolize_names: true)\n    rescue JSON::ParserError","sourceCodeStart":343,"sourceCodeEnd":379,"githubUrl":"https://github.com/we-promise/sure/blob/e69894adb92547273377398c15f45c979cd9416a/app/models/provider/sophtron.rb#L343-L379","documentation":"Provider::Sophtron (an HTTParty-based client for the Sophtron bank-aggregation API) raises this Error when the upstream responds with HTTP 403. The error carries error_type=:access_forbidden and the raw body in details. It means Sophtron recognized the request but rejected the FIApiAUTH HMAC signature/user ID as not permitted for the requested operation.","triggerScenarios":"Any Sophtron V2 REST call (e.g. customer provisioning) or V1 RPC call (institution add, job polling, accounts/transactions fetch) whose response code is 403. Typically caused by a User ID/access key pair that is valid but not entitled to that endpoint, an access key from a different Sophtron environment, or a rotated/revoked key.","commonSituations":"Rotating the Sophtron access key without updating the SophtronItem credentials; using a sandbox key against api.sophtron.com (or vice versa via a custom base_url); API plan that does not include the institution/endpoint being called; copy-paste truncation of the Base64 access key so the HMAC signature no longer matches.","solutions":["Regenerate the access key in the Sophtron developer portal and re-save user_id/access_key on the SophtronItem, then retry the same call","Verify the base_url stored on the SophtronItem matches the environment the key was issued for (the client strips a trailing /v2 in normalize_base_url)","Confirm with Sophtron support that your API user is entitled to the endpoint being invoked (some V1 RPC endpoints require elevated permission)","Inspect e.details (the 403 response body) for Sophtron's specific denial reason before escalating"],"exampleFix":"# before - stale credentials silently kept\nitem.update(user_id: new_user_id) # access_key forgotten\n\n# after - rotate both halves and verify with a cheap call\nitem.update!(user_id: new_user_id, access_key: new_access_key)\nprovider = item.sophtron_provider\nprovider.get_users # raises :access_forbidden immediately if still wrong","handlingStrategy":"try-catch","validationCode":"# Before syncing, confirm credentials are present and the key decodes\nkey_bytes = Base64.decode64(item.access_key.to_s)\nraise ArgumentError, \"access_key not valid base64\" if key_bytes.blank?\nraise ArgumentError, \"user_id missing\" if item.user_id.blank?","typeGuard":"def sophtron_forbidden?(err)\n  err.is_a?(Provider::Sophtron::Error) && err.error_type == :access_forbidden\nend","tryCatchPattern":"begin\n  provider.get_accounts(customer_id)\nrescue Provider::Sophtron::Error => e\n  if e.error_type == :access_forbidden\n    mark_item_requires_update(e) # surface to user; do not retry\n  else\n    raise\n  end\nend","preventionTips":["Rotate both user_id and access_key together and verify with a cheap call before scheduling syncs","Keep one Sophtron API user per environment so quota and entitlements are isolated","Store the base_url alongside credentials so keys are never used against the wrong environment"],"tags":["sophtron","http-403","hmac-auth","aggregator","api-credentials"],"backgroundTag":"http-403-forbidden","analyzedSha":"e69894adb92547273377398c15f45c979cd9416a","analyzedAt":"2026-08-21T18:22:41.165Z","schemaVersion":2},"datasetVersion":"2026-08-21T23:17:16.201Z"}