iblai-api-scim
Provision and manage an organization's directory over the SCIM 2.0 REST API:
users (modeled on the SCIM User schema plus the Enterprise User extension) and
RBAC groups (exposed via the SCIM Group schema), including RBAC group
membership that automatically links users to the platforms behind those groups.
Use when standing up or syncing directory provisioning for an org — creating or
deactivating users, managing RBAC group membership, or wiring an identity
provider's SCIM provisioning.
Scope is exactly two resources: Users and Groups. The SCIM app in the
DM service registers only these two — there are no Departments,
DepartmentMembers, or GroupMembers SCIM endpoints. (Departments and non-RBAC
groups still appear inside a user's Enterprise-extension response block, but
they are read-only there; there are no SCIM routes to manage them.)
Auth & conventions
- Base URL:
https://api.iblai.app/dm — these are Data Manager (DM)
endpoints, so the /dm prefix is required; the /api/orgs/…/scim/v2/…
paths below are appended to it (e.g.
https://api.iblai.app/dm/api/orgs/{platform_key}/scim/v2/Users).
- Header: SCIM accepts two auth schemes (both authentication classes are
wired on every view:
PlatformApiKeyAuthentication,
OAuth2ClientCredentialAuthentication):
Authorization: Api-Token $IBLAI_API_KEY — the Platform API Token used by
the other iblai-* skills; or
Authorization: Bearer <oauth2-token> — an OAuth2 client-credentials token.
- Content type: send
Content-Type: application/scim+json on every write
(the server also accepts plain application/json).
- Path / scope: SCIM paths are
/api/orgs/{platform_key}/scim/v2/... where
{platform_key} = $IBLAI_ORG (the org key). The token's scope must match the
platform_key in the URL.
- Resource casing — keep verbatim. Both resources are PascalCase:
Users and Groups. Do not lowercase them.
- Trailing slash — keep verbatim. Collection routes have no trailing
slash (
/Users, /Groups); single-resource detail routes require a
trailing slash (/Users/{id}/, /Groups/{id}/). The group action routes
(/Groups/{id}/add_members, /Groups/{id}/remove_members) have no trailing
slash.
- DELETE / destructive calls — confirm with the user first.
- Not connected yet? Run
/iblai-api-login first to populate IBLAI_ORG,
IBLAI_USERNAME, and IBLAI_API_KEY.
Reads
Users
User resources use the SCIM core User schema
(urn:ietf:params:scim:schemas:core:2.0:User) with the Enterprise User
extension on responses
(urn:ietf:params:scim:schemas:extension:enterprise:2.0:User).
- GET
/api/orgs/{platform_key}/scim/v2/Users — list users (filtering,
sorting, and pagination supported — see Notes).
- GET
/api/orgs/{platform_key}/scim/v2/Users/{id}/ — retrieve one user.
Response — Enterprise extension. The extension block is returned under the
key urn_ietf_params_scim_schemas_extension_enterprise_2_0_User (underscores,
not colons) and carries:
edxData — edX-specific user data.
userData — additional user metadata.
departments — departments the user belongs to (id, name, $ref, isAdmin).
groups — user-groups the user belongs to (id, name, $ref).
rbacGroups — RBAC groups (id, uniqueId, name, description, platformKey).
platforms — platforms the user can access (id, key, name, org).
(On create/update/PATCH responses these lists are returned empty; the populated
lists come back on GET list/retrieve.)
Groups (RBAC groups)
The Group resource maps to the platform's RBAC groups and is exposed via the
SCIM Group schema (urn:ietf:params:scim:schemas:core:2.0:Group). The SCIM id
field is the RBAC group's unique_id (not a numeric id). These endpoints
manage existing RBAC groups only — POST does not create a new group; if the
referenced group's unique_id does not exist you get a 404 SCIM error.
- GET
/api/orgs/{platform_key}/scim/v2/Groups — list RBAC groups
(filtering supported; pass excludedAttributes=members to omit the member list).
- GET
/api/orgs/{platform_key}/scim/v2/Groups/{id}/ — retrieve one RBAC
group by unique_id.
Writes
Users
- POST
/api/orgs/{platform_key}/scim/v2/Users — create (or, with
"update": true, modify an existing) user.
- PUT
/api/orgs/{platform_key}/scim/v2/Users/{id}/ — replace a user's core
fields (userName, name.formatted, primary emails value, active); only
the fields present in the body are changed.
- PATCH
/api/orgs/{platform_key}/scim/v2/Users/{id}/ — patch a user. Accepts
either a SCIM PatchOp body (Operations with op/path/value; supported
paths: userName, name.formatted, emails[type eq "work"].value, active,
displayName, and externalId — where externalId is treated as an RBAC group
unique_id to add the user to) or a plain partial body (userName, name,
emails, active, displayName).
- DELETE
/api/orgs/{platform_key}/scim/v2/Users/{id}/ — not supported;
returns 405 Method Not Allowed. (User deactivation is done by setting
active: false, not by DELETE.)
Request body for POST (key attributes):
{
"schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"],
"userName": "john.doe@example.com",
"name": { "formatted": "John Doe", "givenName": "John", "familyName": "Doe" },
"emails": [{ "value": "john.doe@example.com", "primary": true }],
"password": "securepassword123",
"provider": "google-oauth2",
"tpaUid": "john.doe@example.com",
"isStaff": false,
"active": true,
"update": false,
"platformOrgs": ["org1", "org2"],
"departmentIds": [1, 2, 3],
"groupIds": [1, 2, 3],
"rbacGroupUniqueIds": ["students", "course_123_enrolled"]
}
Field notes: userName is the unique username/email (required); emails is
required; name is required (may be partly blank); password / provider /
tpaUid / isStaff are optional and forwarded to the LMS on create;
active toggles account activation (set false to deactivate); update: true
modifies an existing user instead of creating (skips the duplicate check);
platformOrgs links the user to those org platforms; departmentIds /
groupIds assign existing departments / user-groups by integer id (validated —
unknown ids return 400); rbacGroupUniqueIds assigns RBAC groups by unique id
and auto-links the required platforms (see Notes). On a create that finds the
user already exists, the API returns the existing user with HTTP 200 and still
applies the link fields, rather than erroring.
Groups (RBAC groups)
POST /api/orgs/{platform_key}/scim/v2/Groups — manage an existing RBAC
group: optionally update its displayName / description and set its full
member list. The group is located by id (or externalId), both of which map
to the RBAC group's unique_id.
{
"schemas": ["urn:ietf:params:scim:schemas:core:2.0:Group"],
"id": "students",
"displayName": "Students",
"description": "All enrolled students",
"members": [{ "value": "user-id-1", "display": "john.doe@example.com" }]
}
members[].value is resolved as a user id, then username, then email; if no
user matches, one is created from the member data. The member list passed here
replaces the group's current membership.
PUT /api/orgs/{platform_key}/scim/v2/Groups/{id}/ — update an existing RBAC
group's displayName / description, and (if members is present) replace
its complete member list (same body shape as POST, without needing id in the
body — the id comes from the URL).
PATCH /api/orgs/{platform_key}/scim/v2/Groups/{id}/ — patch an RBAC group.
Accepts a SCIM PatchOp body whose Operations support
replace on displayName / description / members (replace whole list),
add on members, and remove with a members[value eq "<user-id>"] path —
or a plain partial body (displayName, description, members).
POST /api/orgs/{platform_key}/scim/v2/Groups/{id}/add_members — add members
via a SCIM PatchOp (only op: add, path: members operations are applied).
{
"schemas": ["urn:ietf:params:scim:api:messages:2.0:PatchOp"],
"Operations": [
{ "op": "add", "path": "members",
"value": [{ "value": "user-id-3", "display": "bob.wilson@example.com" }] }
]
}
POST /api/orgs/{platform_key}/scim/v2/Groups/{id}/remove_members — remove
members via a SCIM PatchOp. Each operation must be op: remove with a path of
the form members[value eq "<user-id>"].
{
"schemas": ["urn:ietf:params:scim:api:messages:2.0:PatchOp"],
"Operations": [
{ "op": "remove", "path": "members[value eq \"user-id-1\"]" }
]
}
DELETE /api/orgs/{platform_key}/scim/v2/Groups/{id}/ — hard-delete the RBAC
group (returns 204). Confirm with the user first.
Example
Create a SCIM user with RBAC groups (which auto-link the user's platforms):
curl -X POST \
"https://api.iblai.app/dm/api/orgs/$IBLAI_ORG/scim/v2/Users" \
-H "Authorization: Api-Token $IBLAI_API_KEY" \
-H "Content-Type: application/scim+json" \
-d '{
"schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"],
"userName": "jane.smith@example.com",
"name": { "formatted": "Jane Smith", "givenName": "Jane", "familyName": "Smith" },
"emails": [{ "value": "jane.smith@example.com", "primary": true }],
"active": true,
"rbacGroupUniqueIds": ["students", "course_123_enrolled"]
}'
Notes
- Error schema. Group-not-found returns the SCIM error object:
{"schemas": ["urn:ietf:params:scim:api:messages:2.0:Error"], "scimType": "invalidValue", "detail": "..."} with 404. Other failures return
a plain {"error": "..."} body. Standard HTTP codes apply: 400
invalid/missing fields (e.g. unknown departmentIds / groupIds /
rbacGroupUniqueIds), 401 missing/invalid auth, 404 not found, 405
on DELETE Users/{id}/, 409 the user already exists on create (on a local
match the API instead returns the existing user with 200 — use update: true
to modify), 500 server error.
- Editing semantics. Users are edited by re-POSTing with
update: true,
or via PUT Users/{id}/ / PATCH Users/{id}/. Groups are edited via
PUT Groups/{id}/ (replaces the whole members list) or PATCH Groups/{id}/;
incremental membership changes go through the dedicated
add_members / remove_members actions, which take a PatchOp body
(urn:ietf:params:scim:api:messages:2.0:PatchOp with an Operations array of
op / path / value).
- RBAC group assignment auto-links platforms. Assigning
rbacGroupUniqueIds on a user validates the groups exist, then automatically
creates platform links for any platforms behind those RBAC groups the user
isn't already linked to — so you can grant RBAC access without listing
platformOrgs explicitly. RBAC assignment runs after all other linking. Default
RBAC groups (server-configured) are applied to every user on platform link;
rbacGroupUniqueIds is additive on top of those.
- Group lookup is platform-scoped, with optional fallback. Group lookups try
(unique_id, platform_key) first; if the server has strict platform matching
disabled, they fall back to matching unique_id on any platform.
- Filtering, sorting & pagination. Both list endpoints accept SCIM
filter
expressions (e.g. userName eq "john.doe", displayName eq "Students"; user
filters support userName, name.formatted, emails.value, active, id,
externalId; group filters support displayName, id, description).
Users list also honors startIndex / count pagination (1-based;
defaults startIndex=1, count=50), sortBy (userName, name.formatted,
active) / sortOrder (ascending / descending), and attributes /
excludedAttributes. Groups list does not paginate (it returns all
matching groups) but does honor excludedAttributes=members.
- Enterprise extension key. On responses the extension block's key uses
underscores —
urn_ietf_params_scim_schemas_extension_enterprise_2_0_User —
not the colon-delimited URN.
- Schema reference. Interactive SCIM API docs (drf-spectacular) are published
at
https://base.manager.iblai.tech/api-docs/#/scim.
1---2name: iblai-api-scim3description: iblai-api-scim4---56# iblai-api-scim78Provision and manage an organization's **directory over the SCIM 2.0 REST API**:9users (modeled on the SCIM User schema plus the Enterprise User extension) and10**RBAC groups** (exposed via the SCIM Group schema), including RBAC group11membership that automatically links users to the platforms behind those groups.12Use when standing up or syncing directory provisioning for an org — creating or13deactivating users, managing RBAC group membership, or wiring an identity14provider's SCIM provisioning.1516> **Scope is exactly two resources: `Users` and `Groups`.** The SCIM app in the17> DM service registers only these two — there are **no** Departments,18> DepartmentMembers, or GroupMembers SCIM endpoints. (Departments and non-RBAC19> groups still appear *inside* a user's Enterprise-extension response block, but20> they are read-only there; there are no SCIM routes to manage them.)2122## Auth & conventions2324- **Base URL:** `https://api.iblai.app/dm` — these are Data Manager (DM)25 endpoints, so the **`/dm` prefix is required**; the `/api/orgs/…/scim/v2/…`26 paths below are appended to it (e.g.27 `https://api.iblai.app/dm/api/orgs/{platform_key}/scim/v2/Users`).28- **Header:** SCIM accepts **two** auth schemes (both authentication classes are29 wired on every view: `PlatformApiKeyAuthentication`,30 `OAuth2ClientCredentialAuthentication`):31 - `Authorization: Api-Token $IBLAI_API_KEY` — the Platform API Token used by32 the other `iblai-*` skills; or33 - `Authorization: Bearer <oauth2-token>` — an OAuth2 client-credentials token.34- **Content type:** send `Content-Type: application/scim+json` on every write35 (the server also accepts plain `application/json`).36- **Path / scope:** SCIM paths are `/api/orgs/{platform_key}/scim/v2/...` where37 `{platform_key}` = `$IBLAI_ORG` (the org key). The token's scope must match the38 `platform_key` in the URL.39- **Resource casing — keep verbatim.** Both resources are **PascalCase**:40 `Users` and `Groups`. Do not lowercase them.41- **Trailing slash — keep verbatim.** Collection routes have **no** trailing42 slash (`/Users`, `/Groups`); single-resource detail routes **require** a43 trailing slash (`/Users/{id}/`, `/Groups/{id}/`). The group action routes44 (`/Groups/{id}/add_members`, `/Groups/{id}/remove_members`) have **no** trailing45 slash.46- DELETE / destructive calls — **confirm with the user first.**47- Not connected yet? Run **`/iblai-api-login`** first to populate `IBLAI_ORG`,48 `IBLAI_USERNAME`, and `IBLAI_API_KEY`.4950## Reads5152### Users5354User resources use the SCIM core User schema55(`urn:ietf:params:scim:schemas:core:2.0:User`) with the Enterprise User56extension on responses57(`urn:ietf:params:scim:schemas:extension:enterprise:2.0:User`).5859- **GET** `/api/orgs/{platform_key}/scim/v2/Users` — list users (filtering,60 sorting, and pagination supported — see Notes).61- **GET** `/api/orgs/{platform_key}/scim/v2/Users/{id}/` — retrieve one user.6263**Response — Enterprise extension.** The extension block is returned under the64key `urn_ietf_params_scim_schemas_extension_enterprise_2_0_User` (underscores,65not colons) and carries:6667- `edxData` — edX-specific user data.68- `userData` — additional user metadata.69- `departments` — departments the user belongs to (`id`, `name`, `$ref`, `isAdmin`).70- `groups` — user-groups the user belongs to (`id`, `name`, `$ref`).71- `rbacGroups` — RBAC groups (`id`, `uniqueId`, `name`, `description`, `platformKey`).72- `platforms` — platforms the user can access (`id`, `key`, `name`, `org`).7374(On create/update/PATCH responses these lists are returned empty; the populated75lists come back on GET list/retrieve.)7677### Groups (RBAC groups)7879The Group resource maps to the platform's **RBAC groups** and is exposed via the80SCIM Group schema (`urn:ietf:params:scim:schemas:core:2.0:Group`). The SCIM `id`81field is the RBAC group's **`unique_id`** (not a numeric id). These endpoints82**manage existing RBAC groups only** — POST does not create a new group; if the83referenced group's `unique_id` does not exist you get a `404` SCIM error.8485- **GET** `/api/orgs/{platform_key}/scim/v2/Groups` — list RBAC groups86 (filtering supported; pass `excludedAttributes=members` to omit the member list).87- **GET** `/api/orgs/{platform_key}/scim/v2/Groups/{id}/` — retrieve one RBAC88 group by `unique_id`.8990## Writes9192### Users9394- **POST** `/api/orgs/{platform_key}/scim/v2/Users` — create (or, with95 `"update": true`, modify an existing) user.96- **PUT** `/api/orgs/{platform_key}/scim/v2/Users/{id}/` — replace a user's core97 fields (`userName`, `name.formatted`, primary `emails` value, `active`); only98 the fields present in the body are changed.99- **PATCH** `/api/orgs/{platform_key}/scim/v2/Users/{id}/` — patch a user. Accepts100 either a SCIM **PatchOp** body (`Operations` with `op`/`path`/`value`; supported101 paths: `userName`, `name.formatted`, `emails[type eq "work"].value`, `active`,102 `displayName`, and `externalId` — where `externalId` is treated as an RBAC group103 `unique_id` to add the user to) **or** a plain partial body (`userName`, `name`,104 `emails`, `active`, `displayName`).105- **DELETE** `/api/orgs/{platform_key}/scim/v2/Users/{id}/` — **not supported**;106 returns `405 Method Not Allowed`. (User deactivation is done by setting107 `active: false`, not by DELETE.)108109Request body for POST (key attributes):110111```json112{113 "schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"],114 "userName": "john.doe@example.com",115 "name": { "formatted": "John Doe", "givenName": "John", "familyName": "Doe" },116 "emails": [{ "value": "john.doe@example.com", "primary": true }],117 "password": "securepassword123",118 "provider": "google-oauth2",119 "tpaUid": "john.doe@example.com",120 "isStaff": false,121 "active": true,122 "update": false,123 "platformOrgs": ["org1", "org2"],124 "departmentIds": [1, 2, 3],125 "groupIds": [1, 2, 3],126 "rbacGroupUniqueIds": ["students", "course_123_enrolled"]127}128```129130Field notes: `userName` is the unique username/email (required); `emails` is131required; `name` is required (may be partly blank); `password` / `provider` /132`tpaUid` / `isStaff` are optional and forwarded to the LMS on create;133`active` toggles account activation (set `false` to deactivate); `update: true`134modifies an existing user instead of creating (skips the duplicate check);135`platformOrgs` links the user to those org platforms; `departmentIds` /136`groupIds` assign existing departments / user-groups by integer id (validated —137unknown ids return `400`); `rbacGroupUniqueIds` assigns RBAC groups by unique id138and auto-links the required platforms (see Notes). On a create that finds the139user already exists, the API returns the existing user with HTTP `200` and still140applies the link fields, rather than erroring.141142### Groups (RBAC groups)143144- **POST** `/api/orgs/{platform_key}/scim/v2/Groups` — manage an existing RBAC145 group: optionally update its `displayName` / `description` and **set** its full146 member list. The group is located by `id` (or `externalId`), both of which map147 to the RBAC group's `unique_id`.148149 ```json150 {151 "schemas": ["urn:ietf:params:scim:schemas:core:2.0:Group"],152 "id": "students",153 "displayName": "Students",154 "description": "All enrolled students",155 "members": [{ "value": "user-id-1", "display": "john.doe@example.com" }]156 }157 ```158159 `members[].value` is resolved as a user id, then username, then email; if no160 user matches, one is created from the member data. The member list passed here161 **replaces** the group's current membership.162163- **PUT** `/api/orgs/{platform_key}/scim/v2/Groups/{id}/` — update an existing RBAC164 group's `displayName` / `description`, and (if `members` is present) **replace**165 its complete member list (same body shape as POST, without needing `id` in the166 body — the id comes from the URL).167- **PATCH** `/api/orgs/{platform_key}/scim/v2/Groups/{id}/` — patch an RBAC group.168 Accepts a SCIM **PatchOp** body whose `Operations` support169 `replace` on `displayName` / `description` / `members` (replace whole list),170 `add` on `members`, and `remove` with a `members[value eq "<user-id>"]` path —171 **or** a plain partial body (`displayName`, `description`, `members`).172- **POST** `/api/orgs/{platform_key}/scim/v2/Groups/{id}/add_members` — add members173 via a SCIM PatchOp (only `op: add`, `path: members` operations are applied).174175 ```json176 {177 "schemas": ["urn:ietf:params:scim:api:messages:2.0:PatchOp"],178 "Operations": [179 { "op": "add", "path": "members",180 "value": [{ "value": "user-id-3", "display": "bob.wilson@example.com" }] }181 ]182 }183 ```184185- **POST** `/api/orgs/{platform_key}/scim/v2/Groups/{id}/remove_members` — remove186 members via a SCIM PatchOp. Each operation must be `op: remove` with a path of187 the form `members[value eq "<user-id>"]`.188189 ```json190 {191 "schemas": ["urn:ietf:params:scim:api:messages:2.0:PatchOp"],192 "Operations": [193 { "op": "remove", "path": "members[value eq \"user-id-1\"]" }194 ]195 }196 ```197198- **DELETE** `/api/orgs/{platform_key}/scim/v2/Groups/{id}/` — hard-delete the RBAC199 group (returns `204`). **Confirm with the user first.**200201## Example202203Create a SCIM user with RBAC groups (which auto-link the user's platforms):204205```bash206curl -X POST \207 "https://api.iblai.app/dm/api/orgs/$IBLAI_ORG/scim/v2/Users" \208 -H "Authorization: Api-Token $IBLAI_API_KEY" \209 -H "Content-Type: application/scim+json" \210 -d '{211 "schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"],212 "userName": "jane.smith@example.com",213 "name": { "formatted": "Jane Smith", "givenName": "Jane", "familyName": "Smith" },214 "emails": [{ "value": "jane.smith@example.com", "primary": true }],215 "active": true,216 "rbacGroupUniqueIds": ["students", "course_123_enrolled"]217 }'218```219220## Notes221222- **Error schema.** Group-not-found returns the SCIM error object:223 `{"schemas": ["urn:ietf:params:scim:api:messages:2.0:Error"],224 "scimType": "invalidValue", "detail": "..."}` with `404`. Other failures return225 a plain `{"error": "..."}` body. Standard HTTP codes apply: `400`226 invalid/missing fields (e.g. unknown `departmentIds` / `groupIds` /227 `rbacGroupUniqueIds`), `401` missing/invalid auth, `404` not found, `405`228 on `DELETE Users/{id}/`, `409` the user already exists on create (on a local229 match the API instead returns the existing user with `200` — use `update: true`230 to modify), `500` server error.231- **Editing semantics.** **Users** are edited by re-POSTing with `update: true`,232 or via `PUT Users/{id}/` / `PATCH Users/{id}/`. **Groups** are edited via233 `PUT Groups/{id}/` (replaces the whole `members` list) or `PATCH Groups/{id}/`;234 incremental membership changes go through the dedicated235 `add_members` / `remove_members` actions, which take a **PatchOp** body236 (`urn:ietf:params:scim:api:messages:2.0:PatchOp` with an `Operations` array of237 `op` / `path` / `value`).238- **RBAC group assignment auto-links platforms.** Assigning239 `rbacGroupUniqueIds` on a user validates the groups exist, then automatically240 creates platform links for any platforms behind those RBAC groups the user241 isn't already linked to — so you can grant RBAC access without listing242 `platformOrgs` explicitly. RBAC assignment runs after all other linking. Default243 RBAC groups (server-configured) are applied to every user on platform link;244 `rbacGroupUniqueIds` is additive on top of those.245- **Group lookup is platform-scoped, with optional fallback.** Group lookups try246 `(unique_id, platform_key)` first; if the server has strict platform matching247 disabled, they fall back to matching `unique_id` on any platform.248- **Filtering, sorting & pagination.** Both list endpoints accept SCIM `filter`249 expressions (e.g. `userName eq "john.doe"`, `displayName eq "Students"`; user250 filters support `userName`, `name.formatted`, `emails.value`, `active`, `id`,251 `externalId`; group filters support `displayName`, `id`, `description`).252 **`Users` list** also honors `startIndex` / `count` pagination (1-based;253 defaults `startIndex=1`, `count=50`), `sortBy` (`userName`, `name.formatted`,254 `active`) / `sortOrder` (`ascending` / `descending`), and `attributes` /255 `excludedAttributes`. **`Groups` list** does **not** paginate (it returns all256 matching groups) but does honor `excludedAttributes=members`.257- **Enterprise extension key.** On responses the extension block's key uses258 underscores — `urn_ietf_params_scim_schemas_extension_enterprise_2_0_User` —259 not the colon-delimited URN.260- **Schema reference.** Interactive SCIM API docs (drf-spectacular) are published261 at `https://base.manager.iblai.tech/api-docs/#/scim`.262</content>263</invoke>