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::ParserError

View on GitHub (pinned to e69894adb9)

Solutions

  1. Regenerate the access key in the Sophtron developer portal and re-save user_id/access_key on the SophtronItem, then retry the same call
  2. 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)
  3. Confirm with Sophtron support that your API user is entitled to the endpoint being invoked (some V1 RPC endpoints require elevated permission)
  4. 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

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

Related errors


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