Stitchflow
Microsoft Dynamics 365 logo

Microsoft Dynamics 365 User Management API Guide

API workflow

How to automate user lifecycle operations through APIs with caveats that matter in production.

UpdatedMar 11, 2026

Summary and recommendation

The Dynamics 365 Dataverse Web API (v9.2) exposes the `systemusers` entity collection for user lifecycle management, authenticated via OAuth 2.0 against Microsoft Entra ID.

User provisioning is a two-phase operation: a Dynamics 365 license must first be assigned via Microsoft Graph (`POST /v1.0/users/{userId}/assignLicense`), after which the `systemuser` record is auto-created or can be explicitly POSTed to `/api/data/v9.2/systemusers`.

The API alone cannot grant a license - attempting to activate a `systemuser` record without a valid license assignment results in `isdisabled=true` and no login access. All requests require `OData-MaxVersion: 4.0` and `OData-Version: 4.0` headers; omitting them produces unpredictable errors.

The base URL is region-specific (`.crm.dynamics.com` for North America, `.crm4.dynamics.com` for EMEA, `.crm5.dynamics.com` for APAC) and should always be resolved from the environment's discovery endpoint rather than hardcoded.

API quick reference

Has user APIYes
Auth methodOAuth 2.0 (Azure AD / Microsoft Entra ID)
Base URLOfficial docs
SCIM availableYes
SCIM plan requiredRequires Microsoft Entra ID (Azure AD) P1 or P2 for automated provisioning; Dynamics 365 license required for target users. Entra ID P1 is included in Microsoft 365 E3/E5 and many Dynamics 365 bundles.

Authentication

Auth method: OAuth 2.0 (Azure AD / Microsoft Entra ID)

Setup steps

  1. Register an application in Microsoft Entra ID (Azure AD) at https://portal.azure.com/#view/Microsoft_AAD_RegisteredApps
  2. Grant the app API permissions: Dynamics CRM > user_impersonation (delegated) or application-level permissions
  3. Generate a client secret or configure certificate credentials for the app registration
  4. Obtain an access token via the OAuth 2.0 client credentials or authorization code flow from https://login.microsoftonline.com/{tenant_id}/oauth2/v2.0/token
  5. Pass the Bearer token in the Authorization header on all API requests
  6. Ensure the calling user or service principal has a Dynamics 365 security role assigned (e.g., System Administrator)

Required scopes

Scope Description Required for
https://org.crm.dynamics.com/user_impersonation Delegated scope allowing the app to access Dynamics 365 on behalf of the signed-in user All Dataverse Web API calls in delegated (user) context
https://graph.microsoft.com/User.Read.All Microsoft Graph scope to read Entra ID user profiles; used when syncing AAD users into Dynamics 365 Reading Azure AD user data prior to provisioning into Dynamics 365
https://graph.microsoft.com/Directory.ReadWrite.All Microsoft Graph scope for managing directory objects; required for license assignment via Graph Assigning Dynamics 365 licenses to users via Microsoft Graph

User object / data model

Field Type Description On create On update Notes
systemuserid Guid Unique identifier for the system user (primary key) auto-generated immutable Used as the record ID in all API URLs
domainname String User's UPN / login name (e.g., user@contoso.com) required optional Must match the Azure AD UPN for SSO to function
firstname String User's first name required optional
lastname String User's last name required optional
fullname String Computed full name (read-only) read-only read-only Derived from firstname + lastname
internalemailaddress String Primary email address of the user required optional
title String Job title of the user optional optional
mobilephone String Mobile phone number optional optional
businessunitid Lookup (businessunit) Business unit the user belongs to required optional Defaults to root business unit if not specified
isdisabled Boolean Whether the user account is disabled in Dynamics 365 optional (default false) optional Set to true to deactivate; does not delete the record
accessmode OptionSet (Integer) License/access type: 0=Read-Write, 1=Administrative, 2=Read, 3=Support User, 4=Non-interactive, 5=Delegated Admin optional optional Non-interactive users (4) do not consume a full license
caltype OptionSet (Integer) Client Access License type: 0=Professional, 1=Administrative, 2=Basic, 3=Device Professional, 4=Device Basic, 11=Team Member optional optional Determines which Dynamics 365 features the user can access
azureactivedirectoryobjectid Guid Azure AD Object ID of the user; links Dynamics record to Entra ID identity auto-populated on sync read-only after set Required for SSO and license assignment
territoryid Lookup (territory) Sales territory assigned to the user optional optional
defaultmailbox Lookup (mailbox) Default mailbox record associated with the user auto-created optional
userlicensetype Integer Numeric license type indicator optional optional Deprecated in favor of caltype in newer API versions
positionid Lookup (position) Organizational position/hierarchy node for the user optional optional
parentsystemuserid Lookup (systemuser) Manager of this user (for org hierarchy) optional optional
systemuserroles_association Many-to-Many (role) Security roles assigned to the user via $ref association after create via $ref association/disassociation Roles are assigned via a separate POST to the association endpoint, not inline on the user record

