we-promise/sure · error · Provider::Snaptrade::AuthenticationError

Authentication failed (#{operation}): HTTP #{response.status

Error message

Authentication failed (#{operation}): HTTP #{response.status}

What it means

handle_response is the shared non-2xx handler for all SnapTrade data API calls. A 401 or 403 marks the item status :requires_update and raises AuthenticationError with the operation name and HTTP status. 401 means the Bearer access token was rejected (expired and refresh failed, revoked at SnapTrade, or never valid); 403 means the token is valid but the app/token lacks permission for that endpoint. Note request_json already tried one automatic refresh+replay on 401 before this can raise, so reaching here means the token is durably unusable.

Source

Thrown at app/models/provider/snaptrade.rb:387

    rescue StandardError => e
      Rails.logger.warn("SnapTrade: could not mark item requires_update: #{e.message}")
    end

    def handle_response(response, operation)
      if response.success?
        return {} if response.body.blank?
        begin
          JSON.parse(response.body)
        rescue JSON::ParserError
          raise ApiError.new("SnapTrade API error (#{operation}): invalid JSON response",
                             status_code: response.status, response_body: response.body)
        end
      else
        Rails.logger.error("SnapTrade API error (#{operation}): #{response.status}")
        case response.status
        when 401, 403
          mark_requires_update!
          raise AuthenticationError, "Authentication failed (#{operation}): HTTP #{response.status}"
        when 429
          raise ApiError.new("Rate limit exceeded. Please try again later.",
                             status_code: response.status, response_body: response.body)
        when 500..599
          raise ApiError.new("SnapTrade server error (#{response.status}). Please try again later.",
                             status_code: response.status, response_body: response.body)
        else
          raise ApiError.new("SnapTrade API error (#{operation}): HTTP #{response.status}",
                             status_code: response.status, response_body: response.body)
        end
      end
    end

    def api_connection
      @api_connection ||= Faraday.new do |faraday|
        faraday.options.timeout = 30
        faraday.options.open_timeout = 10
      end

View on GitHub (pinned to e69894adb9)

Solutions

  1. Check the item: status is now requires_update -- send the user through the OAuth authorize flow again
  2. If 403: review the OAuth app's granted scopes on dashboard.snaptrade.com and the scope requested in authorize_url (default 'read')
  3. If 401 persists immediately after re-auth, confirm server clock accuracy and that SNAPTRADE_OAUTH credentials match the dashboard app

Example fix

# before
begin
  provider.get_positions
rescue => e
  Rails.logger.error(e.message)
end

# after
begin
  provider.get_positions
rescue Provider::Snaptrade::AuthenticationError => e
  item.update!(status: :requires_update)
  NotifyUserReauthorizationJob.perform_later(item.id)
end
Defensive patterns

Strategy: try-catch

Try / catch

begin
  provider.get_positions
rescue Provider::Snaptrade::AuthenticationError => e
  # provider already set item.status = requires_update; never retry with the same token
  ReauthorizationPromptJob.perform_later(item.id)
end

Prevention

When it happens

Trigger: Any get_positions/get_connection_url/delete_connection call where SnapTrade returns 401 after the built-in refresh retry was exhausted (refresh failed with invalid_grant), or 403 for an endpoint outside the OAuth app's granted scope. The message names the failing operation, e.g. 'Authentication failed (GET /api/v1/positions): HTTP 401'.

Common situations: User disconnected/relinked the account at SnapTrade dashboard (old token revoked); token revoked via revoke_token elsewhere; app permissions changed on the SnapTrade side; long-lived background jobs using a token that was rotated by a newer session.

Understand the failure class

Related errors


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