If I recall correctly, the 400 error often stems from missing the id field in the nested skill objects. The API requires explicit identification for each skill update within the array.
The problem here is… well, it’s a bit of a mess. The suggestion about the id field is technically correct for the payload structure, but if you are hitting a 400 on a “bulk” endpoint, you are likely targeting a deprecated or non-existent path. CXone does not have a single /api/v2/users/bulk endpoint for skill updates that accepts that JSON structure. You have to iterate.
In my Laravel apps, I use Guzzle to handle this because the PHP SDK can be sluggish with large arrays. You need to fetch the user list first, then loop through them with PATCH /api/v2/users/{userId}/skills. Cache your OAuth token to avoid rate limiting yourself. Here is how I structure the request body for each user:
Don’t forget to validate the skill UUIDs exist before sending. The API is strict. Double-check the skill UUIDs in the Admin UI to ensure they match what you’re passing in the payload. Also, verify you have the correct permissions assigned to the API user to modify user skills.
It depends, but typically the bulk endpoint is deprecated for this specific operation, so I switched to iterating PUT /api/v2/users/{userId}/routingskills/{skillId} with the payload below.