Unexpected 429s on /api/v2/users with seemingly low request volume

Hi all,

We’re seeing intermittent 429 Too Many Requests errors on the /api/v2/users endpoint, specifically when retrieving user details by email address. It’s happening in production, impacting our provisioning process. Oddly, the request rate appears low - under 10 requests per second, spread across several organizations. The API client is using the Python SDK, version 23.1.1.

Is anyone else hitting this? It feels like a hidden per-org throttle that isn’t documented. The errors are appearing even after implementing exponential backoff with a jitter of 1-2 seconds. It’s a bit strange.

Here’s what we’ve checked so far:

  • Confirmed the SDK is correctly handling retries. We’re logging all requests and responses.
  • Checked organization-level rate limits in Genesys Cloud admin - nothing obviously restricted.
  • Verified the user search query isn’t excessively broad. We’re targeting individual email addresses.
  • Tested with different organizations - the issue isn’t isolated to a single tenant.
  • The throttling appears to be more frequent during peak business hours (CET).

The logs consistently show this header when the 429s occur: X-RateLimit-Remaining: 1, X-RateLimit-Limit: 1. Which is… concerning. Anyone have experience with similar behavior? A screenshot of a typical error response might help illustrate it further.

1 Like

Right, so the 429s on users. Wonderful. Because the API design team clearly thinks everyone wants to hammer that endpoint all day, doesn’t it? Honestly, the rate limiting on the user endpoint is… peculiar. It’s not a straightforward requests-per-minute limit, it’s a leaky bucket that takes into account the number of organizations you’re hitting. Which, ugh.

The Python SDK doesn’t really expose much in the way of fine-grained rate limit handling - it does the basic exponential backoff, but that’s about it. What you’ll need to do is wrap your calls in something a little more sophisticated. Here’s a snippet that attempts to manage this, albeit imperfectly. It’s a basic approach, obviously, but it’s better than letting the SDK choke.

import time
import requests

MAX_RETRIES = 5
RETRY_DELAY = 2 # seconds

def get_user_with_retry(api_url, email, headers):
 for attempt in range(MAX_RETRIES):
 try:
 response = requests.get(api_url.format(email=email), headers=headers)
 response.raise_for_status() # Raise HTTPError for bad responses (4xx or 5xx)
 return response.json()
 except requests.exceptions.HTTPError as e:
 if response.status_code == 429:
 print(f"Rate limited - waiting {RETRY_DELAY} seconds. Attempt {attempt + 1}")
 time.sleep(RETRY_DELAY)
 else:
 print(f"Error fetching user: {e}")
 raise
 print("Max retries reached.")
 return None

# Example Usage (replace with your actual API URL and headers)
api_url = "/api/v2/users/{email}" # ffs, why is it formatted this way?
email = "test.user@example.com"
headers = {"Authorization": "Bearer YOUR_API_TOKEN"} # obviously replace this

user_data = get_user_with_retry(api_url, email, headers)
if user_data:
 print(user_data)

The key is to catch the 429, wait, and retry. The requests library is used directly to avoid the SDK’s opaque error handling - it’s slightly more involved, but you’ll at least see why the call failed. And honestly, you’ll probably need to adjust the RETRY_DELAY and MAX_RETRIES based on testing. This won’t eliminate the problem entirely, but it buys you some stability. You might also consider queuing requests if you’re doing bulk operations - hammering the API isn’t a sustainable strategy, after all.