Aller au contenu principal
Version 0.1Brouillon

Groups

What Groups are for

Groups are how your partnership models role-based access at the LuxID layer. Rather than each of your applications maintaining its own user-role table, you can let LuxID hold the group memberships and have your applications check them through the OIDC token or through the Partner API. This is the right tool when:

  • Several applications under your partnership share the same access tiers ("admin", "editor", "viewer").
  • An external system (HR tool, identity governance, partner CRM) needs to drive memberships in batches.
  • Auditors need a single place to answer "who is in myapp-admins today?".

If you only have a single application with simple role logic, you may not need this capability at all - your application can hold its own user-role table. Groups become valuable when multiple systems care about the same membership.

The endpoints in this capability:

What you want to doEndpoint
List the members of one groupGET /groups/{groupName}/memberships
List the groups a specific account belongs to (limited to groups your partnership manages)GET /accounts/{md5HashOfLowercaseEmail}/memberships
Add or remove accounts in groups, in a single callPOST /bulk-actions/group-memberships

To discover which groups exist for an application, see Applications.

List the members of a Group

curl -sS \
-H "X-Client-Id: $LUXID_CLIENT_ID" \
-H "X-Client-Secret: $LUXID_CLIENT_SECRET" \
https://api-uat.luxid.lu/services/luxid-partner-api/groups/myapp-admins/memberships

Response:

{
"memberships": [
{ "groupName": "myapp-admins", "accountEmail": "alice@example.com" },
{ "groupName": "myapp-admins", "accountEmail": "bob@example.com" }
]
}

The accountEmail is the email of the member's LuxID Account. In rare cases of legacy accounts, this field can be null; your code should handle that.

List a user's Group memberships

EMAIL_HASH=$(echo -n "alice@example.com" | tr 'A-Z' 'a-z' | md5sum | awk '{print $1}')

curl -sS \
-H "X-Client-Id: $LUXID_CLIENT_ID" \
-H "X-Client-Secret: $LUXID_CLIENT_SECRET" \
"https://api-uat.luxid.lu/services/luxid-partner-api/accounts/$EMAIL_HASH/memberships"

Response:

{
"memberships": [
{ "groupName": "myapp-admins", "accountEmail": "alice@example.com" },
{ "groupName": "myapp-readonly", "accountEmail": "alice@example.com" }
]
}

This endpoint only returns groups that your partnership manages. If the user is in other groups managed by a different partnership, those are not visible to you.

Add or remove members in bulk

remarque

A single bulk call can carry up to 20 actions, each either ADD or REMOVE, each targeting one (account, group) pair. Duplicate (account, group) pairs in the same request are rejected.

curl -sS -X POST \
-H "X-Client-Id: $LUXID_CLIENT_ID" \
-H "X-Client-Secret: $LUXID_CLIENT_SECRET" \
-H "Content-Type: application/json" \
-d '{
"actions": [
{ "action": "ADD", "accountEmail": "alice@example.com", "groupName": "myapp-admins" },
{ "action": "ADD", "accountEmail": "bob@example.com", "groupName": "myapp-readonly" },
{ "action": "REMOVE", "accountEmail": "carol@example.com", "groupName": "myapp-admins" }
]
}' \
https://api-uat.luxid.lu/services/luxid-partner-api/bulk-actions/group-memberships

The response gives a per-action result, so partial success is the expected outcome - one bad action does not roll back the others:

{
"actions": [
{
"action": "ADD",
"accountEmail": "alice@example.com",
"groupName": "myapp-admins",
"result": "ACCOUNT_ADDED_TO_GROUP"
},
{
"action": "ADD",
"accountEmail": "bob@example.com",
"groupName": "myapp-readonly",
"result": "ACCOUNT_ALREADY_IN_GROUP"
},
{
"action": "REMOVE",
"accountEmail": "carol@example.com",
"groupName": "myapp-admins",
"result": "ACCOUNT_NOT_FOUND"
}
]
}

The full set of result codes:

CodeMeaning
ACCOUNT_ADDED_TO_GROUPSuccess: the account was added.
ACCOUNT_REMOVED_FROM_GROUPSuccess: the account was removed.
ACCOUNT_ALREADY_IN_GROUPNo-op: the account was already in the group when you tried to add it.
ACCOUNT_ALREADY_NOT_IN_GROUPNo-op: the account was not in the group when you tried to remove it.
ACCOUNT_NOT_FOUNDThe email does not match any LuxID Account.
GROUP_NOT_FOUNDThe group name does not exist.
GROUP_FORBIDDENThe group exists but is not managed by your partnership.

The two ALREADY codes mean the request was structurally fine and the end state matches what you asked for; you can usually treat them as success.

Designing around the 20-action cap

If your provisioning source produces more than 20 changes at a time, chunk the calls. A naive approach that works fine in most cases:

  1. Sort the change list deterministically (so retries are stable).
  2. Slice into batches of 20.
  3. Call the endpoint for each batch.
  4. Aggregate the per-action results into a single status report.
  5. If a 4xx/5xx hits the whole batch, retry that batch with exponential backoff.

Avoid sending the same (account, group, action) triple in two batches that overlap in time - it does not break anything, but it makes the per-action result codes harder to reason about.

Errors you may see

StatusWhenWhat to do
400Request structure invalid (e.g. >20 actions, duplicate pairs).Read message and fix the batch.
403At least one group in the request is not managed by your partnership.Look up the group via Applications > groups first.
404The path-identified group or account does not exist.Verify spelling and (for accounts) recompute the MD5 hash.

Typical use cases

  • HR-driven access management. Your HR system emits a daily diff of "who joined which role". A scheduled job converts it into bulk add/remove calls. The same job can compare against the membership list returned by GET /groups/{groupName}/memberships to spot drift.
  • Off-boarding cleanup. When a user leaves the partner organisation, a single bulk call removes them from every relevant group (one REMOVE per group, all in one batch).
  • Read-only display in your admin UI. Your support agents need to see "what is this user a member of?" - hash the email, call the account memberships endpoint, render the list.
Mise à jour le 2026-05-18