Core endpoints

List users

  • Method: GET
  • URL: https://{org}.api.crm.dynamics.com/api/data/v9.2/systemusers?$select=systemuserid,domainname,fullname,isdisabled&$top=50
  • Watch out for: Deleted/disabled users are still returned unless filtered with $filter=isdisabled eq false. Use $select to avoid fetching all 200+ fields.

Request example

GET /api/data/v9.2/systemusers?$select=systemuserid,domainname,fullname,isdisabled&$top=50
Authorization: Bearer {token}
OData-MaxVersion: 4.0
OData-Version: 4.0

Response example

{
  "value": [
    {
      "systemuserid": "a1b2c3d4-...",
      "domainname": "user@contoso.com",
      "fullname": "Jane Doe",
      "isdisabled": false
    }
  ],
  "@odata.nextLink": "https://{org}.api.crm.dynamics.com/api/data/v9.2/systemusers?$skiptoken=..."
}

Get single user

  • Method: GET
  • URL: https://{org}.api.crm.dynamics.com/api/data/v9.2/systemusers({systemuserid})
  • Watch out for: You cannot look up a user by domainname directly in the URL; use $filter=domainname eq 'user@contoso.com' on the collection endpoint instead.

Request example

GET /api/data/v9.2/systemusers(a1b2c3d4-0000-0000-0000-000000000001)
Authorization: Bearer {token}

Response example

{
  "systemuserid": "a1b2c3d4-...",
  "domainname": "user@contoso.com",
  "firstname": "Jane",
  "lastname": "Doe",
  "internalemailaddress": "user@contoso.com",
  "isdisabled": false,
  "accessmode": 0
}

Create user

  • Method: POST
  • URL: https://{org}.api.crm.dynamics.com/api/data/v9.2/systemusers
  • Watch out for: The user must already exist in Azure AD / Entra ID with a valid Dynamics 365 license assigned before the record can be activated. Creating the Dataverse record alone does not provision a license.

Request example

POST /api/data/v9.2/systemusers
Authorization: Bearer {token}
Content-Type: application/json

{
  "domainname": "newuser@contoso.com",
  "firstname": "John",
  "lastname": "Smith",
  "internalemailaddress": "newuser@contoso.com",
  "businessunitid@odata.bind": "/businessunits({buId})"
}

Response example

HTTP 204 No Content
OData-EntityId: https://{org}.api.crm.dynamics.com/api/data/v9.2/systemusers(new-guid)

Update user

  • Method: PATCH
  • URL: https://{org}.api.crm.dynamics.com/api/data/v9.2/systemusers({systemuserid})
  • Watch out for: PATCH is a partial update (only supplied fields are changed). Do not use PUT; it is not supported for systemuser.

Request example

PATCH /api/data/v9.2/systemusers(a1b2c3d4-...)
Authorization: Bearer {token}
Content-Type: application/json

{
  "title": "Senior Manager",
  "mobilephone": "+1-555-0100"
}

Response example

HTTP 204 No Content

Disable (deactivate) user

  • Method: PATCH
  • URL: https://{org}.api.crm.dynamics.com/api/data/v9.2/systemusers({systemuserid})
  • Watch out for: Dynamics 365 does not support hard-deleting system users via the API. Deactivation (isdisabled=true) is the only supported removal path. The user record is retained for audit/ownership history.

