# SCIM 2.0

> Le service SCIM 2.0 d’une organisation gérée : jeton de l’annuaire, ressources Users et Groups, filtres, opérations PATCH, limites et erreurs au format SCIM.

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](https://realmoon.ai/documentation/organisations/scim/).

## Adresse et authentification

| Élément | Valeur |
| --- | --- |
| Adresse de base | `https://chat.realmoon.ai/api/v1/orgs/scim/v2` |
| Authentification | `Authorization: Bearer moai_scim_…` |
| Réponses | `application/scim+json` ; 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: Bearer` et le message « Jeton invalide. ». Toutes les routes demandent le jeton, y compris `/ServiceProviderConfig`, `/ResourceTypes` et `/Schemas`.

## 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://chat.realmoon.ai/api/v1/orgs/{org_id}` :

| Méthode et chemin | Qui | Rôle |
| --- | --- | --- |
| `GET /scim-token` | Clé avec `security:read` | État du jeton, jamais sa valeur. |
| `POST /scim-token` | Propriétaire ou administrateur, avec sa session | Créer un jeton et révoquer le précédent. |
| `DELETE /scim-token` | 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 owner_only` sur `POST` et `DELETE`.

```json
{
  "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 du jeton>`.

## Ce que le service annonce

`GET /ServiceProviderConfig` :

| 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 /ResourceTypes` et `GET /Schemas` décrivent les ressources `User` et `Group`.

## 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 /Users/{id}` | Un membre. |
| `POST /Users` | Ajouter un membre ; répond `201`. |
| `PUT /Users/{id}` | Appliquer les attributs envoyés. |
| `PATCH /Users/{id}` | Modifier des attributs. |
| `DELETE /Users/{id}` | Retirer le membre de l’organisation ; répond `204` sans corps. |

### Filtres

Une seule condition `attribut eq "valeur"`, sur `userName`, `externalId`, `id`, `emails`, `emails.value` ou `emails[type eq "work"].value`. 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 /Users?filter=userName eq "camille.martin@ecole.fr"`.

### Une ressource User

```json
{
  "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 membre

- L’adresse est lue dans `userName`, sinon dans l’adresse `primary`, sinon la première de `emails`.
- Elle doit être dans un domaine vérifié par l’organisation, sinon `409` (`scimType` `invalidValue`), que le compte existe ou non.
- Un compte inconnu est créé ; un compte existant est rattaché. Le nom vient de `name.formatted`, sinon de `givenName` et `familyName`, sinon de `displayName`.
- `active` absent vaut `true` ; `active: false` crée le membre suspendu.
- `409` aussi 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 retirer

- `PUT` n’applique que les attributs présents : un attribut absent ne change rien.
- **`active: false`** suspend le membre et ferme toutes les sessions de son compte. **`active: true`** lève une suspension faite par l’annuaire ; une suspension décidée dans l’administration répond `403`. Sur un workspace payé par carte, la réactivation se fait depuis l’administration (`409`).
- **`DELETE`** retire 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` (`scimType` `mutability`), 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.

### 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 eq "work"].value`. 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"`.

```bash title="cURL"
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 }]
  }'
```

```python title="Python"
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"])
```

```javascript title="JavaScript"
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);
```

## 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 eq "…"`, `id eq "…"`, ou `id eq "…" and members[value eq "…"]` ; `excludedAttributes=members` allège la réponse. |
| `GET /Groups/{id}` | Un groupe, avec tous ses membres. |
| `POST /Groups` | Créer un groupe (`displayName` obligatoire, `members`) ; répond `201`. |
| `PUT /Groups/{id}` | Remplacer le nom et les membres. Sans `members`, le groupe est vidé. |
| `PATCH /Groups/{id}` | Chemins `displayName`, `members` et `members[value eq "<id>"]` (pour retirer un membre). |
| `DELETE /Groups/{id}` | Supprimer le groupe ; ses membres restent dans l’organisation. Répond `204`. |

- `remove` sur `members` sans valeur vide le groupe. Un autre chemin répond `400` (`invalidPath`), sauf `externalId` qui est ignoré.
- Un identifiant de `members` qui n’est pas un membre de l’organisation est ignoré.
- 5 000 membres au plus par liste `members` d’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’organisation

Les créations et rattachements envoient l’événement [webhook](https://realmoon.ai/documentation/api-organisations/webhooks/) `member.joined`, les retraits `member.removed`, les suspensions `member.suspended` et les réactivations `member.reactivated`.

## Limites

| Limite | Valeur |
| --- | --- |
| Requêtes par jeton | 600 par minute (`429`, `scimType` `tooMany`, `Retry-After: 60`) |
| Échecs d’authentification par adresse IP | 60 en 10 minutes, puis `429` |
| Taille du corps | 1 Mio (`413`) |
| Opérations par PATCH | 100 |

## Erreurs

Les erreurs suivent le format SCIM :

```json
{
  "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`.

## Voir aussi

- [Domaines](https://realmoon.ai/documentation/api-organisations/domaines/)
- [Connexion unique](https://realmoon.ai/documentation/api-organisations/sso/)
- [Groupes](https://realmoon.ai/documentation/api-organisations/groupes/)

---

Documentation Moon AI › API des organisations › SCIM 2.0. Page : https://realmoon.ai/documentation/api-organisations/scim/. 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.
