CXone Admin API bulk agent skill update returning 400 Bad Request

I’m trying to automate our weekly skill proficiency updates for about 200 agents using the CXone Admin API. I’ve been handling this manually in the UI for months, but with the roster changes getting more frequent, I need a script to push these updates. I’m using Python with the requests library since I need to handle the OAuth token refresh manually for this internal tool.

The endpoint I’m hitting is PUT /api/v2/users/{userId}/skills. According to the docs, I can send an array of skill objects in the body. Here’s what my request looks like:

import requests
import json

headers = {
 'Authorization': f'Bearer {access_token}',
 'Content-Type': 'application/json'
}

payload = [
 {
 "skillId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
 "proficiency": 1.0
 },
 {
 "skillId": "b2c3d4e5-f6a7-8901-bcde-f12345678901",
 "proficiency": 0.8
 }
]

response = requests.put(
 f'https://{org_domain}.mypurecloud.com/api/v2/users/{user_id}/skills',
 headers=headers,
 data=json.dumps(payload)
)

print(response.status_code)
print(response.text)

I’m getting a 400 Bad Request back. The error message in the body is just {"errors":["Invalid input"]}. It’s not very helpful. I’ve verified that the skillIds exist and are valid by fetching them separately. The proficiency values are floats between 0 and 1, which should be fine.

I tried sending a single skill in the array, same result. I also tried sending an empty array, which actually worked (it cleared all skills), so the auth and endpoint seem okay.

Is there a specific format required for the bulk update that isn’t obvious from the docs? Or is this endpoint not actually designed for bulk operations and I need to loop through individual PUTs for each skill? I’d rather avoid N+1 API calls if possible.

Any ideas what I’m missing here?