Group
Included from Basic tier
Groups are served on three distinct surfaces. JSON:API type:
group_members (one group's membership). The admin local-groups surface is
a plain-JSON payload, documented honestly below; the group catalog is the
v2 GroupService.
The v2 OpenAPI document declares this resource's schema; the resource-field coverage gate holds this page to it.
Two kinds of group exist: provider snapshot groups — each identity
provider connection's directory/IdP groups as captured at its users'
sign-ins (federated_group_snapshotsretired the old
ldap_directory_entries mirror and its GET /api/ui_support/ldap_groups
route and refresh) — and local groups, records OpsChain itself stores
(local_groups, with memberships in local_group_members: a local
group may exist with zero members, and the listing unions the group rows
with any membership-derived names older replicas wrote). A principal's
effective groups are the union of both (snapshot names namespaced
<code>:<group> plus whatever the mapping entries grant), and
authorisation policy assignments bind to the resulting group names.
group_members — one group's membership (JSON:API)
GET /api/ui_support/group_members/{group_name}
(upstream UISupportController#group_members) — deprecated in the v1
OpenAPI document (no caller; the v2
GroupService.ListGroups serves each group's members), now answering from
the provider snapshots:
| Field | Meaning |
|---|---|
id | The group name (the retired mirror's constructed DN went with the mirror). |
attributes.members | The member usernames whose provider snapshot names the group, sorted ascending; [] when none do. |
Local groups — the admin surface (plain JSON, not JSON:API)
Admin-gated, PostgreSQL-store compositions only:
| Verb | Path | Behaviour |
|---|---|---|
GET | /api/admin/groups | {"groups": [{"groupname": "...", "source": "local", "members": ["user", …], "member_accounts": [{"user_id": "...", "username": "...", "provider": "local|ldap:<code>|…"}, …]}, …]} — every local group, group then member ordered, empty groups included. members keeps the original username-only shape; member_accounts carries each membership's provider (an open provider-instance string), since membership is per-account and a same-named local/ldap pair holds two distinguishable rows, and the membership's user_id. source names the group's source instance — always local on this surface, the same key vocabulary as the v2 group catalog's source, so badges, filters and grouped views share one naming. Takes the q/limit filter vocabulary. |
POST | /api/admin/groups | Body {"groupname": "..."} creates an empty local group (members optional at creation; 1–255 characters). 201 with the group document; a duplicate name is a named 422. Emits api:groups:create. |
DELETE | /api/admin/groups/{groupname} | Deletes the group and its memberships (204; 404 when unknown) — refused with a named 422 listing the group-named policy assignments while any reference the group (remove the assignments first). Emits api:groups:delete. |
POST | /api/admin/groups/{groupname}/members | Body {"username": "..."} (plus optional ?provider=<auth_provider> for a same-named pair) adds the user to the local group, creating the group if needed. 204. Service accounts refuse with a named 422 — machine identities take their authority from trust grants, not groups. Emits api:groups:add_member. |
DELETE | /api/admin/groups/{groupname}/members/{username} | Removes the membership (optional ?provider= selects the account of a pair); 404 ("user … is not a member of local group …") when it does not exist. 204. Emits api:groups:remove_member. |
The CLI family is opschain admin groups … (list, create, delete,
add-member, remove-member) — riding the v2 GroupService verbs below,
with the username arguments resolved to user ids through
UserService.GetUser (opschain docs cli/admin-groups).
The v2 group catalog — GroupService
POST /api/v2/opschain.api.v2.GroupService/ListGroups serves both kinds
in one typed, cursor-paginated catalog, snapshot rows first: a snapshot
row carries the connection key as source (the catalog
names each group's source instance), <code>:<group> as id, the bare
group name as cn, and the member usernames/accounts whose snapshot
names it; a local row (source: "local") carries the group name and its
member usernames. Every row carries member_accounts — each membership's
user_id, username and provider (membership is per user id,
so a same-named local/ldap pair holds two distinguishable rows and
federated shadow accounts are addressable). Any authenticated caller (the
v1 ui_support posture); PostgreSQL compositions only — the embedded store
answers the typed postgres posture. Directory freshness, import and
leaver detection ride IdentityProviderService.SweepIdentityProvider
(the retired RefreshDirectory mirror refresh's successor). See
opschain docs api-v2.
Local group management — the v2 write verbs
All four sit behind the /admin updatable determination
(permission_denied on deny) and operate on local groups only —
snapshot groups are read-only provider facts, managed at the provider.
A local group may share a provider group's bare name: the catalog
namespaces snapshot ids by connection key, so nothing collides (that is
the documented way a mirror account inherits locally).
| Verb | Behaviour |
|---|---|
GroupService.CreateGroup | name (1–255 characters) creates an empty local group; a duplicate name is a named invalid_argument. Emits api:groups:create. |
GroupService.DeleteGroup | id (the group name) deletes the group and its memberships; not_found when unknown — refused with a named invalid_argument listing the group-named policy assignments while any reference the group (remove the assignments first). Emits api:groups:destroy. |
GroupService.AddMember | group_id + user_id adds the member — keyed by user id, provider-agnostic, so ldap and federated shadow accounts need no username/provider disambiguation. The group must exist; a service account refuses a named invalid_argument; adding an existing member is an idempotent no-op. The membership takes effect on the member's next request (the shared group resolver reads local memberships per request). Emits api:groups:member_added on a real add. |
GroupService.RemoveMember | group_id + user_id removes the membership; removing a non-member is an idempotent no-op. Emits api:groups:member_removed on a real removal. |
The membership verbs answer the group after the write, member_accounts
included.
Related pages
opschain docs identity— how directory and local groups combine into a principal's effective groups.opschain docs resources/user— the member side.