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