Request example

PATCH /api/data/v9.2/systemusers(a1b2c3d4-...)
Authorization: Bearer {token}
Content-Type: application/json

{"isdisabled": true}

Response example

HTTP 204 No Content

Assign security role to user

  • Method: POST
  • URL: https://{org}.api.crm.dynamics.com/api/data/v9.2/systemusers({systemuserid})/systemuserroles_association/$ref
  • Watch out for: Role IDs are environment-specific GUIDs. The same role name will have a different GUID in each Dynamics 365 environment (dev/test/prod). Always query /roles to resolve IDs dynamically.

Request example

POST /api/data/v9.2/systemusers(a1b2c3d4-...)/systemuserroles_association/$ref
Authorization: Bearer {token}
Content-Type: application/json

{
  "@odata.id": "https://{org}.api.crm.dynamics.com/api/data/v9.2/roles({roleId})"
}

Response example

HTTP 204 No Content

Remove security role from user

  • Method: DELETE
  • URL: https://{org}.api.crm.dynamics.com/api/data/v9.2/systemusers({systemuserid})/systemuserroles_association({roleId})/$ref
  • Watch out for: Removing all roles from a user does not disable them; the user retains access until isdisabled is set to true or the Entra ID license is revoked.

Request example

DELETE /api/data/v9.2/systemusers(a1b2c3d4-...)/systemuserroles_association(role-guid)/$ref
Authorization: Bearer {token}

Response example

HTTP 204 No Content

List security roles for user

  • Method: GET
  • URL: https://{org}.api.crm.dynamics.com/api/data/v9.2/systemusers({systemuserid})/systemuserroles_association?$select=roleid,name
  • Watch out for: This returns roles directly assigned to the user. Roles inherited via team membership are not included in this response.

Request example

GET /api/data/v9.2/systemusers(a1b2c3d4-...)/systemuserroles_association?$select=roleid,name
Authorization: Bearer {token}

Response example

{
  "value": [
    {
      "roleid": "role-guid-1",
      "name": "Salesperson"
    },
    {
      "roleid": "role-guid-2",
      "name": "System Customizer"
    }
  ]
}

Rate limits, pagination, and events

  • Rate limits: Dynamics 365 / Dataverse enforces service protection API limits per user per 5-minute sliding window. Limits apply per environment and are not plan-tiered in the traditional sense.
  • Rate-limit headers: Yes
  • Retry-After header: Yes
  • Rate-limit notes: When limits are exceeded, HTTP 429 is returned with a Retry-After header. Headers x-ms-ratelimit-burst-remaining-xrm-requests and x-ms-ratelimit-time-remaining-xrm-requests indicate remaining quota. Limits are per authenticated user, not per app registration. Service principal (S2S) calls have separate higher limits.
  • Pagination method: token
  • Default page size: 5000
  • Max page size: 5000
  • Pagination pointer: $top (max 5000); continuation via @odata.nextLink token in response
Plan Limit Concurrent
All Dynamics 365 / Dataverse environments 6,000 API requests per user per 5-minute window; 52,000 requests per user per 24-hour period; max execution time 20 minutes per 5-minute window 52
  • Webhooks available: Yes
  • Webhook notes: Dynamics 365 / Dataverse supports event-driven notifications via Webhooks (registered as Service Endpoints) and Azure Service Bus/Event Hub integration. Webhooks can be triggered on Create, Update, Delete, and custom message events on any entity including systemuser.
  • Alternative event strategy: Azure Service Bus or Azure Event Hub can be used as an alternative to HTTP webhooks for more reliable async processing. Plugin steps registered in the Plug-in Registration Tool can also trigger custom logic on user events.
  • Webhook events: Create (systemuser), Update (systemuser), Delete (systemuser), Associate (systemuserroles_association), Disassociate (systemuserroles_association), SetState (systemuser enable/disable)

