we-promise/sure · warning · Provider::Binance::RateLimitError
Rate limit exceeded
Error message
Rate limit exceeded
What it means
Raised by Provider::Binance#handle_response when Binance returns HTTP 429. Binance enforces IP-based request-weight limits (e.g. 6,000 weight/min per IP on spot); each endpoint costs weight, and when the bucket is exhausted every request gets 429. Sustained 429s escalate to HTTP 418 (IP auto-ban), so this error must be handled with backoff, not immediate retries.
Source
Thrown at app/models/provider/binance.rb:184
def sign(params)
query_string = params.is_a?(Hash) ? URI.encode_www_form(params.sort) : params
OpenSSL::HMAC.hexdigest("sha256", api_secret, query_string)
end
def auth_headers
{ "X-MBX-APIKEY" => api_key }
end
def handle_response(response)
parsed = response.parsed_response
case response.code
when 200..299
parsed
when 401
raise AuthenticationError, extract_error_message(parsed) || "Unauthorized"
when 429
raise RateLimitError, "Rate limit exceeded"
else
msg = extract_error_message(parsed) || "API error: #{response.code}"
raise InvalidSymbolError, msg if parsed.is_a?(Hash) && parsed["code"] == -1121
raise ApiError, msg
end
end
def extract_error_message(parsed)
return parsed if parsed.is_a?(String)
return nil unless parsed.is_a?(Hash)
parsed["msg"] || parsed["message"] || parsed["error"]
end
end
View on GitHub (pinned to e69894adb9)
Solutions
- Retry with exponential backoff honoring the Retry-After header (start >= 60s for weight-limit bans; 2s for transient bursts).
- Throttle request emission: include/throttle via Provider::RateLimitable or set a min interval between calls in the loop.
- Reduce weight: fetch wider kline intervals/ranges per request instead of many small calls, and batch symbols where the endpoint allows.
- If the IP is shared, lower per-process concurrency (Sidekiq concurrency, job queues) or move Binance calls to a single dedicated worker.
- If requests now fail with 418, the IP is temporarily banned — stop retrying and wait out the ban window (typically 2 min to 3 days) before resuming at a lower rate.
Example fix
# before prices = provider.fetch_security_prices(symbol: s, exchange_operating_mic: mic, start_date: from, end_date: to) # after attempts = 0 begin attempts += 1 prices = provider.fetch_security_prices(symbol: s, exchange_operating_mic: mic, start_date: from, end_date: to) rescue Provider::Binance::RateLimitError raise if attempts >= 5 sleep(2**attempts) retry end
Defensive patterns
Strategy: retry
Type guard
def binance_rate_limited?(err) err.is_a?(Provider::Binance::RateLimitError) end
Try / catch
attempts = 0 begin attempts += 1 result = provider.fetch_security_prices(symbol: s, exchange_operating_mic: mic, start_date: from, end_date: to) rescue Provider::Binance::RateLimitError raise if attempts >= 5 sleep(2**attempts + rand(2)) retry end
Prevention
- Throttle request emission via Provider::RateLimitable or a min interval between calls.
- Prefer fewer, wider kline requests over many narrow ones to lower request weight.
- Serialize Binance calls through one worker/queue so concurrency cannot burst past the IP weight cap.
- Monitor for HTTP 418 follow-ons: on 418 stop retrying entirely and wait out the IP ban.
- Add jittered exponential backoff on 429; never retry immediately.
When it happens
Trigger: Calling fetch_security_prices or account endpoints in a tight loop (backfilling many securities) so weight accumulates past the per-minute cap; batch imports that page klines per symbol without a delay; multiple app instances or jobs sharing one egress IP; running alongside other Binance tooling on the same server.
Common situations: A nightly backfill job iterating hundreds of symbols; Sidekiq concurrency multiplying requests; deploying to a shared NAT gateway whose IP is also used by other Binance clients; forgetting that Binance counts weight per IP, not per API key; getting intermittent 429s that become permanent 418 because code retries without waiting.
Related errors
AI-assisted analysis of we-promise/sure@e69894adb9 (2026-08-21).
Data as JSON: /api/errors/f278c7280fb2f0e6.
Report an issue: GitHub.