An organization’s SCIM 2.0 service follows RFC 7643 and RFC 7644. Your directory (Microsoft Entra ID, Okta, Google Workspace…) calls it to create, suspend and remove members, and to keep groups up to date. The step-by-step setup is in SCIM directory.
URL and authenticationLink to the URL and authentication section
| Item | Value |
|---|---|
| Base URL | https:// |
| Authentication | Authorization: |
| Responses | application/; the request’s content type is not checked |
The token identifies the organization: the URL never contains its ID. A missing, wrong or revoked token, or one from a personal workspace, gets 401 with WWW-Authenticate: and the message Jeton (invalid token). Every route requires the token, including /, / and /Schemas.
The directory tokenLink to the The directory token section
An organization has a single active token. The organization’s owner or an admin creates it in the admin console › “Sign-in” › “Directory synchronization (SCIM)”. It is shown only once; creating a new one revokes the previous one immediately.
The token routes are under https://:
| Method and path | Who | Purpose |
|---|---|---|
GET / | Key with security:read | Token status, never its value. |
POST / | Owner or admin, with their session | Create a token and revoke the previous one. |
DELETE / | Owner or admin, with their session | Revoke the active token (404 if there is none). |
An admin key gets 403 on POST and 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 is updated at most once a minute. The creation response also includes token (its value) and revoked_previous. Every write from the directory is recorded in the organization’s log with the author scim:<start .
What the service advertisesLink to the What the service advertises section
GET /:
| Feature | Supported |
|---|---|
patch | yes |
filter | yes, up to 200 results |
bulk | no |
changePassword | no |
sort | no |
etag | no |
| Authentication | bearer token (oauthbearertoken) |
GET / and GET /Schemas describe the User and Group resources.
UsersLink to the Users section
A User is a member of the organization, active or suspended.
| Method and path | Purpose |
|---|---|
GET /Users | List, with startIndex (starting at 1), count (100 by default, up to 200) and filter; sorted by join date. |
GET / | One member. |
POST /Users | Add a member; returns 201. |
PUT / | Apply the attributes sent. |
PATCH / | Update attributes. |
DELETE / | Remove the member from the organization; returns 204 with no body. |
FiltersLink to the Filters section
A single attribute condition, on userName, externalId, id, emails, emails.value or emails[type . Only the eq operator is supported; the value is up to 512 characters and the filter up to 1,024. userName and addresses are compared case-insensitively. Example: GET /.
A User resourceLink to the A User resource section
{
"schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"],
"id": "<account id>",
"externalId": "<id in your directory>",
"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": "<group id>", "display": "Site de Lyon", "$ref": "https://chat.realmoon.ai/api/v1/orgs/scim/v2/Groups/<group id>" }],
"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/<account id>" }
}groups only lists the groups created by the directory. givenName and familyName come from the display name, split at the first space.
Create a memberLink to the Create a member section
- The address is read from
userName, otherwise from theprimaryaddress, otherwise from the first entry inemails. - It must belong to a domain verified by the organization, otherwise
409(scimTypeinvalidValue), whether or not the account exists. - An unknown account is created; an existing account is attached. The name comes from
name.formatted, otherwise fromgivenNameandfamilyName, otherwise fromdisplayName. - A missing
activemeanstrue;active:creates the member as suspended.false 409also when the person already has a place in the organization (uniqueness), when every seat is taken, or for the account of a platform administrator. Same response on a workspace paid by card: each added seat would be billed, so invite the person from the admin console instead.
Update and removeLink to the Update and remove section
PUTonly applies the attributes present: a missing attribute changes nothing.active:suspends the member and closes every session of their account.false active: truelifts a suspension made by the directory; a suspension decided in the admin console returns403. On a workspace paid by card, reactivation is done from the admin console (409).DELETEremoves the member and closes their sessions. Their account remains, unless it was created by this directory, has had no activity and belongs to no other team workspace: it is then deleted.- The owner, the organization’s admins and platform administrators are never suspended or removed by the directory (
403). - The address does not change: a different address returns
400(scimTypemutability), the same one is accepted. The name only changes for an account created by the organization or whose address is in a verified domain; otherwise it is ignored without an error. - Unknown attributes are ignored.
PATCHLink to the PATCH section
add, replace and remove operations, 1 to 100 per request, in Operations. Supported paths: active, displayName, name, name.formatted, name.givenName, name.familyName, externalId, userName, emails, emails.value, emails[type . An operation without a path accepts an object whose keys are these paths.
remove clears name.givenName and name.familyName, returns 400 (mutability) on active, and has no effect on other paths. active accepts a boolean or the string "true" or "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);GroupsLink to the Groups section
A Group is an organization group created by the directory. These groups are read-only in the admin console.
| Method and path | Purpose |
|---|---|
GET /Groups | List, with filter: displayName , id eq "…", or id ; excludedAttributes= makes the response lighter. |
GET / | One group, with all its members. |
POST /Groups | Create a group (displayName required, members); returns 201. |
PUT / | Replace the name and the members. Without members, the group is emptied. |
PATCH / | Paths displayName, members and members[value (to remove a member). |
DELETE / | Delete the group; its members stay in the organization. Returns 204. |
removeonmemberswithout a value empties the group. Any other path returns400(invalidPath), exceptexternalId, which is ignored.- An ID in
membersthat is not a member of the organization is ignored. - Up to 5,000 members per
memberslist in a request or an operation (400,tooMany). - Up to 1,000 groups per organization, including groups created in the admin console (
400,tooMany). - Two groups cannot have the same name, case-insensitive, including a group from the admin console (
409,uniqueness). - The directory never fills a group that grants the admin role (
409,mutability), nor a group whose roles go beyond what the person who created the token can give (403).
Effects on the organizationLink to the Effects on the organization section
Creating or attaching a member sends the member.joined webhook event, removals send member.removed, suspensions member.suspended and reactivations member.reactivated.
LimitsLink to the Limits section
| Limit | Value |
|---|---|
| Requests per token | 600 per minute (429, scimType tooMany, Retry-After: ) |
| Authentication failures per IP address | 60 in 10 minutes, then 429 |
| Body size | 1 MiB (413) |
| Operations per PATCH | 100 |
ErrorsLink to the Errors section
Errors follow the SCIM format. The detail text is in French:
{
"schemas": ["urn:ietf:params:scim:api:messages:2.0:Error"],
"status": "409",
"scimType": "uniqueness",
"detail": "Un groupe porte déjà ce nom."
}| Status | Cause |
|---|---|
| 400 | Invalid request: unreadable JSON, unsupported filter, invalid operation or value. |
| 401 | Missing or invalid token. |
| 403 | Protected person or group, suspension decided in the admin console. |
| 404 | Member or group not found. |
| 409 | Domain not verified, already a member, no seats left, group name taken, admin group. |
| 413 | Body too large. |
| 429 | Too many requests or authentication failures. |
| 503 | Service temporarily unavailable; try again. |
scimType is present when it narrows down the cause: uniqueness, mutability, invalidFilter, invalidSyntax, invalidPath, invalidValue, tooMany.