we-promise/sure · warning · Provider::Snaptrade::ApiError

Rate limit exceeded. Please try again later.

Error message

Rate limit exceeded. Please try again later.

What it means

Raised by Provider::Snaptrade.handle_response when the SnapTrade API answers HTTP 429. The ApiError carries status_code: 429 and the raw body (which may include a Retry-After hint). Note the mark_requires_update! path is not taken for 429 - the connection is fine, the caller is simply over the rate limit for its SnapTrade plan/window.

Source

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

    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
    end

View on GitHub (pinned to e69894adb9)

Solutions

  1. Back off and retry later - honor any Retry-After header from the provider; exponential backoff with jitter is safest
  2. Throttle the sync loop: insert a small delay between per-account API calls so bursts stay under the limit
  3. Serialize competing syncs per user (lock/flag) so autosync and manual refresh never double-hammer the API
  4. If limits are chronically hit, review the SnapTrade plan's quota or reduce polling frequency

Example fix

# before - unthrottled account loop
accounts.each { |a| client.get_positions(account_id: a.id) }

# after - throttle plus backoff on 429
accounts.each do |a|
  begin
    client.get_positions(account_id: a.id)
  rescue Provider::Snaptrade::ApiError => e
    sleep(backoff) and retry if e.status_code == 429
    raise
  end
  sleep(0.2)
end
Defensive patterns

Strategy: retry

Try / catch

attempts = 0
begin
  snaptrade.get_positions(account_id: id)
rescue Provider::Snaptrade::ApiError => e
  raise unless e.status_code == 429
  attempts += 1
  raise if attempts > 3
  sleep(backoff_with_jitter(attempts))
  retry
end

Prevention

When it happens

Trigger: Bursty polling of /accounts, /balances, or /positions during a multi-account sync; concurrent jobs (autosync + manual refresh) double-hitting the API; scheduled jobs syncing many SnapTrade users in the same window.

Common situations: A user spamming the refresh button; a backfill/import job iterating hundreds of accounts without throttling; SnapTrade tightening per-client limits; multiple app instances sharing one client_id quota.

Related errors


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