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
- 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
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
- 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
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.