we-promise/sure · error · Provider::Questrade::ConfigurationError

missing_credentials

missing_credentials

Error message

Refresh token is required

What it means

Provider::Questrade's constructor calls validate_configuration!, which raises ConfigurationError(:missing_credentials) when the refresh_token keyword is nil or blank. It fires immediately at Provider::Questrade.new, before any network call — the client refuses to operate without a refresh token because Questrade has no other auth path.

Source

Thrown at app/models/provider/questrade.rb:116

      activities.concat(Array(page[:activities]))
      window_start = window_end + 1
    end

    { activities: activities }
  end

  private

    RETRYABLE_ERRORS = [
      SocketError, Net::OpenTimeout, Net::ReadTimeout,
      Errno::ECONNRESET, Errno::ECONNREFUSED, Errno::ETIMEDOUT, EOFError
    ].freeze

    MAX_RETRIES = 3
    INITIAL_RETRY_DELAY = 2 # seconds

    def validate_configuration!
      raise ConfigurationError.new("Refresh token is required", :missing_credentials) if @refresh_token.blank?
    end

    def get_json(path, query: {})
      ensure_authenticated!
      with_retries(path) do
        response = self.class.get("#{api_base}#{path}", headers: auth_headers, query: query)
        # Access token can expire mid-sync; refresh once and retry on 401.
        if response.code == 401
          authenticate!(force: true)
          response = self.class.get("#{api_base}#{path}", headers: auth_headers, query: query)
        end
        handle_response(response)
      end
    end

    # Exchange the refresh token unless we already hold a valid access token.
    def ensure_authenticated!
      authenticate! if @access_token.nil? || @access_expires_at.nil? || Time.current >= @access_expires_at

View on GitHub (pinned to e69894adb9)

Solutions

  1. Guard before constructing: check the item's stored questrade refresh token is present, and skip/queue the sync if blank.
  2. Verify you pass refresh_token: (snake_case keyword) and that the persisted field actually maps to it.
  3. If the token is genuinely missing, route the user through Questrade authorization to obtain the first refresh token.
  4. Search for code paths that instantiate the provider from partial or stale item state (e.g. after failed onboarding) and add presence checks there.

Example fix

# before
provider = Provider::Questrade.new(
  refresh_token: item.settings["refresh_token"], # nil -> raises in initialize
  api_server: item.settings["api_server"]
)

# after
token = item.settings["refresh_token"]
if token.blank?
  Rails.logger.info "Questrade item #{item.id} has no refresh token; re-auth required"
  next
end
provider = Provider::Questrade.new(refresh_token: token, api_server: item.settings["api_server"])
Defensive patterns

Strategy: validation

Validate before calling

token = item.settings["refresh_token"].to_s
raise ArgumentError, "Questrade refresh token missing" if token.blank?
provider = Provider::Questrade.new(refresh_token: token, api_server: item.settings["api_server"])

Try / catch

begin
  provider = Provider::Questrade.new(refresh_token: token, on_token_refresh: persister)
rescue Provider::Questrade::ConfigurationError => e
  raise unless e.error_type == :missing_credentials
  item.flag_reauthorization_required!(e)
end

Prevention

When it happens

Trigger: Provider::Questrade.new(refresh_token: nil) or refresh_token: "" — e.g. the persisted Questrade item has no token yet (user never finished authorization), the DB column was cleared, or a hash key typo ('refreshToken') yields nil via the missing default.

Common situations: Sync job firing before the OAuth return flow stored the first refresh token; seed/fixture data without a token; a migration wiping encrypted credentials; reading the wrong attribute off the item model (e.g. api_key instead of the questrade refresh token).

Related errors


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