Replace Application Ownership
The EasyLife 365 Identity API enables applying a desired ownership state to an Entra application registration or enterprise application, in stages, reporting what landed. The API synchronizes three ownership areas: application owners (stored directly on the Entra object; users and service principals), technical owners, and business owners (both recorded by EasyLife 365 Identity; version 1 accepts users only).
A bucket that is not included in the request is left untouched. Owners are added before any are removed, so the application never passes through a state with fewer owners than both the old and the new set.
Permissions
| Permission Type | Permissions (Scope) |
|---|---|
| Delegated (work or school account) | https://api.easylife365.cloud/admin/Config.ReadWrite.All |
| Application | https://api.easylife365.cloud/admin/Identity.Ownership.ReadWrite.All, https://api.easylife365.cloud/admin/Identity.Config.ReadWrite.All |
Either application permission grants access to this endpoint. Identity.Ownership.ReadWrite.All is the least-privilege choice for owner synchronization; Identity.Config.ReadWrite.All also grants the rest of the EasyLife 365 Identity administration API. Entra ID does not infer one role from the other, so the application must hold at least one of them explicitly.
Application permissions always require admin consent. See Create Application Registration for the app-only setup.
HTTP Request
PUT https://api.easylife365.cloud/admin/identity/v1/apps/{id}/ownership
Content-Type: application/json
| Parameter | Description |
|---|---|
id | The Entra Object ID of the target application registration or enterprise application (its service principal). |
HTTP Request Headers
| Header | Value |
|---|---|
| Authorization | ****** (Required) |
| Content-Type | application/json |
Request Body
| Property | Type | Description |
|---|---|---|
appType | String | appRegistration or enterpriseApplication. |
applicationOwners | Object | Optional. The desired application-owner bucket. Omit to leave application owners untouched. |
technicalOwners | Object | Optional. The desired technical-owner bucket. Omit to leave technical owners untouched. |
businessOwners | Object | Optional. The desired business-owner bucket. Omit to leave business owners untouched. |
options | Object | Optional. See below. |
Each bucket (applicationOwners, technicalOwners, businessOwners) has the shape:
| Property | Type | Description |
|---|---|---|
mode | String | replace (the default), add, or remove. |
owners | Array | Array of { "objectId": "...", "type": "..." }. type is user or servicePrincipal for applicationOwners; user for the other buckets. Names are not accepted — resolving one server-side would give the same payload different meanings on different days. |
options supports:
| Property | Type | Description |
|---|---|---|
expectedApplicationOwners | Array | Require the current Entra application owners to match this exact object-ID set. A mismatch returns 409 and applies nothing. |
continueOnError | Boolean | Continue past a failed stage. Default true. |
allowUnmanage | Boolean | Explicitly allow removing the EasyLife 365 Identity service principal. Without it, a request that would remove it applies nothing. |
allowNoOwners | Boolean | Explicitly allow a result with no Entra owners. Without it, such a request is refused. |
For an app-only request that would change application owners, the EasyLife 365 Identity service principal must already be an owner of the target object — the API performs those writes with its Microsoft Graph Application.ReadWrite.OwnedBy permission. allowUnmanage does not bypass this prerequisite; it only permits deliberately removing EasyLife 365 Identity once the caller is otherwise authorized. A delegated caller whose own Entra permissions authorize the owner change does not need EasyLife 365 Identity to own the target. Changes limited to technical or business owners do not require it either.
Response
A 200 OK is returned when at least one operation was applied. A 400 Bad Request is returned when a guardrail refused the change or the payload was malformed — nothing is applied. A 409 Conflict is returned when expectedApplicationOwners did not match the current owners — nothing is applied. All three carry the same body shape.
| Property | Type | Description |
|---|---|---|
appObjectId | String | The Entra Object ID of the target application. |
status | String | succeeded, partiallyFailed, or failed. |
changed | Boolean | false when the desired state already matched — the steady state of a recurring sync. |
errorCode | String | Set when the request was refused. null otherwise. |
error | String | A human-readable explanation of errorCode. null otherwise. |
stages[].stage | String | The ownership area the stage applied to. |
stages[].status | String | The outcome of that stage. |
stages[].operations[].target | String | The object ID the operation acted on. |
stages[].operations[].targetType | String | The type of target. |
stages[].operations[].action | String | The change that was attempted (add or remove). |
stages[].operations[].status | String | The outcome of that operation. |
stages[].operations[].authorizedBy | String | caller when the calling principal owns the app itself, easylife when EasyLife 365 Identity's own ownership made the write possible. |
stages[].operations[].errorCode | String | Set when that specific operation failed. null otherwise. |
ownership | Object | The ownership after the apply, in the same shape as Get Application Ownership. Reflects the current state when nothing was applied. |
Example 1: Replace application owners
This example replaces the application owners of the app registration with Object ID 57523d68-2913-4fc0-8f91-af100f37562f.
Request
PUT https://api.easylife365.cloud/admin/identity/v1/apps/57523d68-2913-4fc0-8f91-af100f37562f/ownership
Content-Type: application/json
{
"appType": "appRegistration",
"applicationOwners": {
"mode": "replace",
"owners": [
{ "objectId": "00d0c474-abd1-4dda-a516-646a5046647c", "type": "user" },
{ "objectId": "5c3a4a2b-6b7a-4a8e-9b8b-2f6e4d7c11a9", "type": "servicePrincipal" }
]
}
}
Response
{
"appObjectId": "57523d68-2913-4fc0-8f91-af100f37562f",
"status": "succeeded",
"changed": true,
"errorCode": null,
"error": null,
"stages": [
{
"stage": "applicationOwners",
"status": "succeeded",
"operations": [
{
"target": "5c3a4a2b-6b7a-4a8e-9b8b-2f6e4d7c11a9",
"targetType": "servicePrincipal",
"action": "add",
"status": "succeeded",
"authorizedBy": "caller",
"errorCode": null
}
]
}
],
"ownership": {
"app": {
"objectId": "57523d68-2913-4fc0-8f91-af100f37562f",
"appId": "1b6f4b1a-8f2f-4e3b-9c8b-9a2f6e4d7c11",
"displayName": "Contoso Invoicing",
"type": 0,
"isManaged": true
},
"applicationOwners": [
{
"objectId": "00d0c474-abd1-4dda-a516-646a5046647c",
"type": "user",
"displayName": "Alice Johnson",
"userPrincipalName": "alice.johnson@contoso.com",
"mail": "alice.johnson@contoso.com",
"appId": null
},
{
"objectId": "5c3a4a2b-6b7a-4a8e-9b8b-2f6e4d7c11a9",
"type": "servicePrincipal",
"displayName": "EasyLife 365 Identity",
"userPrincipalName": null,
"mail": null,
"appId": "5c3a4a2b-6b7a-4a8e-9b8b-2f6e4d7c11a9"
}
],
"technicalOwners": { "owners": [], "permissions": [] },
"businessOwners": { "owners": [], "permissions": [] },
"capabilities": {
"identityIsOwner": true,
"canWriteApplicationOwners": true,
"canWriteTechnicalOwners": true,
"canWriteBusinessOwners": true,
"reason": null
}
}
}
Example 2: Add a business owner
This example adds a business owner to the app registration with Object ID 57523d68-2913-4fc0-8f91-af100f37562f, leaving the existing business owners in place.
Request
PUT https://api.easylife365.cloud/admin/identity/v1/apps/57523d68-2913-4fc0-8f91-af100f37562f/ownership
Content-Type: application/json
{
"appType": "appRegistration",
"businessOwners": {
"mode": "add",
"owners": [
{ "objectId": "56a64fe6-4de3-4489-9719-0a3776c7399b", "type": "user" }
]
}
}
Response
{
"appObjectId": "57523d68-2913-4fc0-8f91-af100f37562f",
"status": "succeeded",
"changed": true,
"errorCode": null,
"error": null,
"stages": [
{
"stage": "businessOwners",
"status": "succeeded",
"operations": [
{
"target": "56a64fe6-4de3-4489-9719-0a3776c7399b",
"targetType": "user",
"action": "add",
"status": "succeeded",
"authorizedBy": "caller",
"errorCode": null
}
]
}
],
"ownership": {
"app": {
"objectId": "57523d68-2913-4fc0-8f91-af100f37562f",
"appId": "1b6f4b1a-8f2f-4e3b-9c8b-9a2f6e4d7c11",
"displayName": "Contoso Invoicing",
"type": 0,
"isManaged": true
},
"applicationOwners": [],
"technicalOwners": { "owners": [], "permissions": [] },
"businessOwners": {
"owners": [
{
"objectId": "56a64fe6-4de3-4489-9719-0a3776c7399b",
"type": "user",
"displayName": "Bob Smith",
"userPrincipalName": "bob.smith@contoso.com",
"mail": "bob.smith@contoso.com",
"appId": null
}
],
"permissions": []
},
"capabilities": {
"identityIsOwner": true,
"canWriteApplicationOwners": true,
"canWriteTechnicalOwners": true,
"canWriteBusinessOwners": true,
"reason": null
}
}
}
Example 3: Replace application owners using PowerShell
The following example demonstrates replacing the application owners using PowerShell. Before executing the script, ensure you've registered an application and obtained the TenantId and ClientId. Also, retrieve the correct URI and scope from this document.
$tenantId = "[TENANT_ID]"
$scope = "[SCOPE]"
$uri = "[URI]"
az login --tenant $tenantId | Out-Null
$accessToken = az account get-access-token `
--tenant $tenantId `
--scope $scope `
--query accessToken `
--output tsv
# Create object with values to pass to the API.
$body = @{
appType = "appRegistration"
applicationOwners = @{
mode = "replace"
owners = @(
@{ objectId = "00d0c474-abd1-4dda-a516-646a5046647c"; type = "user" },
@{ objectId = "5c3a4a2b-6b7a-4a8e-9b8b-2f6e4d7c11a9"; type = "servicePrincipal" }
)
}
}
# Invoke the API and capture responses.
$jsonBody = $body | ConvertTo-Json -Depth 5 -Compress
$encodedBody = [System.Text.Encoding]::UTF8.GetBytes($jsonBody)
$headers = @{
"Authorization"= "******"
"Content-Type" = "application/json"
}
$result = Invoke-RestMethod -Method PUT -Uri $uri -Headers $headers -Body $encodedBody
Write-Information "Apply status: $($result.status)" -InformationAction Continue