# Groupes

> Créer des groupes statiques ou dynamiques, écrire une règle d’étiquettes, ajouter des membres et nommer des gestionnaires avec l’API d’administration.

Un groupe réunit des membres. Un groupe **statique** contient les membres qu’on y ajoute ; un groupe **dynamique** contient aussi tous les membres qui portent les étiquettes de sa règle. Un groupe reçoit des rôles, des règles et des instructions ; ses gestionnaires agissent sur ses membres.

Toutes les routes sont sous `https://chat.realmoon.ai/api/v1/orgs/{org_id}`. Portées : `groups:read` pour lire, `groups:write` pour écrire.

## L’objet groupe

```json
{
  "id": "3e4f5a6b-7c8d-4e9f-8a0b-1c2d3e4f5a6b",
  "org_id": "8c1f2a4e-5b6d-4e7f-9a0b-1c2d3e4f5a6b",
  "name": "Classe de 3A",
  "description": "Élèves de 3A, année 2026-2027.",
  "kind": "dynamic",
  "label_rule": { "all": [{ "key": "classe", "value": "3A" }] },
  "position": 10,
  "source": "manual",
  "created_by": "1f2e3d4c-5b6a-4987-8a6b-5c4d3e2f1a0b",
  "created_at": 1793836800,
  "updated_at": 1793836800
}
```

| Champ | Description |
| --- | --- |
| `kind` | `static` ou `dynamic`. Ne change pas après la création. |
| `label_rule` | La règle d’un groupe dynamique ; `null` pour un groupe statique. |
| `position` | Ordre d’affichage (0 à 100 000). |
| `source` | `manual`, ou `scim` pour un groupe créé par l’annuaire : il se modifie alors dans l’annuaire (`409 scim_managed`). |

Dans les listes et `GET /groups/{group_id}`, chaque groupe porte aussi `member_count` (membres actifs, gestionnaires non comptés), `manager_count`, `has_policy` (il a des règles ou des instructions) et `is_managed_by_me`. `GET /groups/{group_id}` ajoute `roles`, les rôles attribués au groupe.

## La règle d’étiquettes

```json
{
  "all": [{ "key": "site", "value": "Lyon" }],
  "any": [{ "key": "service", "value": "Ventes" }, { "key": "service", "value": "Support" }]
}
```

- `all` : le membre porte toutes ces étiquettes. `any` : au moins une. Les deux ensemble : les deux conditions.
- Chaque condition est exactement `{"key", "value"}` ; la clé est mise en minuscules, la valeur se compare sans tenir compte de la casse.
- 20 conditions au plus. Chaque étiquette nommée doit exister (sinon `400`, « Étiquettes inconnues : … »).
- `{"all": []}` est accepté à la création : le groupe ne retient alors personne.

## Lister les groupes

`GET /groups` · `groups:read`

| Paramètre | Type | Description |
| --- | --- | --- |
| `q` | chaîne | Recherche dans le nom, 128 caractères. |
| `kind` | chaîne | `static` ou `dynamic`. |
| `page`, `limit` | entier | Pagination. Tri par position puis nom. |

Si le créateur de la clé ne gère que certains groupes, seuls ceux-là sont listés.

## Créer un groupe

`POST /groups` · `groups:write`

