Inbound API-Driven Provisioning
API-driven inbound provisioning lets external systems push identity data into Microsoft Entra ID (or on-premises Active Directory) through a Graph API endpoint. Unlike outbound provisioning where Entra pulls or pushes to target applications, inbound API provisioning reverses the direction: an external system of record sends data to Entra ID.
This is the integration path when the identity source is not Workday or SAP SuccessFactors (which have purpose-built gallery integrations), but rather a custom HR system, payroll platform, student information system, or any data source that can make HTTP calls.
Architecture and Data Flow
The inbound API provisioning architecture has four layers:
- System of record. The authoritative source of identity data (HR system, payroll, SIS, CSV export, SQL database, LDAP directory, or any custom system).
- API client. An automation component that reads data from the system of record and sends it to the Entra provisioning service. This can be a PowerShell script, Azure Logic App, Azure Function, or any HTTP client.
- Entra provisioning service. The cloud engine that processes incoming data, applies attribute mappings, scoping filters, and matching rules, then provisions users to the configured target.
- Target directory. Either Microsoft Entra ID (cloud-only users) or on-premises Active Directory (hybrid users via the provisioning agent).
The API client does not need to determine whether to create, update, or disable users. It uploads the current state from the source system; the provisioning engine handles comparison with existing user records and determines the appropriate action (create, update, disable, or no-op).
sequenceDiagram
participant SoR as System of Record
participant Client as API Client
participant Graph as Microsoft Graph API
participant Prov as Provisioning Service
participant Target as Entra ID / On-Prem AD
Client->>SoR: Read identity data
SoR-->>Client: User records
Client->>Graph: POST /bulkUpload (SCIM payload)
Graph-->>Client: 202 Accepted
Graph->>Prov: Queue bulk request
Prov->>Prov: Apply scoping, mapping, matching
Prov->>Target: Create / Update / Disable users
Client->>Graph: GET /auditLogs/provisioning
Graph-->>Client: Provisioning log entries
The following diagram shows where inbound API provisioning fits within the broader Entra provisioning platform:
architecture-beta
group platform(cloud)[Entra Provisioning Platform]
service inbound(server)[Inbound API] in platform
service outbound(server)[Outbound SCIM] in platform
service engine(server)[Provisioning Engine] in platform
group sources(cloud)[Identity Sources]
service hr(server)[HR Systems] in sources
service custom(server)[Custom Sources] in sources
group targets(cloud)[Targets]
service entra(server)[Entra ID] in targets
service ad(server)[On-Prem AD] in targets
service saas(server)[SaaS Apps] in targets
hr:R --> L:inbound
custom:R --> L:inbound
inbound:R --> L:engine
engine:R --> L:entra
engine:R --> L:ad
outbound:R --> L:saas
engine:B --> T:outbound
API Surface
Endpoint
POST https://graph.microsoft.com/beta/servicePrincipals/{servicePrincipalId}/synchronization/jobs/{jobId}/bulkUpload
The servicePrincipalId and jobId are specific to the inbound provisioning app you create in the Entra admin center. You can retrieve them via the Graph API:
GET https://graph.microsoft.com/beta/servicePrincipals?$filter=displayName eq '{appName}'
GET https://graph.microsoft.com/beta/servicePrincipals/{id}/synchronization/jobs
Request Format
The request body uses SCIM bulk request format with Content-Type: application/scim+json:
{
"schemas": ["urn:ietf:params:scim:api:messages:2.0:BulkRequest"],
"Operations": [
{
"method": "POST",
"bulkId": "unique-id-1",
"path": "/Users",
"data": {
"schemas": [
"urn:ietf:params:scim:schemas:core:2.0:User",
"urn:ietf:params:scim:schemas:extension:enterprise:2.0:User"
],
"externalId": "EMP001",
"userName": "jdoe@contoso.com",
"active": true,
"displayName": "Jane Doe",
"name": {
"givenName": "Jane",
"familyName": "Doe"
},
"urn:ietf:params:scim:schemas:extension:enterprise:2.0:User": {
"employeeNumber": "EMP001",
"department": "Engineering",
"manager": {
"value": "MGR001"
}
}
}
}
]
}
Key Constraints
| Constraint | Value |
|---|---|
| Max operations per request | 50 user records |
| Throttle rate | 40 calls per 5-second window |
| Daily call limit (P1/P2 license) | 2,000 calls per 24 hours |
| Daily call limit (Governance license) | 6,000 calls per 24 hours |
| Max users per day (P1/P2) | 100,000 (2,000 calls x 50 records) |
| Max users per day (Governance) | 300,000 (6,000 calls x 50 records) |
| Response | 202 Accepted (asynchronous processing) |
SCIM Schema
The standard schemas are:
urn:ietf:params:scim:schemas:core:2.0:User- core user attributes (userName, displayName, name, emails, active)urn:ietf:params:scim:schemas:extension:enterprise:2.0:User- enterprise attributes (employeeNumber, department, manager, costCenter, division, organization)
For attributes not covered by the standard schemas, you can define custom SCIM schema extensions:
urn:ietf:params:scim:schemas:extension:<company>:1.0:User- custom attributes (payGrade, hireDate, jobCode, etc.)
Custom schema extensions must be registered with the provisioning app before use. The provisioning service maps SCIM attributes to Entra ID or AD attributes through the standard attribute mapping configuration.
Matching Logic
The externalId field in the SCIM payload is the primary matching attribute. By default, it maps to employeeId in Entra ID. The provisioning engine uses this to determine whether a user already exists:
- Match found: Update the existing user with the incoming data.
- No match: Create a new user.
- Record marked inactive (
"active": false): Disable or soft-delete the matched user according to the deprovisioning action configured in the provisioning app.
Use Cases
HR System Integration
Any HR system that can export identity data (via API, database query, or file export) can serve as a source. The API client reads employee records from the HR system and sends them to the bulkUpload endpoint. This replaces legacy CSV import pipelines and eliminates the need for staging databases.
Custom Identity Feeds
ISV platforms, partner identity systems, or federated organization directories can push identity data directly to Entra ID. The API client handles authentication, data transformation to SCIM format, and the upload call.
Bulk Provisioning and Migration
For initial tenant population or migration from another identity provider, the API can batch-load user records. The 50-record-per-call limit with up to 2,000 (or 6,000) daily calls supports loading tens of thousands of users per day.
Student Information Systems
Educational institutions with custom SIS platforms can provision student and faculty accounts by pushing enrollment data through the API.
Configuration
Step 1: Create the Provisioning App
- In the Entra admin center, go to Enterprise applications > New application.
- Search the gallery for either:
- API-driven provisioning to Microsoft Entra ID (for cloud-only users)
- API-driven provisioning to on-premises Active Directory (for hybrid users)
- Create the application.
For on-premises AD targets, you also need to install the provisioning agent on a domain-joined Windows server. The agent initiates outbound connections to the cloud; no inbound firewall rules are required.
Step 2: Configure Attribute Mappings
- Go to the provisioning app’s Provisioning page > Edit provisioning.
- Under Mappings, select the user mapping.
- Review and customize the mappings from SCIM attributes to Entra ID (or AD) attributes.
- Key mappings to verify:
externalIdtoemployeeId(matching attribute)userNametouserPrincipalNameactiveto account enabled status- Manager reference resolution (if using manager chain)
Step 3: Configure Scoping
Set scoping filters to control which incoming records are processed. By default, all records are in scope. You can add filters based on any SCIM attribute (e.g., only process records where department equals “Engineering”).
Step 4: Grant API Access
The API client needs an identity with the SynchronizationData-User.Upload permission. Three options:
- App registration with client secret or certificate. Register an app in Entra ID, grant the permission, and use client_credentials OAuth flow.
- Managed identity. If the API client runs in Azure (Logic App, Azure Function), assign a system-managed identity and grant the permission via PowerShell:
$servicePrincipal = Get-MgServicePrincipal -Filter "appId eq '00000003-0000-0000-c000-000000000000'"
$appRole = $servicePrincipal.AppRoles | Where-Object { $_.Value -eq "SynchronizationData-User.Upload" }
New-MgServicePrincipalAppRoleAssignment -ServicePrincipalId $managedIdentityId `
-PrincipalId $managedIdentityId `
-ResourceId $servicePrincipal.Id `
-AppRoleId $appRole.Id
- Delegated permissions. For interactive testing only (not recommended for production).
For reading provisioning logs, also grant AuditLog.Read.All or ProvisioningLog.Read.All.
Step 5: Start the Provisioning Service
- Set Provisioning Status to On.
- Save the configuration.
The service is now listening for bulkUpload requests. When data arrives, it processes records asynchronously using the configured mappings and scoping rules.
API Client Patterns
PowerShell with CSV2SCIM
Microsoft provides a CSV2SCIM PowerShell module for converting CSV files to SCIM bulk requests:
Install-Module CSV2SCIM
Import-Module CSV2SCIM
# Convert CSV to SCIM and upload
Import-Csv "employees.csv" | Send-ScimBulkRequest `
-ServicePrincipalId $spId `
-Body { $_ | ConvertTo-ScimBulkPayload }
The module handles batching (50 records per request), throttle-aware retry, and provisioning log retrieval.
cURL
# Get OAuth token
TOKEN=$(curl -s -X POST \
"https://login.microsoftonline.com/{tenantId}/oauth2/v2.0/token" \
-d "client_id={clientId}&client_secret={secret}&scope=https://graph.microsoft.com/.default&grant_type=client_credentials" \
| jq -r '.access_token')
# Send bulk upload
curl -X POST \
"https://graph.microsoft.com/beta/servicePrincipals/{spId}/synchronization/jobs/{jobId}/bulkUpload" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/scim+json" \
-d @scim-payload.json
Azure Logic Apps
A Logic App workflow can:
- Trigger on a schedule or event (e.g., file drop in blob storage).
- Call an Azure Function to convert source data (CSV, JSON, SQL query results) to SCIM format.
- POST the SCIM payload to the bulkUpload endpoint using the Logic App’s managed identity for authentication.
This approach requires no custom code deployment and supports monitoring through Logic App run history.
Monitoring and Troubleshooting
Provisioning Logs
All inbound provisioning activity is recorded in the Entra provisioning logs:
- Entra admin center: Monitor > Provisioning logs. Filter by the inbound provisioning app name.
- Graph API:
GET https://graph.microsoft.com/beta/auditLogs/provisioning?$filter=jobid eq '{jobId}'
Each log entry shows the source record, matched target, applied mappings, and the action taken (create, update, skip, or error).
Common Errors
| HTTP Status | Cause | Resolution |
|---|---|---|
| 400 Bad Request | Malformed SCIM payload, missing required attributes | Validate JSON structure and required SCIM fields |
| 401 Unauthorized | Invalid or expired token | Refresh the OAuth token; verify app registration |
| 403 Forbidden | Missing SynchronizationData-User.Upload permission | Grant the permission to the API client identity |
| 429 Too Many Requests | Throttle limit exceeded | Implement backoff; respect Retry-After header |
Quarantine
If the provisioning service encounters repeated errors (e.g., connectivity issues, invalid mappings), it enters quarantine. In quarantine, the processing frequency drops and the service reports degraded status. Fix the root cause, then restart the provisioning cycle to clear quarantine.
UPN Conflicts
When provisioning to Entra ID, userPrincipalName must be unique across the tenant. If the API sends a record that maps to an existing UPN, provisioning fails for that record. Use a unique UPN generation expression in attribute mappings (e.g., append a number suffix) or pre-validate UPN uniqueness.
Manager Resolution
Manager references use externalId matching. The manager’s externalId in the SCIM payload must match an existing user’s employeeId in Entra ID. If the manager has not been provisioned yet, the manager reference fails. Solution: process managers before their reports, or run two passes (first pass creates users, second pass updates manager references).
Custom SCIM Schema Extensions
For attributes not covered by the standard SCIM schemas, register a custom extension:
- In the provisioning app’s attribute mapping page, select Show advanced options > Edit attribute list for API.
- Add the custom schema namespace (e.g.,
urn:ietf:params:scim:schemas:extension:contoso:1.0:User). - Define attributes under the namespace (e.g.,
payGrade,costCenter,buildingCode). - Map the custom attributes to Entra ID extension attributes or custom security attributes.
The API client must include the custom schema namespace in the schemas array and provide the attribute values in the corresponding namespace object within each SCIM user record.
Next Steps
- What Is Provisioning - platform overview and architecture
- How Provisioning Works - provisioning cycles, scoping, matching, and mappings
- Monitoring and Logs - provisioning log analysis and alerting
- Troubleshooting - common error patterns and quarantine recovery
- Microsoft Graph bulkUpload API reference