You’ve defined an entitlement attribute such as accessGroup in your account schema, marked it as:
-
Entitlement: Marked
-
Multi-valued: Marked
And your Create Account JSON body looks like this:
{
"firstName": "$plan.firstName$",
"lastName": "$plan.lastName$",
"authorised": "$plan.authorised$",
"division": { "href": "https://edu.au:804/api/divisions/773" },
"@Email": "$plan.email$",
"accessGroups": [
{
"accessGroup": {
"href": "$plan.accessGroup$"
}
}
]
}
When provisioning a user with one entitlement, everything works perfectly.
But when you try multiple entitlements, only the first one is processed—or worse, you get a Bad Request error.
Why It Happens
SailPoint ISC’s Web Service Connector does not automatically “fan out” multi-valued attributes when they’re nested inside an object.
So, when $plan.accessGroup$ contains multiple values, it doesn’t generate multiple objects under accessGroups[]. Instead, it attempts to stuff all values into the same node, leading to malformed JSON or ignored entries.
The Correct Approach
You need to intercept the request before it’s sent and dynamically build the JSON payload.
That’s exactly what the Web Service Before Operation Rule is designed for.
The WebServiceBeforeOperationRule Solution
Below is a fully compliant ISC rule that:
-
Reads all
accessGroupvalues from the provisioning plan. -
Builds a clean, nested
accessGroupsarray. -
Ensures no duplicate or blank entries.
-
Returns the updated
requestEndPointwith properjsonBody.
Rule: Build Multi-Entitlement JSON for Create Account
import java.util.*;
import connector.common.JsonUtil;
import connector.common.Util;
import sailpoint.object.ProvisioningPlan;
import sailpoint.object.ProvisioningPlan.AccountRequest;
// Inputs: requestEndPoint, provisioningPlan, application, restClient, oldResponseMap
// Return: updated requestEndPoint
// ---- helper functions ----
String getAttr(ProvisioningPlan plan, String name) {
if (plan == null) return null;
for (AccountRequest accReq : Util.iterate(plan.getAccountRequests())) {
if ("Create".equalsIgnoreCase(accReq.getOperation())) {
for (ProvisioningPlan.AttributeRequest ar : Util.iterate(accReq.getAttributeRequests())) {
if (name.equalsIgnoreCase(ar.getName())) {
Object v = ar.getValue();
return (v == null) ? null : String.valueOf(v).trim();
}
}
}
}
return null;
}
List<String> getAttrList(ProvisioningPlan plan, String name) {
List<String> values = new ArrayList<>();
if (plan == null) return values;
for (AccountRequest accReq : Util.iterate(plan.getAccountRequests())) {
if ("Create".equalsIgnoreCase(accReq.getOperation())) {
for (ProvisioningPlan.AttributeRequest ar : Util.iterate(accReq.getAttributeRequests())) {
if (name.equalsIgnoreCase(ar.getName())) {
Object v = ar.getValue();
if (v instanceof Collection) {
for (Object o : (Collection)v)
if (o != null && !String.valueOf(o).trim().isEmpty())
values.add(String.valueOf(o).trim());
} else if (v != null) {
values.add(String.valueOf(v).trim());
}
}
}
}
}
return values;
}
// ---- build JSON body ----
Map bodyMap = requestEndPoint.getBody();
if (bodyMap == null) bodyMap = new HashMap();
Map<String,Object> json = new LinkedHashMap<>();
json.put("firstName", getAttr(provisioningPlan, "firstName"));
json.put("lastName", getAttr(provisioningPlan, "lastName"));
json.put("authorised", Boolean.valueOf(getAttr(provisioningPlan, "authorised")));
Map<String,Object> division = new LinkedHashMap<>();
division.put("href", "https://edu.au:804/api/divisions/773");
json.put("division", division);
json.put("@Email", getAttr(provisioningPlan, "email"));
json.put("@AU Cardholder ID", getAttr(provisioningPlan, "AU_Cardholder_ID"));
json.put("@Program Code", getAttr(provisioningPlan, "Program_Code"));
json.put("@Academic Plan Code", getAttr(provisioningPlan, "academic_plan_code"));
json.put("@Faculty", getAttr(provisioningPlan, "faculty"));
json.put("@School", getAttr(provisioningPlan, "school"));
json.put("@School Code", getAttr(provisioningPlan, "school_Code"));
json.put("@Course Code", getAttr(provisioningPlan, "course_Code"));
json.put("@EDU Person Affiliation", getAttr(provisioningPlan, "eduPersonAffiliation"));
// ---- accessGroups array ----
List<String> hrefs = getAttrList(provisioningPlan, "accessGroup");
Set<String> seen = new LinkedHashSet<>(hrefs);
List<Map<String,Object>> ag = new ArrayList<>();
for (String href : seen) {
Map<String,Object> item = new LinkedHashMap<>();
Map<String,Object> inner = new LinkedHashMap<>();
inner.put("href", href);
item.put("accessGroup", inner);
ag.add(item);
}
if (!ag.isEmpty()) json.put("accessGroups", ag);
// Attach back to endpoint
String finalBody = JsonUtil.render(json);
bodyMap.put("jsonBody", finalBody);
requestEndPoint.setBody(bodyMap);
requestEndPoint.setFullUrl(requestEndPoint.getFullUrl().replaceAll("&&", "&"));
return requestEndPoint;
Configure it safely
-
Remove any
accessGroupsblock from the Create Account action’s static body template (to avoid duplicates). -
Attach this rule as the Before Rule for the Create Account HTTP action in your source. (Either via UI/VS Code or the PATCH API; the developer community threads explain where to set
beforeRule/ action index.) How to attach the rule
Expected Outcome
Single Entitlement — works as before
Multiple Entitlements — all entitlements get correctly assigned in a single request No Bad Request — payload is clean and API-compliant
Example final payload sent to API:
{
"firstName": "John",
"lastName": "Smith",
"authorised": true,
"division": { "href": "https://edu.au:804/api/divisions/773" },
"@Email": "john.smith@edu.au",
"accessGroups": [
{ "accessGroup": { "href": "https://edu.au/api/group/123" } },
{ "accessGroup": { "href": "https://edu.au/api/group/456" } }
]
}
Cheers!!!