CXone Admin API bulk update agent skills 400 error

Does anyone know why this fails? I am trying to bulk update agent skill proficiencies.

Background

I am using the CXone Admin API to update multiple agents at once.

Issue

The docs state “bulk operations accept an array of update requests.” I sent this payload:

POST /api/v2/users/skills/bulkUpdate
[
 { "userId": "123", "skillId": "456", "proficiency": "Expert" }
]

I get a 400 Bad Request.

Troubleshooting

Docs say the format is correct. Why does it fail?

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.

{
 "items": [
 {
 "userId": "user-uuid",
 "skills": [
 { "id": "skill-uuid", "proficiency": 5 }
 ]
 }
 ]
}

Note: Ensure you use client_credentials scope admin:agent:write.

The documentation actually says bulk endpoints are deprecated for skill assignments. Use the standard single-user endpoint in a loop within n8n.

  • Validate skill UUIDs exist.
  • Use admin:agent:write scope.
  • Implement retry logic for rate limits.
PATCH /api/v2/users/{userId}/skills
Content-Type: application/json
[{"id":"skill-uuid","proficiency":5}]

The problem here is… well, it’s a bit of a mess. The suggestion above 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. Genesys Cloud 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:

// Inside your loop for each user
$payload = [
 'skills' => [
 [
 'id' => 'skill-uuid-here',
 'proficiency' => 5
 ]
 ]
];

$client->patch("/api/v2/users/{$userId}/skills", [
 'json' => $payload,
 'headers' => [
 'Authorization' => 'Bearer ' . $accessToken,
 'Content-Type' => 'application/json'
 ]
]);

Don’t forget to validate the skill UUIDs exist before sending. The API is strict.

It depends, but typically the bulk endpoint is deprecated for this specific operation, so I switched to iterating PATCH /api/v2/users/{userId}/skills with the payload below.

{
 "id": "skill-uuid",
 "proficiency": 5
}