DocsOpen Moon AI
Browse the docs

Groups

Create static or dynamic groups, write a label rule, add members and appoint managers with the admin API of a Moon AI organization.

On this page

A group brings members together. A static group contains the members you add to it; a dynamic group also contains every member who carries the labels in its rule. A group receives roles, rules and instructions; its managers act on its members.

All routes are under https://chat.realmoon.ai/api/v1/orgs/{org_id}. Scopes: groups:read to read, groups:write to write.

The group objectLink to the The group object section

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
}
FieldDescription
kindstatic or dynamic. Cannot change after creation.
label_ruleThe rule of a dynamic group; null for a static group.
positionDisplay order (0 to 100,000).
sourcemanual, or scim for a group created by the directory: it is then edited in the directory (409 scim_managed).

In lists and in GET /groups/{group_id}, each group also carries member_count (active members, managers not counted), manager_count, has_policy (it has rules or instructions) and is_managed_by_me. GET /groups/{group_id} also returns roles, the roles assigned to the group.

The label ruleLink to the The label rule section

JSON
{
  "all": [{ "key": "site", "value": "Lyon" }],
  "any": [{ "key": "service", "value": "Ventes" }, { "key": "service", "value": "Support" }]
}
  • all: the member carries all of these labels. any: at least one. Both together: both conditions.
  • Each condition is exactly {"key", "value"}; the key is lowercased, the value is compared case-insensitively.
  • Up to 20 conditions. Each label named must exist (otherwise 400, with the message Étiquettes inconnues : …, unknown labels).
  • {"all": []} is accepted on creation: the group then includes no one.

List groupsLink to the List groups section

GET /groups · groups:read

ParameterTypeDescription
qstringSearches the name, 128 characters.
kindstringstatic or dynamic.
page, limitintegerPagination. Sorted by position, then name.

If the key’s creator only manages some groups, only those are listed.

Create a groupLink to the Create a group section

POST /groups · groups:write

FieldTypeRequiredDescription
namestringyes1 to 128 characters, unique in the organization (case-insensitive).
descriptionstringnoUp to 500 characters.
kindstringnostatic (default) or dynamic.
label_ruleobjectfor dynamicLabel rule. Not allowed on a static group.
positionintegerno0 by default.
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
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
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);

An organization can have up to 1,000 groups.

StatuscodeCause
400invalid_requestEmpty name, rule missing for a dynamic group or present for a static one, invalid rule (fields points to label_rule), unknown labels, more than 1,000 groups.
409invalid_requestUn groupe porte déjà ce nom. (a group already has this name)

Read a groupLink to the Read a group section

GET /groups/{group_id} · groups:read

Returns the group with its counters and roles. 404 for an unknown group or one outside the groups the key can read.

Update a groupLink to the Update a group section

PATCH /groups/{group_id} · groups:write

FieldTypeRequiredDescription
namestringnoNew name (not on a group synced from the directory).
descriptionstringnonull clears it.
positionintegernoNew order.
label_ruleobjectnoNew rule, for a dynamic group only.

Changing the rule requires the permission to set groups: the key must also have a rules:read or rules:write scope. The new rule applies right away: members join or leave the group, along with its roles and rules. If it moves members to another license pool, licenses:write is also required (403 license_change_forbidden).

Delete a groupLink to the Delete a group section

DELETE /groups/{group_id} · groups:write

Deletes the group, its memberships, rules, instructions, roles and labels. The members stay in the organization. Response: {"success": true}. 409 scim_managed for a group synced from the directory.

Group membersLink to the Group members section

List membersLink to the List members section

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

Parameters: q (name, and address if the key can see addresses), page, limit. Lists active and suspended members, whether added by hand or included by the rule. The response also includes 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 is manual, scim, sso, import, or label for a member included only by the rule (dynamic: true). Each member also carries their groups, labels, roles, effective_roles and role_sources.

Find members to addLink to the Find members to add section

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

Parameters: q (name; an exact address if it contains “@” and the key can see addresses), limit (1 to 50, 20 by default). Returns items (user_id, name, profile_image_url), total and limit, without addresses. Excluded: inactive members, the owner, people already in the group and people the key cannot act on.

Add membersLink to the Add members section

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

FieldTypeRequiredDescription
user_idslistyes1 to 500 active members.
is_managerbooleannotrue makes these people managers of the group, false takes it away; missing: unchanged (false for a new person). Requires the permission across the whole organization.

Response: {"success": true, "added": 2, "updated": 0}. Members already present are skipped (updated counts those whose is_manager changed). 404 (with the message Membres introuvables : …, members not found) if an ID is not an active member.

Remove membersLink to the Remove members section

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

One member with ?user_id=<id>, or several with a {"user_ids": [...]} body (up to 500). Response: {"success": true, "removed": 1}. A member included by a dynamic group’s rule stays in it: remove their label instead.

Preview a ruleLink to the Preview a rule section

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

Lists the active members a rule would include, without saving anything. rule parameter: the rule as URL-encoded JSON (up to 8,192 characters, at least one condition); without rule, the group’s own rule. Returns items (user_id, name, email), pagination and rule, the rule as it was read.

Terminal
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"}]}'

See alsoLink to the See also section

  • Labels
  • Roles, to give a role to a group
  • Rules, for a group’s rules and instructions

Moon AI

Moon AI in action

0:00

Explore

Screenshots of chat.realmoon.ai taken on October 29, 2026, with fictional data.

Moon AI

Your preferences

Visits counted without cookies.

Our server counts visits without cookies or trackers. Your theme and your answer about ad measurement are remembered on this device.

Ad measurement

With your consent, our ad networks’ trackers (Google) measure our ads, from the visit to the sign-up and then to the first subscription and its amount, and may show you our ads. Your answer is valid for 13 months; you can change it here at any time.

No answer saved on this device.

After you decline, no tag is loaded anymore and the site deletes the cookies they had set on realmoon.ai. Those on the ad networks’ own domains are managed with them or in your browser.

The Moon AI app has its own privacy settings.