Summary and recommendation
Microsoft Entra ID exposes user lifecycle operations through the Microsoft Graph API (base URL: `https://graph.microsoft.com/v1.0`). Authentication uses OAuth 2.0 client credentials flow - register an app in Entra, create a client secret, add the required Graph application permissions (`User.ReadWrite.All` or `Directory.ReadWrite.All`), grant admin consent, and exchange credentials for a Bearer token at `https://login.microsoftonline.com/{tenant_id}/oauth2/v2.0/token`.
For teams managing Entra ID programmatically at scale, Stitchflow's MCP server with ~100 deep IT/identity integrations connects Entra ID alongside the rest of the SaaS stack, enabling cross-system lifecycle automation without building and maintaining individual Graph API integrations per app. The MCP server handles auth, pagination, delta sync, and offboarding orchestration across connected systems.
Key Graph endpoints for user management: `GET /users` (list, max `$top=999`), `POST /users` (create), `PATCH /users/{id}` (update or disable), `DELETE /users/{id}` (soft delete, 30-day recycle bin), and `GET /users/delta` (incremental sync).
Throttling is enforced at 10,000 requests per 10 minutes per app per tenant, with a global ceiling of 130,000 requests per 10 minutes per tenant across all apps.
API quick reference
| Has user API | Yes |
| Auth method | OAuth 2.0 (Microsoft identity platform / Azure AD) |
| Base URL | Official docs |
| SCIM available | Yes |
| SCIM plan required | Microsoft Entra ID Premium P1 or P2 (also included in Microsoft 365 E3/E5). Entra ID acts as the SCIM provisioning SOURCE (IdP) that pushes users to SCIM-enabled target applications. |
Authentication
Auth method: OAuth 2.0 (Microsoft identity platform / Azure AD)
Setup steps
- Register an application in the Microsoft Entra admin center (portal.azure.com or entra.microsoft.com) under App registrations.
- Note the Application (client) ID and Directory (tenant) ID.
- Under Certificates & secrets, create a client secret (or upload a certificate for production).
- Under API permissions, add Microsoft Graph application permissions (e.g., User.Read.All, User.ReadWrite.All, Directory.ReadWrite.All) and grant admin consent.
- Obtain an access token via the client credentials flow: POST https://login.microsoftonline.com/{tenant_id}/oauth2/v2.0/token with client_id, client_secret, scope=https://graph.microsoft.com/.default, grant_type=client_credentials.
- Use the returned access_token as a Bearer token in the Authorization header for all Microsoft Graph requests.
Required scopes
| Scope | Description | Required for |
|---|---|---|
| User.Read.All | Read all users' full profiles | List users, get user by ID |
| User.ReadWrite.All | Read and write all users' full profiles | Create, update, delete users |
| Directory.ReadWrite.All | Read and write directory data including users and groups | Full directory management including group membership |
| Directory.Read.All | Read directory data | Read-only directory operations |
| User.ManageIdentities.All | Manage all users' identities | Managing external identities on user accounts |
User object / data model
| Field | Type | Description | On create | On update | Notes |
|---|---|---|---|---|---|
| id | string (GUID) | Unique identifier for the user object in Entra ID | auto-generated | immutable | Use this as the stable identifier; UPN can change |
| userPrincipalName | string | UPN (login name), typically user@domain.com | required | optional | Must be a verified domain in the tenant |
| displayName | string | Display name shown in directory | required | optional | |
| mailNickname | string | Mail alias (portion before @) used for email | required | optional | Required when creating a user |
| accountEnabled | boolean | Whether the user account is enabled for sign-in | required | optional | Set to false to disable without deleting |
| passwordProfile | object | Password and force-change-on-next-sign-in settings | required | optional | Contains password (string) and forceChangePasswordNextSignIn (boolean) |
| givenName | string | First name | optional | optional | |
| surname | string | Last name | optional | optional | |
| string | Primary SMTP email address | optional | optional | Read-only for synced users from on-premises AD | |
| jobTitle | string | User's job title | optional | optional | |
| department | string | Department name | optional | optional | |
| mobilePhone | string | Primary mobile phone number | optional | optional | |
| officeLocation | string | Office location in the user's place of business | optional | optional | |
| usageLocation | string (ISO 3166-1 alpha-2) | Two-letter country code for license assignment | optional | optional | Required before assigning Microsoft 365 licenses |
| assignedLicenses | array of assignedLicense | Licenses assigned to the user | not set here | via assignLicense action | Use POST /users/{id}/assignLicense to modify |
| userType | string | Member or Guest | optional | optional | Defaults to Member for new users |
| externalUserState | string | State of guest invitation (PendingAcceptance, Accepted) | auto-set for guests | read-only | |
| onPremisesSyncEnabled | boolean | True if synced from on-premises AD via Entra Connect | auto-set | read-only | Many fields are read-only for synced users |
| manager | directoryObject (relationship) | User's manager | not in body | via PUT /users/{id}/manager/$ref | Separate endpoint required to set/update manager |
| proxyAddresses | array of string | Email addresses associated with the user | optional | optional | Read-only for on-premises synced users |
Core endpoints
List users
- Method: GET
- URL:
https://graph.microsoft.com/v1.0/users - Watch out for: Without $select, Graph returns a default subset of fields. Use $select to control returned fields and reduce payload size. Max $top is 999.
Request example
GET /v1.0/users?$top=100&$select=id,displayName,userPrincipalName,accountEnabled
Authorization: Bearer {token}
Response example
{
"value": [
{"id":"abc-123","displayName":"Jane Doe","userPrincipalName":"jane@contoso.com","accountEnabled":true}
],
"@odata.nextLink":"https://graph.microsoft.com/v1.0/users?$skiptoken=..."
}
Get user by ID or UPN
- Method: GET
- URL:
https://graph.microsoft.com/v1.0/users/{id|userPrincipalName} - Watch out for: Using UPN as the identifier can fail if the UPN contains special characters (e.g., #). Prefer the immutable object ID.
Request example
GET /v1.0/users/abc-123
Authorization: Bearer {token}
Response example
{
"id": "abc-123",
"displayName": "Jane Doe",
"userPrincipalName": "jane@contoso.com",
"accountEnabled": true,
"jobTitle": "Engineer"
}
Create user
- Method: POST
- URL:
https://graph.microsoft.com/v1.0/users - Watch out for: displayName, userPrincipalName, mailNickname, accountEnabled, and passwordProfile are all required. The UPN domain must be a verified domain in the tenant.
Request example
POST /v1.0/users
Content-Type: application/json
{
"displayName":"John Smith",
"userPrincipalName":"john@contoso.com",
"mailNickname":"john",
"accountEnabled":true,
"passwordProfile":{"password":"Temp!Pass1","forceChangePasswordNextSignIn":true}
}
Response example
{
"id": "def-456",
"displayName": "John Smith",
"userPrincipalName": "john@contoso.com",
"accountEnabled": true
}
Update user
- Method: PATCH
- URL:
https://graph.microsoft.com/v1.0/users/{id} - Watch out for: PATCH returns 204 with no body on success. Many properties are read-only for on-premises synced users (onPremisesSyncEnabled=true); those must be updated in on-premises AD.
Request example
PATCH /v1.0/users/def-456
Content-Type: application/json
{
"jobTitle": "Senior Engineer",
"department": "Platform"
}
Response example
HTTP 204 No Content
Delete user
- Method: DELETE
- URL:
https://graph.microsoft.com/v1.0/users/{id} - Watch out for: Deleted users are soft-deleted and moved to the deleted items container for 30 days before permanent deletion. They can be restored within that window via POST /directory/deletedItems/{id}/restore.
Request example
DELETE /v1.0/users/def-456
Authorization: Bearer {token}
Response example
HTTP 204 No Content
Disable user (block sign-in)
- Method: PATCH
- URL:
https://graph.microsoft.com/v1.0/users/{id} - Watch out for: Preferred over deletion for temporary deactivation. Existing sessions may persist until token expiry (up to 1 hour for access tokens).
Request example
PATCH /v1.0/users/def-456
Content-Type: application/json
{
"accountEnabled": false
}
Response example
HTTP 204 No Content
List group members
- Method: GET
- URL:
https://graph.microsoft.com/v1.0/groups/{group-id}/members - Watch out for: Members can be users, groups, service principals, or devices. Filter by @odata.type if only users are needed.
Request example
GET /v1.0/groups/{group-id}/members?$select=id,displayName,userPrincipalName
Authorization: Bearer {token}
Response example
{
"value": [
{"@odata.type":"#microsoft.graph.user","id":"abc-123","displayName":"Jane Doe"}
]
}
Delta query (incremental user sync)
- Method: GET
- URL:
https://graph.microsoft.com/v1.0/users/delta - Watch out for: Store the deltaLink token and use it on subsequent calls to get only changed users. Deleted users appear with @removed annotation. Initial call returns all users; subsequent calls return only changes.
Request example
GET /v1.0/users/delta?$select=displayName,userPrincipalName,accountEnabled
Authorization: Bearer {token}
Response example
{
"value": [{"id":"abc-123","displayName":"Jane Doe"}],
"@odata.deltaLink":"https://graph.microsoft.com/v1.0/users/delta?$deltatoken=..."
}
Rate limits, pagination, and events
- Rate limits: Microsoft Graph applies per-app, per-tenant throttling. For identity and access (users/groups), the limit is 10,000 requests per 10 minutes per tenant per app. A global limit of 130,000 requests per 10 minutes per tenant across all apps also applies.
- Rate-limit headers: Yes
- Retry-After header: Yes
- Rate-limit notes: When throttled, Graph returns HTTP 429 with a Retry-After header indicating seconds to wait. Headers x-ms-throttle-limit-percentage and x-ms-throttle-scope may be returned. Batch requests (up to 20 per batch) count individually against limits. Delta queries are recommended for large-scale sync to reduce request volume.
- Pagination method: token
- Default page size: 100
- Max page size: 999
- Pagination pointer: $top (page size), @odata.nextLink (continuation token in response)
| Plan | Limit | Concurrent |
|---|---|---|
| All plans (per app per tenant) | 10,000 requests per 10 minutes | 0 |
| All plans (global per tenant) | 130,000 requests per 10 minutes | 0 |
- Webhooks available: Yes
- Webhook notes: Microsoft Graph supports change notifications (webhooks) for user and group objects. Subscribe via POST /subscriptions to receive HTTP POST notifications to a specified endpoint when user objects change.
- Alternative event strategy: For polling-based sync, use the Microsoft Graph delta query endpoint (/users/delta) to retrieve incremental changes without webhooks. For audit events, use the Entra ID audit logs API (GET /auditLogs/directoryAudits).
- Webhook events: created (user), updated (user), deleted (user), created (group), updated (group), deleted (group), group member added, group member removed
SCIM API status
SCIM available: Yes
SCIM version: 2.0
Plan required: Microsoft Entra ID Premium P1 or P2 (also included in Microsoft 365 E3/E5). Entra ID acts as the SCIM provisioning SOURCE (IdP) that pushes users to SCIM-enabled target applications.
Endpoint: Varies per target application; configured in Entra admin center under Enterprise Applications > Provisioning. Entra ID sends SCIM requests to the target app's SCIM endpoint.
Supported operations: Create user (POST /Users), Read user (GET /Users/{id}), Update user (PATCH /Users/{id}), Deactivate user (PATCH /Users/{id} with active=false), Delete user (DELETE /Users/{id}), Create group (POST /Groups), Update group membership (PATCH /Groups/{id}), Delete group (DELETE /Groups/{id}), Filter users (GET /Users?filter=)
Limitations:
- Entra ID is the SCIM client (provisioner), not a SCIM server for inbound provisioning to Entra itself (inbound SCIM to Entra is available but in a different configuration path).
- Provisioning cycles run approximately every 40 minutes; near-real-time provisioning requires on-demand provisioning or API-triggered provisioning.
- Attribute mapping is configurable but limited to the schema supported by the target app.
- Requires Premium P1 or P2 license; not available on free or basic Entra ID tiers.
- SCIM provisioning to gallery apps is generally well-supported; non-gallery apps require manual SCIM endpoint configuration.
- Inbound SCIM provisioning (external system pushing users into Entra ID) is supported via the Entra ID inbound provisioning API but requires additional configuration.
Common scenarios
Provision a new employee: POST /v1. 0/users with displayName, userPrincipalName, mailNickname, accountEnabled=true, passwordProfile, and usageLocation (required before license assignment).
Capture the returned id, then call POST /v1. 0/users/{id}/assignLicense with the target skuId.
Retrieve available SKUs via GET /v1. 0/subscribedSkus.
Missing usageLocation returns a 400 on license assignment - set it at user creation, not after.
Incremental sync: Call GET /v1.0/users/delta?$select=id,displayName,userPrincipalName,accountEnabled,department to retrieve all users and a deltaLink token. On each subsequent cycle, call the stored deltaLink URL to retrieve only changed users. Deleted users appear with an @removed annotation. Delta tokens can expire if left unused for too long - restart with a full delta query if the token is stale.
Offboard a departing employee: PATCH /v1.0/users/{id} with accountEnabled=false to block sign-in, then POST /v1.0/users/{id}/revokeSignInSessions to invalidate refresh tokens. Remove group memberships via DELETE /v1.0/groups/{group-id}/members/{user-id}/$ref. Reclaim licenses via POST /v1.0/users/{id}/assignLicense with removeLicenses. Soft-delete via DELETE /v1.0/users/{id}; permanently delete via DELETE /v1.0/directory/deletedItems/{id} if immediate removal is required. Note: existing access tokens remain valid up to 1 hour post-revocation unless Continuous Access Evaluation (CAE) is enabled on the target resource.
Provision a new employee and assign a license
- POST /v1.0/users with displayName, userPrincipalName, mailNickname, accountEnabled=true, passwordProfile, usageLocation (e.g., 'US').
- Capture the returned user id.
- POST /v1.0/users/{id}/assignLicense with addLicenses array containing the skuId for the desired Microsoft 365 plan and removeLicenses as empty array.
- Optionally PUT /v1.0/users/{id}/manager/$ref to set the user's manager.
- Optionally POST /v1.0/groups/{group-id}/members/$ref to add the user to relevant security or Microsoft 365 groups.
Watch out for: usageLocation must be set before assignLicense or the license assignment will return a 400 error. Retrieve available SKUs via GET /v1.0/subscribedSkus to find the correct skuId.
Incremental user sync to an external system
- Make an initial GET /v1.0/users/delta?$select=id,displayName,userPrincipalName,accountEnabled,department to retrieve all users and receive a deltaLink token.
- Store the deltaLink token securely.
- On each subsequent sync cycle, call the stored deltaLink URL to retrieve only users added, updated, or deleted since the last call.
- Process created/updated users (present in value array) and deleted users (present with @removed annotation).
- Store the new deltaLink from the response for the next cycle.
Watch out for: Delta tokens can expire if not used for an extended period. If the token expires, restart with a full delta query. Do not rely on deltaLink tokens for long-term storage without periodic refresh.
Offboard a departing employee
- PATCH /v1.0/users/{id} with accountEnabled=false to immediately block sign-in.
- Revoke all refresh tokens via POST /v1.0/users/{id}/revokeSignInSessions to invalidate active sessions (access tokens still valid up to 1 hour).
- Remove the user from groups via DELETE /v1.0/groups/{group-id}/members/{user-id}/$ref for each group.
- POST /v1.0/users/{id}/assignLicense with removeLicenses containing the skuIds to reclaim licenses.
- DELETE /v1.0/users/{id} to soft-delete the user (recoverable for 30 days).
- After 30 days or when confirmed, permanently delete via DELETE /v1.0/directory/deletedItems/{id} if immediate permanent deletion is required.
Watch out for: revokeSignInSessions invalidates refresh tokens but existing access tokens remain valid until expiry (up to 1 hour). For immediate access revocation, Continuous Access Evaluation (CAE) must be enabled and the target resource must support it.
Why building this yourself is a trap
On-premises AD-synced users (onPremisesSyncEnabled=true) have most Graph-writable attributes set to read-only; PATCH calls on those fields return success but are silently overwritten on the next sync cycle. All attribute changes for synced users must originate in on-premises AD.
This affects a significant portion of enterprise tenants and is not surfaced as an error at the API level.
Application permissions used in the client credentials flow require explicit admin consent per tenant before any API call succeeds. Consent granted in one tenant does not transfer. Additionally, $filter support across user properties is inconsistent - not all fields support all filter operators, and unsupported combinations return 400 errors rather than empty result sets.
Batch requests support up to 20 sub-requests per call, but each sub-request counts individually against throttling limits. At scale, batching reduces HTTP overhead but does not increase effective throughput. For large-scale sync workloads, delta queries are the recommended pattern to minimize request volume against the 10,000 requests/10-minute per-app ceiling.
Automate Microsoft Azure / Entra ID 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.