Good to hear, Chris, and thanks for coming back with the working code. For anyone who lands here later: the verb is PATCH, not the PUT from my earlier answer. The Central VSG's Configuration via API chapter lists PUT and PATCH together as the update verbs (https://arubanetworking.hpe.com/techdocs/VSG/docs/002-central/central-020-configuration-api/), but as you found, policy-groups won't take a PUT. Your PATCH sent one row and the row already in the list stayed at position 1, so PATCH merges into the list. You don't need the GET and resend the whole list step.
One catch in your function: every row in the loop gets the same next_position. That's fine with one policy, but with two in the yaml both will try to take the same slot. Bump next_position inside the loop, or call get_next_available_position() before each PATCH.
The VSG will take a while to change. An issue on the central-python-workflows repo with your function as the fix will probably get the example updated sooner.
@Worldcom Exchange, Inc.
Original Message:
Sent: Sep 25, 2026 10:48 AM
From: CD-GzN358
Subject: Policy Group vs Policy, GUI vs API
PUT is not possible, but PATCH did the job! It's now on the 2nd position in the group-list:
You're right about the example, it is done against a plain, new, fresh new central environment. For existing ones, the function code should be:
def add_policy_to_group(central_conn, policy_group_details, next_position): """ Adds policies to a policy group using the Central API. :param central_conn: Instance of class:`pycentral.NewCentralBase` to establish connection to Central. :type central_conn: class:`NewCentralBase` :param policy_group_details: Dictionary containing the details of the policy group. :type policy_group_details: dict """ print("Adding Policies to Policy Group...") path = NewCentralURLs.generate_url(api_endpoint="policy-groups") # Iterate through the list of policy groups for policy_group in policy_group_details["policy-group"]["policy-group-list"]: body = { "policy-group": { "policy-group-list": [ { "name": policy_group["name"], "position": next_position, "description": policy_group.get("description", ""), } ] } } # Make the API call to add the policy into the policy group resp = central_conn.command("PATCH", path, api_data=body, api_params=None) # Check the response and print appropriate messages if resp["code"] == 200: print(f"Successfully added policy group: {colored(policy_group['name'], 'green')}") else: error = resp.get("msg", "Unknown error") print(f"Error adding policy group {colored(policy_group['name'], 'red')}: {error}") exit()
Then still, why do you check for the next available position if you are creating the policy-group from scratch and POSTing to it? Iterating through it means you create it the first time, and after it you have to update (PATCH) it... Let's see if Aruba can change their VSG text and examples a bit to match our findings and explain a little better how it works.
Thanks!
Br,
------------------------------
Chris Donkelaar, Wireless Specialist, SURF, ACMX
------------------------------
Original Message:
Sent: Sep 25, 2026 09:18 AM
From: DB86
Subject: Policy Group vs Policy, GUI vs API
Chris, I think the word "group" is doing the damage. I went and read the workflow code rather than the yaml. get_next_available_position() does a GET on policy-groups and reads resp["msg"]["policy-group"]["policy-group-list"]. Singular. There's one policy-group object in the Library and the "groups" are rows in its list, each one a policy name plus a position. Creating a role-based policy in the GUI doesn't create a policy group, it adds a row to that list, which is what your GET showed you. Deleting your policies empties the list, the object stays, and the module the error names is that object.
The workflow POSTs because on the tenant it was written against the object didn't exist yet. Yours does, so every POST is a create on something that already exists, empty list or not. Same path, same body shape, PUT instead:
PUT network-config/v1alpha1/policy-groups
{"policy-group": {"policy-group-list": [{"name": "wpa3-psk-policy", "position": 4, "description": "..."}]}}
Treat it as a full replace. GET the list first, add your row, send the whole list back, or you'll drop whatever the GUI put there. If your PATCH went to policy-groups/<name>, that's the wrong shape for a singleton and would fail the same way.
If PUT still 400s, paste the exact verb, path and body and I'll run the same calls against my lab tenant.
------------------------------
Dustin Burns
@Worldcom Exchange, Inc.
www.wei.com
- - -
The comments I post here are my own, and not my employers
If my post was useful accept solution and/or give kudos
Personal Blog: www.networkfieldnotes.com
- - -
------------------------------
Original Message:
Sent: Sep 25, 2026 08:23 AM
From: CD-GzN358
Subject: Policy Group vs Policy, GUI vs API
Ok, so what I tried so far, all using API. Created a yaml copy from the WPA3 PSK example for easy repetition, copied the steps taken, created sub menu options to get/create/delete roles, get/create/delete policies, get/create/delete policy-groups. Totally cleaned it up (via API, new GET requests showed they were gone), tried it again, PATCH, POST, emptied all roles and policies created, policy-group creation fails. Also, just creating a policy doesn't create a policy-group.
Then cloned the central-python-workflows githup repo, into the WPA3-PAK example, edited the files a bit so that device assignment wouldn't be done, but also this fails with the same error.
Go to the GUI, create a role-based policy, and via API I can verify there's a new policy-group created. I'm out of clues... :( (Doing things by hand in the GUI is not an option for our move from classic to new)
Br,
------------------------------
Chris Donkelaar, Wireless Specialist, SURF, ACMX
------------------------------
Original Message:
Sent: Sep 24, 2026 09:39 AM
From: DB86
Subject: Policy Group vs Policy, GUI vs API
Cleaning up the list empties it, but the policy group itself is still sitting in the Library, just with nothing in policy-group-list. Your error names the module rather than a group, which reads to me like Central keeps one policy group per Library, so a POST collides with it whether the list is empty or full.
A GET on policy-groups will show you. I'd expect the group to come back with an empty list, and that's what your POST ran into.
The Central VSG's Configuration via API chapter lays out the verbs, POST to create an object and PUT or PATCH to update one: https://arubanetworking.hpe.com/techdocs/VSG/docs/002-central/central-020-configuration-api/ So create the policy, add it to that list with a position and send the group back as an update. There's no need to delete and rebuild the group.
------------------------------
Dustin Burns
@Worldcom Exchange, Inc.
www.wei.com
- - -
The comments I post here are my own, and not my employers
If my post was useful accept solution and/or give kudos
Personal Blog: www.networkfieldnotes.com
- - -
------------------------------
Original Message:
Sent: Sep 24, 2026 08:13 AM
From: CD-GzN358
Subject: Policy Group vs Policy, GUI vs API
Mmh, I cleaned up the policy list before POSTing the policy-group, so I don't get why I got a duplicate there. But lets see if it works you're way! Reading through the VSG now ;)
------------------------------
Chris Donkelaar, Wireless Specialist, SURF, ACMX
------------------------------
Original Message:
Sent: Sep 24, 2026 08:00 AM
From: DB-280928
Subject: Policy Group vs Policy, GUI vs API
A policy holds rules, and the order inside it only settles which rule wins within that policy. The policy group orders the policies themselves: a list of policy names, each with a position, which Central follows when it combines them into a role's resulting policy. The Central VSG's Policy Configuration chapter covers the resultant role policy and match order: https://arubanetworking.hpe.com/techdocs/VSG/docs/002-central/central-040-policy-configuration/
That's your 400 as well. The policy group already exists in the Library, so a POST to policy-groups asks for a duplicate. The WPA3 PSK workflow doesn't create one; its policy-group-list entry is just your policy's name and a position, added to the group that's already there.
So create the policy, GET the policy group, add your entry at the position you want and send it back as an update rather than a POST. I wouldn't drop the group: it's where your policy gets its place in line.
------------------------------
Dustin Burns
@Worldcom Exchange, Inc.
www.wei.com
- - -
The comments I post here are my own, and not my employers
If my post was useful accept solution and/or give kudos
Personal Blog: www.networkfieldnotes.com
- - -
------------------------------