we-promise/sure · critical · Provider::Sophtron::Error

unauthorized

unauthorized

Error message

Invalid Sophtron User ID or Access Key

What it means

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.

Source

Thrown at app/models/provider/sophtron.rb:359

      {
        "Authorization" => auth_header_for(method, api_path),
        "Content-Type" => "application/json",
        "Accept" => "application/json"
      }
    end

    def handle_response(response, parse_json: true)
      body = response.body.to_s

      case response.code.to_i
      when 200, 201, 204
        return {} if body.strip.blank?

        parse_json ? JSON.parse(body, symbolize_names: true) : parse_optional_json(body)
      when 400
        raise Error.new("Bad request to Sophtron API: #{body}", :bad_request, details: body)
      when 401
        raise Error.new("Invalid Sophtron User ID or Access Key", :unauthorized, details: body)
      when 403
        raise Error.new("Access forbidden by Sophtron", :access_forbidden, details: body)
      when 404
        raise Error.new("Sophtron resource not found", :not_found, details: body)
      when 429
        raise Error.new("Sophtron rate limit exceeded. Please try again later.", :rate_limited, details: body)
      else
        raise Error.new(
          "Sophtron API request failed: #{response.code} #{response.message} - #{body}",
          :fetch_failed,
          details: body
        )
      end
    rescue JSON::ParserError => e
      raise Error.new("Invalid JSON response from Sophtron API: #{e.message}", :invalid_response, details: body)
    end

    def parse_optional_json(body)

View on GitHub (pinned to e69894adb9)

Solutions

  1. Re-copy BOTH user_id and access_key from the Sophtron dashboard - they are a pair and must rotate together
  2. Confirm the base_url matches the credential environment (test vs production) in Provider::Sophtron.new
  3. Check the stored key for trailing whitespace/newlines: Base64.decode64(key).strip comparison catches paste artifacts
  4. If 401 persists with fresh credentials, verify the account's API access is still active with Sophtron support, quoting the details body
Defensive patterns

Strategy: validation

Validate before calling

key = credentials.access_key.to_s.strip
user = credentials.user_id.to_s.strip
raise ArgumentError, "Sophtron user_id missing" if user.blank?
raise ArgumentError, "Sophtron access_key missing or decodes empty" if key.blank? || Base64.decode64(key).bytesize.zero?
# optional smoke test before real work:
Provider::Sophtron.new(user, key).request(:get, "/Institution/allInstitutions")

Try / catch

begin
  client.get_accounts
rescue Provider::Sophtron::Error => e
  if e.error_type == :unauthorized
    mark_credentials_stale # surface 'reconnect Sophtron' to the user; do not retry
  else
    raise
  end
end

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of we-promise/sure@e69894adb9 (2026-08-21). Data as JSON: /api/errors/4de8b0cfbfd84464. Report an issue: GitHub.