SailPoint v2025 Python Library RolesApi not properly listing membership criteria

The RolesApi method for list_roles does not currently return the same information I see returned from the associated v2025 API endpoint /roles.

The membership criteria specifically is missing information. For example, our ALL Managers role has a criteria that checks if the isManager identity attribute = true, and I can see that criteria returned via the /roles API as:

                        {
                            "operation": "EQUALS",
                            "key": {
                                "type": "IDENTITY",
                                "property": "attribute.isManager",
                                "sourceId": null
                            },
                            "values": [
                                "true"
                            ],
                            "stringValue": null,
                            "children": null
                        }

The list_roles method in python does not have the word “true” anywhere in the returned information for this role, though it does show a reference to the isManager attribute just without any values assigned to match it.

I suspect this may be related to the Role configuration changes that happened a decent while ago where we could put multiple values under one statement rather than having to make a bunch of ORs (see: Update! Enhancement: Improved Automated Role Assignment)

How can we get this fixed? We use this information to keep our role documentation up to date.

Below is the methedology I’m using in my python code. I’m not using the SDK:

import requests

Define the API endpoint and headers

url = "https://{tenant}.api.identitynow.com/v3/search"
headers = {
    "Authorization": f"Bearer {token}",
    "Content-Type": "application/json"
}

Define the payload

payload = {
    "indices": ["roles"],
    "query": { "query": "*" },
    "includeNested": True,
    "queryResultFilter": {
        "includes": ["id", "name", "membership", "parentId"]
    }
}

Execute the POST request

response = requests.post(url, headers=headers, json=payload)
response.raise_for_status() 
roles_data = response.json()

for role in roles_data:
    role_name = role.get("name")

    membership = role.get("membership")
    criteria_block = membership.get("criteria") if membership else None
    

    criteria_summary = summarize_criteria(criteria_block)
    
    print(f"Role: {role_name} -> {criteria_summary}")

Yeah i’m thinking just avoiding the SDK altogether and interacting with the API directly is going to be better here. Not really sure what I gain from using the SDK, thanks for the code

What does your summarize_criteria method do? I’m assuming it formats it in a more human-readable way? Do you mind sharing?

It returns it as a string.

I then take it into a jinja2 template and display it with htmlx

def summarize_criteria(group: dict) -> str:
    """Human-readable summary, e.g. 'department IN [Sales, HR]'."""
    if not group:
        return "None"
    operation = group.get("operation")
    children  = group.get("children")
    key       = group.get("key")

    if not children and key:
        prop  = key.get("property", "").replace("attribute.", "")
        multi = group.get("values")
        if multi:
            return f"{prop} IN [{', '.join(multi)}]"
        val    = group.get("stringValue")
        op_sym = {"EQUALS": "==", "NOT_EQUALS": "!=", "CONTAINS": "contains",
                  "STARTS_WITH": "starts_with", "IS_NULL": "is null",
                  "IS_NOT_NULL": "is not null"}.get(operation, operation)
        return f"{prop} {op_sym} '{val}'" if val is not None else f"{prop} {op_sym}"

    if children:
        subs = [s for s in (summarize_criteria(c) for c in children) if s != "None"]
        return f"({f' {operation} '.join(subs)})" if subs else "None"

    return "None"