SCIM user provisioning
EmbayLMS supports SCIM 2.0 (System for Cross-domain Identity Management) so your identity provider — Okta, Microsoft Entra ID (Azure AD), or any SCIM-2.0 client — can create, update, and deactivate EmbayLMS user accounts automatically, keeping them in sync with your HR system.
When an employee is onboarded in your IdP, the IdP provisions an EmbayLMS account. When their attributes change (name, role), the IdP pushes the update. When they are offboarded, the IdP deactivates the account in EmbayLMS — the learning record is preserved, not deleted.
SCIM vs. SSO: SCIM manages accounts (provisioning). SAML/OIDC manages logins (authentication). They are independent — you can run either alone, but most organizations run SCIM alongside SAML or OIDC so accounts are both auto-created and SSO-enabled.
Base endpoint
SCIM is served per-tenant on your own subdomain. The base URL is:
https://<your-tenant>.embaylms.com/api/v1/scim/v2Two resource endpoints are implemented:
| Endpoint | Resource |
|---|---|
/api/v1/scim/v2/Users | User provisioning |
/api/v1/scim/v2/Groups | Group provisioning |
All requests authenticate with a Bearer token (see Step 1) and the tenant is resolved automatically from the subdomain.
Step 1 — Generate a SCIM bearer token
- In EmbayLMS, go to Settings → User Management → SCIM Provisioning.
- Click Create token, enter a label (e.g. “Okta production”), and create it.
- The token is shown only once in a reveal banner. Copy it immediately — it cannot be retrieved again. Only a short prefix is stored for identification.
- The page also shows the SCIM base path (
/api/v1/scim/v2) for reference.
You can create multiple tokens (e.g. one per IdP) and revoke any of them at any time from the same page; the list shows each token’s label, prefix, creation date, and last-used date. Revoking a token immediately stops provisioning for any IdP using it.
Tokens are stored as a SHA-256 hash. EmbayLMS never stores or displays the raw token after creation.
Step 2 — Supported operations and attributes
Users (/Users)
| Operation | Method | Behavior |
|---|---|---|
| List / filter | GET | Lists active users; supports startIndex / count paging and the filter userName eq "user@example.com" |
| Create | POST | Provisions a new user (or updates a match — see Matching) |
| Read | GET /Users/{id} | Returns one user |
| Replace | PUT | Replaces a user’s attributes |
| Update | PATCH | Partial update (e.g. set active) |
| Deactivate | DELETE / active=false | Deactivates the user (see Deprovisioning) |
User attribute mapping — SCIM attributes map to EmbayLMS user fields as follows:
| SCIM attribute | EmbayLMS field | Notes |
|---|---|---|
userName | Must be a valid email address; used as the primary identifier | |
name.givenName | First name | Defaults to Unknown if omitted |
name.familyName | Last name | Defaults to Unknown if omitted |
active | Account status | true = active, false = deactivated (default true) |
externalId | SCIM external ID | The IdP’s stable ID — used for matching on re-sync |
roles[0].value | Role | First role value; one of learner, instructor, manager, group_admin, ecommerce_admin, admin, owner. Any other value falls back to learner. The retired author is still accepted and is provisioned as learner plus the “Content Author” custom role — see Role mapping. |
Groups (/Groups)
| Operation | Method | Behavior |
|---|---|---|
| List | GET | Lists groups with members; supports startIndex / count paging |
| Create | POST | Provisions a group with its members |
| Replace | PUT | Replaces the group’s display name and full member list |
Group attribute mapping:
| SCIM attribute | EmbayLMS field | Notes |
|---|---|---|
displayName | Group name | Required |
externalId | SCIM external ID | Used for matching; auto-generated if the IdP omits it |
members[].value | Member user IDs | The EmbayLMS user id of each member |
Where provisioned groups show up, and linking them to LMS groups
Pushed groups are listed under Settings → User Management → SCIM → Provisioned groups: name, external ID, member count, the time of the last push, and the LMS group each one feeds (or Not linked). The list is read-only — the identity provider owns these rows.
A provisioned group does nothing in EmbayLMS until you link it to an LMS group:
- In Provisioned groups, click Link to LMS group on the row.
- Choose Create a new group named ”…” (the default), or Attach existing: … to feed a manual group you already use for course assignments or group admins.
- Click Link. Members are reconciled immediately and the confirmation says how many were added.
From then on, every push to /Groups reconciles the linked LMS group:
- users in the pushed member list are added with the source Provisioned by SCIM;
- SCIM-provisioned members no longer in the list are removed;
- members added by other means (manually, Google Workspace, Microsoft Entra) are never touched;
- a member ID that matches no EmbayLMS user yet is skipped until that user exists.
Linked groups carry a SCIM badge on the Groups page and on the group itself, and their Add member / Remove controls are disabled — membership is edited in the identity provider. Everything else about the group (course assignments, group admins, name, description) stays editable.
Unlink detaches the group: it keeps its members and becomes a normal manual group again. If the identity provider deletes the SCIM group, the LMS group likewise survives with its members.
| Rule | Behaviour |
|---|---|
| One LMS group per provisioned group | A provisioned group links to at most one LMS group, and a group is fed by at most one provisioned group |
| Only manual groups | A dynamic (rule-based) group cannot be linked — its members come from rules |
| Plan | Linking needs the SCIM capability (Enterprise), like creating a token |
Step 3 — Matching logic and deprovisioning
Matching (create vs. update). On a user POST, EmbayLMS first looks for an
existing user by externalId. If one is found, that user is updated in place
(email, name, role, active) rather than duplicated. If no externalId match
exists, it checks for an existing user with the same email — if that email is
already taken, the request returns 409 Conflict; otherwise a new user is
created. Groups follow the same pattern, matched by externalId.
Recommendation: always send a stable
externalIdfrom your IdP. It is the key that lets EmbayLMS recognize the same person across syncs (including after an email change), which prevents duplicate accounts.
Deprovisioning. EmbayLMS does not hard-delete users via SCIM. When your IdP
unassigns or deactivates a user, send active: false (via PATCH, PUT, or
DELETE). The account is deactivated: the user can no longer sign in, but
their completions, certificates, and transcript are retained for compliance.
Re-sending active: true reactivates the same account with its history intact.
Audit. Every SCIM create and update is written to the EmbayLMS audit log
(actor system, role scim) for SOC 2 evidence.
Step 4 — Worked example: configure Okta
-
In the Okta Admin Console, go to Applications → Browse App Catalog, add a SCIM 2.0 Test App (or your existing EmbayLMS SAML app if it supports provisioning), then open its Provisioning tab and click Configure API Integration.
-
Enter the SCIM connector settings:
Okta field Value SCIM connector base URL https://<your-tenant>.embaylms.com/api/v1/scim/v2Unique identifier field for users userNameAuthentication Mode HTTP Header Authorization Bearer <token from Step 1>Supported provisioning actions Push New Users, Push Profile Updates, Push Groups -
Click Test API Credentials — Okta calls
GET /Usersto verify the token. -
Under Provisioning → To App, enable Create Users, Update User Attributes, and Deactivate Users.
-
Map Okta profile attributes to SCIM:
userName→ email,givenName,familyName, and (optionally) arolesmapping to one oflearner,instructor,manager,group_admin,ecommerce_admin,admin,owner. -
Assign people/groups to the app. Okta provisions them into EmbayLMS; unassigning a user sends
active: false, which deactivates the account.
Entra ID (Azure AD): the flow is equivalent — add the Enterprise application → Provisioning, set Provisioning Mode to Automatic, paste the same base URL as the Tenant URL and the token as the Secret Token, then Test Connection and start provisioning.
Configuration reference
| Field | Where | Value |
|---|---|---|
| SCIM base URL | Your IdP’s SCIM app | https://<your-tenant>.embaylms.com/api/v1/scim/v2 |
| Bearer token | Settings → User Management → SCIM Provisioning → Create token | Shown once; copy immediately |
| Unique user identifier | Your IdP | userName (email) |
| Stable match key | Your IdP | externalId (strongly recommended) |
| Role attribute | Your IdP roles[0].value | learner | instructor | manager | group_admin | ecommerce_admin | admin | owner |
| Paging | Query string | startIndex (1-based), count (max 200) |
| User filter | Query string | userName eq "user@example.com" |
Role mapping
Your IdP sends the EmbayLMS role in roles[0].value. The accepted values are the
seven RBAC v2 personas:
| Value | Description |
|---|---|
learner | Consumes assigned and self-selected learning |
instructor | Facilitates sessions and grades, scoped to their own courses |
manager | Assigns and monitors training for their direct reports |
group_admin | Administers users and content within their assigned groups |
ecommerce_admin | Manages the storefront, pricing, and orders |
admin | Full tenant administration |
owner | Top of the tenant hierarchy |
Any unrecognised value provisions the user as learner. This is deliberate — an
IdP must never be able to provision a role EmbayLMS does not know about.
The retired author role
author was retired in RBAC v2 but is still accepted so existing IdP mappings
keep working. A user provisioned with author is stored with primary role learner
and granted the seeded “Content Author” custom role, which carries the same
permissions the old author role had. The user keeps the capability; the primary
role stops reporting a value that no longer exists.
Update your IdP mapping to instructor when convenient.
Troubleshooting
| Symptom | Cause / fix |
|---|---|
| Test credentials / connection fails with 401 | The bearer token is wrong, revoked, or the tenant is not active. Re-check the Authorization header is Bearer <token> and generate a fresh token if needed. |
| 401 even with a valid-looking token | The request must hit your tenant subdomain (<your-tenant>.embaylms.com) — the tenant is resolved from the host. A bare apex/platform URL will not authenticate. |
Create user (or a reactivation via active: true) returns 403 “Provisioning would exceed the plan limit of N active users” | Your plan’s active-user seat cap is full (Free = 5 — the same limit POST /api/v1/users reports as 422). Deactivate a user or upgrade in Billing; the IdP retries on its next sync cycle. Deactivations and attribute-only updates are never gated (LMS-896). |
| Create user returns 409 Conflict | A user with that userName (email) already exists but has a different/absent externalId. Set a matching externalId in your IdP so EmbayLMS updates the existing account instead of creating a new one. |
userName must be a valid email address (400) | userName must contain @. Map your IdP’s email attribute to userName. |
User provisioned as learner despite a different role | The roles[0].value was missing or not one of the roles in Role mapping; EmbayLMS falls back to learner rather than provisioning an unknown role. Fix the role mapping in your IdP. Note author also resolves to learner by design — see below. |
| Deactivated user still appears in reports | Expected — deactivation preserves the learning record. The user cannot log in. See Deprovisioning. |
| Duplicate accounts after an email change | The IdP did not send a stable externalId, so the new email created a new account. Configure externalId mapping and merge the duplicate via the transcript tools in User Management. |
| A pushed group does not appear on the Groups page | Expected until you link it — see Where provisioned groups show up. Pushed groups are listed under SCIM → Provisioned groups. |
| Add member is missing on a group, or removing a member fails with “managed by SCIM” | The group is fed by a provisioned group (SCIM badge). Change membership in the identity provider, or Unlink the group on the SCIM page to manage it by hand. |
| A linked group is missing some pushed members | Those member IDs match no EmbayLMS user yet (the IdP pushed the group before the users). They are added on the next push once the users exist; the link confirmation reports the skipped count. |