we-promise/sure · error · Provider::Sophtron::Error
access_forbidden
access_forbidden
Error message
Access forbidden by Sophtron
What it means
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.
Source
Thrown at app/models/provider/sophtron.rb:361
"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)
JSON.parse(body, symbolize_names: true)
rescue JSON::ParserErrorView on GitHub (pinned to e69894adb9)
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
Example fix
# before - stale credentials silently kept item.update(user_id: new_user_id) # access_key forgotten # after - rotate both halves and verify with a cheap call item.update!(user_id: new_user_id, access_key: new_access_key) provider = item.sophtron_provider provider.get_users # raises :access_forbidden immediately if still wrong
Defensive patterns
Strategy: try-catch
Validate before calling
# Before syncing, confirm credentials are present and the key decodes key_bytes = Base64.decode64(item.access_key.to_s) raise ArgumentError, "access_key not valid base64" if key_bytes.blank? raise ArgumentError, "user_id missing" if item.user_id.blank?
Type guard
def sophtron_forbidden?(err) err.is_a?(Provider::Sophtron::Error) && err.error_type == :access_forbidden end
Try / catch
begin
provider.get_accounts(customer_id)
rescue Provider::Sophtron::Error => e
if e.error_type == :access_forbidden
mark_item_requires_update(e) # surface to user; do not retry
else
raise
end
end Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Understand the failure class
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
AI-assisted analysis of we-promise/sure@e69894adb9 (2026-08-21).
Data as JSON: /api/errors/e86ca5ffc75fedb7.
Report an issue: GitHub.