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

missing_api_server

missing_api_server

Error message

No api_server; authenticate first

What it means

Raised by Provider::Questrade#api_base: every data request builds its URL from @api_server (the per-session base URL Questrade returns in the token exchange), and if it is blank you get ConfigurationError(:missing_api_server). It means the client tried to call a data endpoint before a successful exchange established api_server, or the caller passed api_server: nil and never authenticated.

Source

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

      end

      body = JSON.parse(response.body, symbolize_names: true)
      @access_token      = body[:access_token]
      @api_server        = body[:api_server]
      @refresh_token     = body[:refresh_token] # rotate in-memory immediately
      @access_expires_at = Time.current + (body[:expires_in].to_i - ACCESS_TOKEN_SKEW).seconds

      # Hand the new credentials to the caller to persist (single-use token!).
      @on_token_refresh&.call(
        refresh_token: @refresh_token,
        api_server:    @api_server,
        access_token:  @access_token,
        expires_at:    @access_expires_at
      )
    end

    def api_base
      raise ConfigurationError.new("No api_server; authenticate first", :missing_api_server) if @api_server.blank?
      @api_server.end_with?("/") ? @api_server : "#{@api_server}/"
    end

    def auth_headers
      {
        "Authorization" => "Bearer #{@access_token}",
        "Accept" => "application/json"
      }
    end

    def iso(time)
      time.utc.iso8601
    end

    def with_retries(operation_name, max_retries: MAX_RETRIES)
      retries = 0

      begin

View on GitHub (pinned to e69894adb9)

Solutions

  1. Always run authentication before data calls: the public methods go through get_json -> ensure_authenticated!, so make sure nothing bypasses it (a direct get_json call in a subclass or spec will hit this).
  2. Persist api_server alongside the refresh token via on_token_refresh (the callback hands you api_server), and pass it back on the next construct so no exchange is needed.
  3. If you stub/mock in tests, stub the exchange to return an api_server or stub get_json entirely.
  4. Check the persisted item: blank api_server with a present refresh token means the callback did not save it — fix the persistence path.

Example fix

# before
provider = Provider::Questrade.new(refresh_token: token, api_server: nil)
provider.get_holdings(account_id: id) # api_base raises missing_api_server

# after
provider = Provider::Questrade.new(
  refresh_token: token,
  api_server: item.settings["api_server"], # restored from last exchange
  on_token_refresh: ->(creds) { item.update_credentials!(creds) }
)
provider.list_accounts # triggers exchange which sets api_server
Defensive patterns

Strategy: validation

Validate before calling

api_server = item.settings["api_server"]
if api_server.blank? && item.settings["refresh_token"].blank?
  raise ArgumentError, "cannot reach Questrade without credentials; authenticate first"
end
# get_json -> ensure_authenticated! sets api_server before any data call

Try / catch

begin
  provider.get_holdings(account_id: id)
rescue Provider::Questrade::ConfigurationError => e
  raise unless e.error_type == :missing_api_server
  provider.exchange_token! # establish api_server, then retry
  retry
end

Prevention

When it happens

Trigger: Constructing with api_server: nil and calling get_json before ensure_authenticated!/exchange_token! has run; a token exchange body missing :api_server so @api_server stays nil; a new process instance created per request but only used for data calls without re-authenticating.

Common situations: Refactor that caches a provider instance across an exchange boundary; persisted item having the refresh token but a blank api_server column; stubbing exchange in tests so @api_server is never set, then hitting a data method.

Understand the failure class

Related errors


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