| Champ | Type | Obligatoire | Description |
| --- | --- | --- | --- |
| `name` | chaîne | oui | 1 à 128 caractères, unique dans l’organisation (sans tenir compte de la casse). |
| `description` | chaîne | non | 500 caractères au plus. |
| `kind` | chaîne | non | `static` (par défaut) ou `dynamic`. |
| `label_rule` | objet | pour `dynamic` | [Règle d’étiquettes](https://realmoon.ai/documentation/api-organisations/groupes/#regle). Interdite pour un groupe statique. |
| `position` | entier | non | 0 par défaut. |

```bash title="cURL"
curl https://chat.realmoon.ai/api/v1/orgs/$ORG_ID/groups \
  -H "Authorization: Bearer $MOONAI_ADMIN_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Site de Lyon",
    "kind": "dynamic",
    "label_rule": {"all": [{"key": "site", "value": "Lyon"}]}
  }'
```

```python title="Python"
import os
import requests

BASE = f"https://chat.realmoon.ai/api/v1/orgs/{os.environ['ORG_ID']}"
HEADERS = {"Authorization": f"Bearer {os.environ['MOONAI_ADMIN_KEY']}"}

group = requests.post(
    f"{BASE}/groups",
    headers=HEADERS,
    json={"name": "Site de Lyon", "kind": "dynamic", "label_rule": {"all": [{"key": "site", "value": "Lyon"}]}},
    timeout=30,
)
group.raise_for_status()
print(group.json()["id"])
```

```javascript title="JavaScript"
const BASE = `https://chat.realmoon.ai/api/v1/orgs/${process.env.ORG_ID}`;
const r = await fetch(`${BASE}/groups`, {
  method: 'POST',
  headers: { Authorization: `Bearer ${process.env.MOONAI_ADMIN_KEY}`, 'Content-Type': 'application/json' },
  body: JSON.stringify({ name: 'Site de Lyon', kind: 'dynamic', label_rule: { all: [{ key: 'site', value: 'Lyon' }] } }),
});
if (!r.ok) throw new Error(await r.text());
console.log((await r.json()).id);
```

Une organisation a 1 000 groupes au plus.

| Statut | `code` | Cause |
| --- | --- | --- |
| 400 | `invalid_request` | Nom vide, règle absente pour un groupe dynamique ou présente pour un statique, règle invalide (`fields` vise `label_rule`), étiquettes inconnues, plus de 1 000 groupes. |
| 409 | `invalid_request` | « Un groupe porte déjà ce nom. » |

## Lire un groupe

`GET /groups/{group_id}` · `groups:read`

Rend le groupe avec ses compteurs et `roles`. `404` pour un groupe inconnu ou hors des groupes que la clé peut lire.

## Modifier un groupe

`PATCH /groups/{group_id}` · `groups:write`

| Champ | Type | Obligatoire | Description |
| --- | --- | --- | --- |
| `name` | chaîne | non | Nouveau nom (pas sur un groupe venu de l’annuaire). |
| `description` | chaîne | non | `null` l’efface. |
| `position` | entier | non | Nouvel ordre. |
| `label_rule` | objet | non | Nouvelle règle, pour un groupe dynamique seulement. |

Changer la règle demande la permission de régler les groupes : la clé doit aussi avoir une portée `rules:read` ou `rules:write`. La nouvelle règle s’applique tout de suite : des membres entrent dans le groupe ou en sortent, avec ses rôles et ses règles. Si elle fait changer de lot de licences des membres, il faut aussi `licenses:write` (`403 license_change_forbidden`).

## Supprimer un groupe

`DELETE /groups/{group_id}` · `groups:write`

Supprime le groupe, ses membres, ses règles, ses instructions, ses rôles et ses étiquettes. Les membres restent dans l’organisation. Réponse : `{"success": true}`. `409 scim_managed` pour un groupe venu de l’annuaire.

## Membres d’un groupe

### Lister les membres

`GET /groups/{group_id}/members` · `groups:read`

Paramètres : `q` (nom, et adresse si la clé voit les adresses), `page`, `limit`. Liste les membres actifs et suspendus, ajoutés ou retenus par la règle. La réponse ajoute `emails_visible`.

```json
{
  "items": [
    {
      "user_id": "7a6b5c4d-3e2f-4a1b-9c8d-7e6f5a4b3c2d",
      "role": "member",
      "status": "active",
      "joined_at": 1793836800,
      "name": "Camille Durand",
      "email": "camille.durand@ecole.fr",
      "last_active_at": 1793923200,
      "never_active": false,
      "is_manager": false,
      "membership_source": "label",
      "dynamic": true
    }
  ],
  "total": 1,
  "page": 1,
  "limit": 50,
  "emails_visible": true
}
```

`membership_source` vaut `manual`, `scim`, `sso`, `import`, ou `label` pour un membre retenu par la règle seulement (`dynamic: true`). Chaque membre porte aussi ses `groups`, `labels`, `roles`, `effective_roles` et `role_sources`.

### Chercher des membres à ajouter

`GET /groups/{group_id}/candidates` · `groups:write`

Paramètres : `q` (nom ; une adresse exacte si elle contient « @ » et que la clé voit les adresses), `limit` (1 à 50, 20 par défaut). Rend `items` (`user_id`, `name`, `profile_image_url`), `total` et `limit`, sans adresse. Exclus : les membres inactifs, le propriétaire, ceux déjà dans le groupe et ceux sur qui la clé ne peut pas agir.

### Ajouter des membres

`POST /groups/{group_id}/members` · `groups:write`

| Champ | Type | Obligatoire | Description |
| --- | --- | --- | --- |
| `user_ids` | liste | oui | 1 à 500 membres actifs. |
| `is_manager` | booléen | non | `true` nomme ces personnes gestionnaires du groupe, `false` le leur retire ; absent : inchangé (`false` pour une nouvelle personne). Demande la permission sur toute l’organisation. |

Réponse : `{"success": true, "added": 2, "updated": 0}`. Les membres déjà présents sont ignorés (`updated` compte ceux dont `is_manager` a changé). `404` (« Membres introuvables : … ») si un identifiant n’est pas un membre actif.

### Retirer des membres

`DELETE /groups/{group_id}/members` · `groups:write`

Un membre par `?user_id=<id>`, ou plusieurs par un corps `{"user_ids": [...]}` (500 au plus). Réponse : `{"success": true, "removed": 1}`. Un membre retenu par la règle d’un groupe dynamique y reste : retirez-lui l’étiquette.

### Aperçu d’une règle

`GET /groups/{group_id}/preview` · `groups:read`

Liste les membres actifs que retiendrait une règle, sans rien enregistrer. Paramètre `rule` : la règle en JSON encodé dans l’adresse (8 192 caractères au plus, au moins une condition) ; sans `rule`, la règle du groupe. Rend `items` (`user_id`, `name`, `email`), la pagination et `rule`, la règle telle qu’elle a été lue.

```bash
curl -G "https://chat.realmoon.ai/api/v1/orgs/$ORG_ID/groups/$GROUP_ID/preview" \
  -H "Authorization: Bearer $MOONAI_ADMIN_KEY" \
  --data-urlencode 'rule={"all":[{"key":"site","value":"Lyon"}]}'
```

## Voir aussi

- [Étiquettes](https://realmoon.ai/documentation/api-organisations/etiquettes/)
- [Rôles](https://realmoon.ai/documentation/api-organisations/roles/#attribuer), pour donner un rôle à un groupe
- [Règles](https://realmoon.ai/documentation/api-organisations/regles/), pour les règles et les instructions d’un groupe

---

Documentation Moon AI › API des organisations › Groupes. Page : https://realmoon.ai/documentation/api-organisations/groupes/. Modifiée le 5 novembre 2026.

© 2026 Stellarr Studio SAS. Tous droits réservés. Moon AI, Moon Blur et Stellarr Studio sont des marques déposées à l’INPI.
