we-promise/sure · critical · Provider::Simplefin::SimplefinError

access_forbidden

access_forbidden

Error message

Access URL is no longer valid

What it means

Raised by Provider::Simplefin#get_accounts when the accounts endpoint returns 403, with error_type :access_forbidden. The claimed access URL embeds HTTP Basic Auth credentials; a 403 means those credentials were revoked server-side. This is permanent for that URL — the user must issue a new setup token and re-claim.

Source

Thrown at app/models/provider/simplefin.rb:92

    accounts_url = "#{access_url}/accounts"
    accounts_url += "?#{URI.encode_www_form(query_params)}" unless query_params.empty?

    # The access URL already contains HTTP Basic Auth credentials
    # Use retry logic with exponential backoff for transient network failures
    # Use self.class.get to inherit class-level SSL and timeout defaults
    response = with_retries("GET /accounts") do
      self.class.get(accounts_url)
    end

    case response.code
    when 200
      JSON.parse(response.body, symbolize_names: true)
    when 400
      Rails.logger.error "SimpleFin API: Bad request - #{response.body}"
      raise SimplefinError.new("Bad request to SimpleFin API: #{response.body}", :bad_request)
    when 403
      raise SimplefinError.new("Access URL is no longer valid", :access_forbidden)
    when 402
      raise SimplefinError.new("Payment required to access this account", :payment_required)
    when 429
      Rails.logger.warn "SimpleFin API: Rate limited - #{response.body}"
      raise SimplefinError.new("SimpleFin rate limit exceeded. Please try again later.", :rate_limited)
    when 500..599
      Rails.logger.error "SimpleFin API: Server error - Code: #{response.code}, Body: #{response.body}"
      raise SimplefinError.new("SimpleFin server error (#{response.code}). Please try again later.", :server_error)
    else
      Rails.logger.error "SimpleFin API: Unexpected response - Code: #{response.code}, Body: #{response.body}"
      raise SimplefinError.new("Failed to fetch accounts: #{response.code} #{response.message} - #{response.body}", :fetch_failed)
    end
  end

  def get_info(base_url)
    # Use self.class.get to inherit class-level SSL and timeout defaults
    response = self.class.get("#{base_url}/info")

View on GitHub (pinned to e69894adb9)

Solutions

  1. Have the user generate a new setup token and run the claim flow again
  2. Clear the stored access URL immediately so jobs stop hitting the revoked credentials
  3. Mark the SimpleFin connection as needing re-link and notify the user
  4. Distinguish this from a one-time claim failure: here the URL worked before and has since been revoked

Example fix

# before
def sync(account)
  data = client.get_accounts(account.access_url)
end

# after
def sync(account)
  data = client.get_accounts(account.access_url)
rescue Provider::Simplefin::SimplefinError => e
  raise unless e.error_type == :access_forbidden
  account.update!(access_url: nil, needs_relink: true)
  UserMailer.simplefin_relink(account.user).deliver_later
end
Defensive patterns

Strategy: try-catch

Validate before calling

def simplefin_url_alive?(client, base_url)
  client.get_info(base_url)
  true
rescue Provider::Simplefin::SimplefinError
  false
end

Type guard

def simplefin_access_revoked?(error)
  error.is_a?(Provider::Simplefin::SimplefinError) && error.error_type == :access_forbidden
end

Try / catch

begin
  client.get_accounts(account.access_url)
rescue Provider::Simplefin::SimplefinError => e
  raise unless e.error_type == :access_forbidden
  account.update!(access_url: nil, status: "needs_relink")
  UserMailer.simplefin_relink(account.user).deliver_later
end

Prevention

When it happens

Trigger: User clicked 'reset/revoke access' at their SimpleFin bridge; bridge rotated credentials; the access URL was claimed from a different account context and got invalidated.

Common situations: Bank re-link flows where the old access URL persists locally; user troubleshooting by resetting the SimpleFin connection; bridge migrations invalidating old URLs.

Related errors


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