SCIM API status

  • SCIM available: Yes

  • SCIM version: 2.0

  • Plan required: Requires Microsoft Entra ID (Azure AD) P1 or P2 for automated provisioning; Dynamics 365 license required for target users. Entra ID P1 is included in Microsoft 365 E3/E5 and many Dynamics 365 bundles.

  • Endpoint: SCIM provisioning is handled by Microsoft Entra ID's provisioning service targeting Dynamics 365 as a connected app. The SCIM endpoint is managed by Entra, not exposed as a standalone Dynamics 365 URL. Entra provisioning job URL pattern: https://graph.microsoft.com/v1.0/servicePrincipals/{spId}/synchronization/jobs

  • Supported operations: Create user (provision Entra ID user into Dynamics 365), Update user attributes (sync profile changes), Deactivate user (set isdisabled=true on systemuser), Group-based provisioning (assign/remove Dynamics 365 app roles via Entra groups)

Limitations:

  • SCIM provisioning for Dynamics 365 is implemented via the Entra ID Application Provisioning service (gallery app), not a direct SCIM endpoint on the Dynamics 365 tenant
  • Provisioning does not assign Dynamics 365 security roles; role assignment must be done separately via the Dataverse Web API or manually
  • Dynamics 365 Business Central uses a separate provisioning mechanism and is not covered by the same Entra gallery app
  • Hard delete of users is not supported; deprovisioning sets isdisabled=true only
  • Attribute mapping is configurable but limited to fields exposed by the Dataverse connector in Entra provisioning
  • SSO (Entra ID / SAML or OIDC) must be configured as a prerequisite for SCIM provisioning to function correctly

Common scenarios

Three scenarios cover the majority of programmatic identity lifecycle work against Dynamics 365.

First, provisioning: assign the license via Graph, wait for Entra sync, then POST the systemuser record if not auto-created, resolve the systemuserid by filtering on domainname, and POST each security role to /systemusers({id})/systemuserroles_association/$ref - role GUIDs are environment-specific and must be resolved dynamically via `GET /roles?

$filter=name eq 'RoleName'for every target environment. Second, deprovisioning: PATCHisdisabled=trueon thesystemuserrecord, invoke theReassignObjectsSystemUseraction for owned records, then separately revoke the license via Graph - hard DELETE on/systemusers` returns an error.

deactivation is the only supported offboarding path. Third, SCIM automation via Entra ID: configure the Dynamics 365 gallery app in Entra Enterprise Applications with Provisioning Mode set to Automatic.

note that Entra SCIM handles create and deactivate but does not assign Dynamics 365 security roles - a separate Power Automate flow or Azure Function triggered on user creation is required to complete role assignment.

This three-scenario pattern maps directly to the identity graph maintained by an orchestration layer: Entra ID as the authoritative identity source, Graph for license state, and the Dataverse Web API for environment-level access and role membership.

Provision a new Dynamics 365 user from Entra ID

  1. Assign a Dynamics 365 license to the user in Microsoft 365 Admin Center or via Microsoft Graph: POST https://graph.microsoft.com/v1.0/users/{userId}/assignLicense with the Dynamics 365 SKU ID.
  2. Wait for Entra ID to sync the license (typically within minutes); the systemuser record is auto-created in Dynamics 365 with isdisabled=false.
  3. If the record is not auto-created, POST to /api/data/v9.2/systemusers with domainname, firstname, lastname, internalemailaddress, and businessunitid@odata.bind.
  4. Retrieve the new systemuserid: GET /api/data/v9.2/systemusers?$filter=domainname eq 'newuser@contoso.com'&$select=systemuserid.
  5. Assign required security roles: POST /api/data/v9.2/systemusers({systemuserid})/systemuserroles_association/$ref with the role @odata.id for each role.
  6. Optionally set businessunitid, title, and parentsystemuserid via PATCH /api/data/v9.2/systemusers({systemuserid}).

Watch out for: License assignment must precede or accompany Dataverse record creation. A systemuser record without a valid license will have isdisabled=true and cannot log in.

