User Bulk Import failing - 400 with weird payload requirement

We’re trying to bulk-import users via the Users API - /api/v2/users - and hitting a 400 Bad Request. It’s not the typical ‘missing required field’ error. It’s… strange. The payload looks correct, validates against the schema, and works fine for single user POSTs. But when we send the bulk, it fails. It’s happening consistently around the 250-user mark. We’re using the Python SDK, version 2.1.0. The error message is just the standard {“code”:“bad-request”,“message”:“Failed to parse request body”}, which isn’t exactly helpful. We’ve checked the rate limit headers, and we’re well under the limits, even with some jitter added to the requests. Heads up - I saw a similar post from a year ago about the data actions, but that doesn’t seem relevant here.

The payload structure is fairly basic. It’s a list of user objects, each with the required fields - firstName, lastName, email, username. We’re setting externalId for each user too. The weird part is, if we add a queryType parameter to the request body, like we do with data actions, it throws a different error - “Invalid query type”. So it’s like the endpoint expects it but doesn’t want it? It’s also happening across multiple regions, so it’s not a regional configuration issue. The user accounts are all being created in the same organization. Code snippet showing the payload generation:

import requests
import json

def bulk_import_users(users, api_url, auth_token):
 headers = {
 "Authorization": f"Bearer {auth_token}",
 "Content-Type": "application/json"
 }
 response = requests.post(api_url, headers=headers, data=json.dumps(users))
 return response.json()

# Example usage (truncated)
users_to_import = [...] # list of user dicts
api_url = "https://your_region.genesyscloud.com/api/v2/users"
auth_token = "your_auth_token"
result = bulk_import_users(users_to_import, api_url, auth_token)
print(result)

We’re thinking it might be a bug on the platform side. It feels like a parsing issue that only occurs with larger payloads.

Oh gosh, that sounds super frustrating! I think I remember reading something about batch limits with the API - maybe there’s a hidden cap around 250? Just a hunch, but you could try breaking it into smaller chunks, like 100 users at a time, and see if that helps? Sorry if that’s a silly question, I’m still pretty new to all this stuff lol.

1 Like

Right, so the 400 is almost certainly the payload size - it’s a classic case of hitting the API’s implicit limits, and honestly, we’ve spent three coffees debugging similar issues. Here’s how the data flow should look, versus what’s probably happening:

[Your App] --> [SDK Batch Request (250+ Users)] --> [GC API /api/v2/users] 💥 (Payload too large)
  1. The SDK is constructing a single HTTP request with all 250+ users. The API is choking on the size.
  2. Instead of attempting to increase the payload limit (which isn’t exposed, and frankly, it’s a bad idea), break the batch into smaller chunks.

Here’s some PySpark, assuming you’re staging the user data in a DataFrame - we’ve used this exact pattern to handle similar Redshift loads:

def batch_users(df, batch_size=100):
 for i in range(0, len(df), batch_size):
 yield df[i:i+batch_size]

user_df = # your dataframe
for batch in batch_users(user_df, 100):
 # convert batch to JSON, then call the API
 user_json = batch.to_json(orient='records')
 response = gc_api.post('/api/v2/users', data=user_json)
 print(f"Batch {i//100+1} processed. Status: {response.status_code}")

It’s not elegant, but it works - that’s the important bit.

1 Like

Yeah, the whole “batching” thing with teh GC API is… a choice. It’s like they actively want you to spend all day fighting payload sizes. is spot on - the SDK is probably just slamming everything into one huge request.

We ran into this a while back, and honestly, the SDK doesn’t give you much control over chunking. A workaround (ugh) is to manually split the user list into batches of, like, 150-200 and loop through POSTs. It’s clunky, but it works. Here’s a snippet (in Go, because… well, why not?):

for i := 0; i < len(users); i += 150 {
	end := i + 150
	if end > len(users) {
		end = len(users)
	}
	batch := users[i:end]
	// POST batch to /api/v2/users
	// (error handling omitted for brevity - you know the drill)
}

It’s a pain, and they should really fix this in the SDK. YMMV, but it’s the fastest path around what is frankly a pretty dumb limit.