{"record":{"id":"2b4f684198490cf7","repo":"we-promise/sure","slug":"rate-limit-exceeded-please-try-again-later","errorCode":null,"errorMessage":"Rate limit exceeded. Please try again later.","messagePattern":"Rate limit exceeded\\. Please try again later\\.","errorType":"exception","errorClass":"Provider::Snaptrade::ApiError","httpStatus":429,"severity":"warning","filePath":"app/models/provider/snaptrade.rb","lineNumber":389,"sourceCode":"    end\n\n    def handle_response(response, operation)\n      if response.success?\n        return {} if response.body.blank?\n        begin\n          JSON.parse(response.body)\n        rescue JSON::ParserError\n          raise ApiError.new(\"SnapTrade API error (#{operation}): invalid JSON response\",\n                             status_code: response.status, response_body: response.body)\n        end\n      else\n        Rails.logger.error(\"SnapTrade API error (#{operation}): #{response.status}\")\n        case response.status\n        when 401, 403\n          mark_requires_update!\n          raise AuthenticationError, \"Authentication failed (#{operation}): HTTP #{response.status}\"\n        when 429\n          raise ApiError.new(\"Rate limit exceeded. Please try again later.\",\n                             status_code: response.status, response_body: response.body)\n        when 500..599\n          raise ApiError.new(\"SnapTrade server error (#{response.status}). Please try again later.\",\n                             status_code: response.status, response_body: response.body)\n        else\n          raise ApiError.new(\"SnapTrade API error (#{operation}): HTTP #{response.status}\",\n                             status_code: response.status, response_body: response.body)\n        end\n      end\n    end\n\n    def api_connection\n      @api_connection ||= Faraday.new do |faraday|\n        faraday.options.timeout = 30\n        faraday.options.open_timeout = 10\n      end\n    end\n","sourceCodeStart":371,"sourceCodeEnd":407,"githubUrl":"https://github.com/we-promise/sure/blob/e69894adb92547273377398c15f45c979cd9416a/app/models/provider/snaptrade.rb#L371-L407","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Back off and retry later - honor any Retry-After header from the provider; exponential backoff with jitter is safest","Throttle the sync loop: insert a small delay between per-account API calls so bursts stay under the limit","Serialize competing syncs per user (lock/flag) so autosync and manual refresh never double-hammer the API","If limits are chronically hit, review the SnapTrade plan's quota or reduce polling frequency"],"exampleFix":"# before - unthrottled account loop\naccounts.each { |a| client.get_positions(account_id: a.id) }\n\n# after - throttle plus backoff on 429\naccounts.each do |a|\n  begin\n    client.get_positions(account_id: a.id)\n  rescue Provider::Snaptrade::ApiError => e\n    sleep(backoff) and retry if e.status_code == 429\n    raise\n  end\n  sleep(0.2)\nend","handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"attempts = 0\nbegin\n  snaptrade.get_positions(account_id: id)\nrescue Provider::Snaptrade::ApiError => e\n  raise unless e.status_code == 429\n  attempts += 1\n  raise if attempts > 3\n  sleep(backoff_with_jitter(attempts))\n  retry\nend","preventionTips":["Throttle per-account calls in multi-account sync loops (small inter-request sleep)","Serialize autosync and manual refresh per user so they never run concurrently","Watch for chronic 429s as a signal to raise plan limits or cut polling frequency"],"tags":["snaptrade","rate-limit","http-429","backoff","throttling"],"backgroundTag":"http-429-rate-limited","analyzedSha":"e69894adb92547273377398c15f45c979cd9416a","analyzedAt":"2026-08-21T18:22:41.165Z","schemaVersion":2},"datasetVersion":"2026-08-21T23:17:16.201Z"}