Deprovision (offboard) a Dynamics 365 user

  1. Resolve the systemuserid: GET /api/data/v9.2/systemusers?$filter=domainname eq 'leavinguser@contoso.com'&$select=systemuserid,isdisabled.
  2. Disable the user in Dynamics 365: PATCH /api/data/v9.2/systemusers({systemuserid}) with body {"isdisabled": true}.
  3. Reassign owned records if required using the ReassignObjectsSystemUser action or manually via bulk update.
  4. Revoke the Dynamics 365 license in Microsoft 365 Admin Center or via Microsoft Graph DELETE /users/{userId}/assignLicense.
  5. Optionally disable the Entra ID account: PATCH https://graph.microsoft.com/v1.0/users/{userId} with {"accountEnabled": false}.

Watch out for: Disabling in Dynamics 365 (step 2) and revoking the license (step 4) are independent operations. Doing only one may leave the user partially accessible. Records owned by the disabled user remain assigned to them until explicitly reassigned.

Automate SCIM provisioning via Microsoft Entra ID

  1. In the Azure Portal, navigate to Entra ID > Enterprise Applications and add the 'Dynamics 365' gallery application.
  2. Configure Single Sign-On (SAML or OIDC) for the application as a prerequisite.
  3. Navigate to Provisioning > set Provisioning Mode to 'Automatic'.
  4. Enter the Dynamics 365 tenant URL and authenticate with a System Administrator account to authorize Entra provisioning.
  5. Configure attribute mappings (Entra ID user attributes → Dataverse systemuser fields) under Mappings.
  6. Define scope: assign users/groups to the Enterprise Application to control who gets provisioned.
  7. Enable provisioning and monitor the provisioning logs in Entra ID for errors.
  8. Security role assignment post-provisioning must be handled separately via the Dataverse Web API or Power Automate flow triggered on user creation.

Watch out for: Entra SCIM provisioning creates and deactivates systemuser records but does not assign Dynamics 365 security roles. A separate automation (Power Automate, Azure Function, or manual step) is required to assign roles after provisioning.

Why building this yourself is a trap

The most common API integration failure is treating Dynamics 365 as a single-surface system. In practice, the identity graph spans three layers - Entra ID (identity and license), Dataverse Web API (environment access and RBAC), and Business Central's own provisioning surface - and writes to one layer do not propagate to the others.

Service Protection Limits enforce 6,000 requests per user per 5-minute window with HTTP 429 and a Retry-After header; S2S (service principal) calls have higher limits but are still per-environment capped, so bulk provisioning jobs must implement exponential backoff.

Role inheritance via team membership is invisible to GET /systemusers({id})/systemuserroles_association, which returns only directly assigned roles - integrations that audit effective permissions without querying team memberships will produce incomplete results. Filtering on the computed fullname field is unreliable; filter on firstname and lastname separately.

Finally, pagination uses @odata.nextLink tokens with a hard cap of 5,000 records per page; integrations that assume a single response contains all users will silently miss records in large tenants.

Automate Microsoft Dynamics 365 workflows without one-off scripts

Stitchflow builds and maintains end-to-end IT automation across your SaaS stack, including apps without APIs. Built for exactly how your company works, with human approvals where they matter.

Every app coverage, including apps without APIs
60+ app integrations plus browser automation for apps without APIs
IT graph reconciliation across apps and your IdP
Less than a week to launch, maintained as APIs and admin consoles change
SOC 2 Type II. ~2 hours of your team's time

UpdatedMar 11, 2026

* Details sourced from official product documentation and admin references.

Keep exploring

Related apps

15Five logo

15Five

Full API + SCIM
AutomationAPI + SCIM
Last updatedFeb 2026

15Five uses a fixed role-based permission model with six predefined roles: Account Admin, HR Admin, Billing Admin, Group Admin, Manager, and Employee. No custom roles can be constructed. User management lives at Settings gear → People → Manage people p

1Password logo

1Password

Full API + SCIM
AutomationAPI + SCIM
Last updatedFeb 2026

1Password's admin console at my.1password.com covers the full user lifecycle — invitations, group assignments, vault access, suspension, and deletion — without any third-party tooling. Like every app that mixes role-based and resource-level permissions

8x8 logo

8x8

Full API + SCIM
AutomationAPI + SCIM
Last updatedFeb 2026

8x8 Admin Console supports full lifecycle user management — create, deactivate, and delete — across its X Series unified communications platform. Every app a user can access (8x8 Work desktop, mobile, web, Agent Workspace) is gated by license assignmen