Le service SCIM 2.0 d’une organisation suit les RFC 7643 et 7644. Votre annuaire (Microsoft Entra ID, Okta, Google Workspace…) l’appelle pour créer, suspendre et retirer les membres, et pour tenir les groupes à jour. La mise en place pas à pas est dans Annuaire SCIM.
Adresse et authentificationLien vers la section Adresse et authentification
| Élément | Valeur |
|---|---|
| Adresse de base | https:// |
| Authentification | Authorization: |
| Réponses | application/ ; le type de contenu de la requête n’est pas lu |
Le jeton désigne l’organisation : l’adresse ne contient jamais son identifiant. Un jeton absent, faux, révoqué ou d’un workspace personnel reçoit 401 avec WWW-Authenticate: et le message « Jeton invalide. ». Toutes les routes demandent le jeton, y compris /, / et /Schemas.
Le jeton de l’annuaireLien vers la section Le jeton de l’annuaire
Une organisation a un seul jeton actif. Il se crée dans l’administration › « Connexion » › « Annuaire », par le propriétaire ou un administrateur de l’organisation. Il est montré une seule fois ; en créer un nouveau révoque le précédent tout de suite.
Les routes du jeton sont sous https:// :
| Méthode et chemin | Qui | Rôle |
|---|---|---|
GET / | Clé avec security:read | État du jeton, jamais sa valeur. |
POST / | Propriétaire ou administrateur, avec sa session | Créer un jeton et révoquer le précédent. |
DELETE / | Propriétaire ou administrateur, avec sa session | Révoquer le jeton actif (404 s’il n’y en a pas). |
Une clé d’administration reçoit 403 sur POST et DELETE.
{
"can_configure": false,
"active": true,
"prefix": "moai_scim_X7k2Qp",
"created_at": 1762339200,
"last_used_at": 1762339800,
"created_by_name": "Camille Martin",
"base_url": "https://chat.realmoon.ai/api/v1/orgs/scim/v2"
}last_used_at est mis à jour au plus une fois par minute. La réponse de création ajoute token (sa valeur) et revoked_previous. Chaque écriture de l’annuaire est au journal de l’organisation avec l’auteur scim:<début .
Ce que le service annonceLien vers la section Ce que le service annonce
GET / :
| Fonction | Prise en charge |
|---|---|
patch | oui |
filter | oui, 200 résultats au plus |
bulk | non |
changePassword | non |
sort | non |
etag | non |
| Authentification | jeton porteur (oauthbearertoken) |
GET / et GET /Schemas décrivent les ressources User et Group.
UsersLien vers la section Users
Un User est un membre de l’organisation, actif ou suspendu.
| Méthode et chemin | Rôle |
|---|---|
GET /Users | Liste, avec startIndex (à partir de 1), count (100 par défaut, 200 au plus) et filter ; triée par date d’arrivée. |
GET / | Un membre. |
POST /Users | Ajouter un membre ; répond 201. |
PUT / | Appliquer les attributs envoyés. |
PATCH / | Modifier des attributs. |
DELETE / | Retirer le membre de l’organisation ; répond 204 sans corps. |
FiltresLien vers la section Filtres
Une seule condition attribut , sur userName, externalId, id, emails, emails.value ou emails[type . Seul l’opérateur eq est pris en charge ; la valeur fait 512 caractères au plus, le filtre 1 024. userName et les adresses sont comparés sans tenir compte de la casse. Exemple : GET /.
Une ressource UserLien vers la section Une ressource User
{
"schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"],
"id": "<id du compte>",
"externalId": "<id dans votre annuaire>",
"userName": "camille.martin@ecole.fr",
"name": { "formatted": "Camille Martin", "givenName": "Camille", "familyName": "Martin" },
"displayName": "Camille Martin",
"emails": [{ "value": "camille.martin@ecole.fr", "type": "work", "primary": true }],
"active": true,
"groups": [{ "value": "<id du groupe>", "display": "Site de Lyon", "$ref": "https://chat.realmoon.ai/api/v1/orgs/scim/v2/Groups/<id du groupe>" }],
"meta": { "resourceType": "User", "created": "2026-11-05T09:00:00+00:00", "lastModified": "2026-11-05T09:00:00+00:00", "location": "https://chat.realmoon.ai/api/v1/orgs/scim/v2/Users/<id du compte>" }
}groups ne liste que les groupes créés par l’annuaire. givenName et familyName sont tirés du nom affiché, coupé au premier espace.
Créer un membreLien vers la section Créer un membre
- L’adresse est lue dans
userName, sinon dans l’adresseprimary, sinon la première deemails. - Elle doit être dans un domaine vérifié par l’organisation, sinon
409(scimTypeinvalidValue), que le compte existe ou non. - Un compte inconnu est créé ; un compte existant est rattaché. Le nom vient de
name.formatted, sinon degivenNameetfamilyName, sinon dedisplayName. activeabsent vauttrue;active:crée le membre suspendu.false 409aussi quand la personne a déjà une place dans l’organisation (uniqueness), quand tous les sièges sont pris ou pour le compte d’un administrateur de la plateforme. Même réponse sur un workspace payé par carte : chaque siège ajouté serait facturé, invitez la personne depuis l’administration.
Modifier et retirerLien vers la section Modifier et retirer
PUTn’applique que les attributs présents : un attribut absent ne change rien.active:suspend le membre et ferme toutes les sessions de son compte.false active: truelève une suspension faite par l’annuaire ; une suspension décidée dans l’administration répond403. Sur un workspace payé par carte, la réactivation se fait depuis l’administration (409).DELETEretire le membre et ferme ses sessions. Son compte reste, sauf s’il a été créé par cet annuaire, n’a eu aucune activité et n’appartient à aucun autre workspace d’équipe : il est alors supprimé.- Le propriétaire, les administrateurs de l’organisation et les administrateurs de la plateforme ne sont jamais suspendus ni retirés par l’annuaire (
403). - L’adresse ne change pas : une autre adresse répond
400(scimTypemutability), la même est acceptée. Le nom ne change que pour un compte créé par l’organisation ou dont l’adresse est d’un domaine vérifié ; sinon il est ignoré sans erreur. - Les attributs inconnus sont ignorés.
PATCHLien vers la section PATCH
Opérations add, replace et remove, de 1 à 100 par requête, dans Operations. Chemins pris en charge : active, displayName, name, name.formatted, name.givenName, name.familyName, externalId, userName, emails, emails.value, emails[type . Une opération sans path accepte un objet dont les clés sont ces chemins.
remove vide name.givenName et name.familyName, répond 400 (mutability) sur active, et n’a pas d’effet sur les autres chemins. active accepte un booléen ou la chaîne "true" ou "false".
curl -X PATCH https://chat.realmoon.ai/api/v1/orgs/scim/v2/Users/$USER_ID \
-H "Authorization: Bearer $MOONAI_SCIM_TOKEN" \
-H "Content-Type: application/scim+json" \
-d '{
"schemas": ["urn:ietf:params:scim:api:messages:2.0:PatchOp"],
"Operations": [{ "op": "replace", "path": "active", "value": false }]
}'import os
import requests
r = requests.patch(
f"https://chat.realmoon.ai/api/v1/orgs/scim/v2/Users/{os.environ['USER_ID']}",
headers={
"Authorization": f"Bearer {os.environ['MOONAI_SCIM_TOKEN']}",
"Content-Type": "application/scim+json",
},
json={
"schemas": ["urn:ietf:params:scim:api:messages:2.0:PatchOp"],
"Operations": [{"op": "replace", "path": "active", "value": False}],
},
timeout=30,
)
r.raise_for_status()
print(r.json()["active"])const r = await fetch(`https://chat.realmoon.ai/api/v1/orgs/scim/v2/Users/${process.env.USER_ID}`, {
method: 'PATCH',
headers: {
Authorization: `Bearer ${process.env.MOONAI_SCIM_TOKEN}`,
'Content-Type': 'application/scim+json',
},
body: JSON.stringify({
schemas: ['urn:ietf:params:scim:api:messages:2.0:PatchOp'],
Operations: [{ op: 'replace', path: 'active', value: false }],
}),
});
if (!r.ok) throw new Error(await r.text());
console.log((await r.json()).active);GroupsLien vers la section Groups
Un Group est un groupe de l’organisation créé par l’annuaire. Ces groupes sont en lecture seule dans l’administration.
| Méthode et chemin | Rôle |
|---|---|
GET /Groups | Liste, avec filter : displayName , id eq "…", ou id ; excludedAttributes= allège la réponse. |
GET / | Un groupe, avec tous ses membres. |
POST /Groups | Créer un groupe (displayName obligatoire, members) ; répond 201. |
PUT / | Remplacer le nom et les membres. Sans members, le groupe est vidé. |
PATCH / | Chemins displayName, members et members[value (pour retirer un membre). |
DELETE / | Supprimer le groupe ; ses membres restent dans l’organisation. Répond 204. |
removesurmemberssans valeur vide le groupe. Un autre chemin répond400(invalidPath), saufexternalIdqui est ignoré.- Un identifiant de
membersqui n’est pas un membre de l’organisation est ignoré. - 5 000 membres au plus par liste
membersd’une requête ou d’une opération (400,tooMany). - 1 000 groupes au plus par organisation, groupes de l’administration compris (
400,tooMany). - Deux groupes ne peuvent pas porter le même nom, sans tenir compte de la casse, même avec un groupe de l’administration (
409,uniqueness). - L’annuaire ne remplit jamais un groupe qui donne le rôle d’administrateur (
409,mutability), ni un groupe dont les rôles dépassent ce que la personne qui a créé le jeton peut donner (403).
Effets sur l’organisationLien vers la section Effets sur l’organisation
Les créations et rattachements envoient l’événement webhook member.joined, les retraits member.removed, les suspensions member.suspended et les réactivations member.reactivated.
LimitesLien vers la section Limites
| Limite | Valeur |
|---|---|
| Requêtes par jeton | 600 par minute (429, scimType tooMany, Retry-After: ) |
| Échecs d’authentification par adresse IP | 60 en 10 minutes, puis 429 |
| Taille du corps | 1 Mio (413) |
| Opérations par PATCH | 100 |
ErreursLien vers la section Erreurs
Les erreurs suivent le format SCIM :
{
"schemas": ["urn:ietf:params:scim:api:messages:2.0:Error"],
"status": "409",
"scimType": "uniqueness",
"detail": "Un groupe porte déjà ce nom."
}| Statut | Cause |
|---|---|
| 400 | Requête invalide : JSON illisible, filtre non pris en charge, opération ou valeur invalide. |
| 401 | Jeton absent ou invalide. |
| 403 | Personne ou groupe protégé, suspension décidée dans l’administration. |
| 404 | Membre ou groupe introuvable. |
| 409 | Domaine non vérifié, déjà membre, sièges pleins, nom de groupe pris, groupe administrateur. |
| 413 | Corps trop grand. |
| 429 | Trop de requêtes ou d’échecs d’authentification. |
| 503 | Service momentanément indisponible ; réessayez. |
scimType est présent quand il précise la cause : uniqueness, mutability, invalidFilter, invalidSyntax, invalidPath, invalidValue, tooMany.