Skip to content

Rate limiting is applied at multiple independent layers. Limits are configurable per account and may change — treat the numbers below as approximate.

LayerScopeApproximate limitOn exceed
Per-IP generalAll requests from one IPConfigurable (high)HTTP 429, no JSON body
Per-IP authLogin endpoint per IP~5 req/min429 TOO_MANY_ATTEMPTS
Login brute-forcePer login name, progressiveProgressive delay429 TOO_MANY_ATTEMPTS + Retry-After
2FA brute-forcePer user + IP, progressiveProgressive delay429 TOO_MANY_ATTEMPTS + Retry-After
Per-accountAll requests from one accountDefault ~1,000 req/s429 RATE_LIMIT_EXCEEDED

Notes worth coding against:

  • Only the login brute-force limiter sends Retry-After. Other 429s do not.
  • The per-IP general limiter returns a bare 429 with no JSON body — don’t assume every 429 parses as JSON.
  • The per-account limiter is shared across all team members and keys on the account, for both JWT and API-key traffic.
  • An edge-proxy limit also exists in front of the application and returns 429 with an HTML body.
attempt = 0
while attempt < max_retries:
response = send_request()
if response.status != 429:
break
delay = retry_after_header or (base_delay * 2 ** attempt) + jitter
sleep(delay)
attempt += 1
  • Exponential backoff with jitter, starting around 1s.
  • Honor Retry-After when present.
  • Spread batch traffic — for sustained high volume, a steady stream beats bursts that bounce off the limiter.