Microsoft has been telling us for years that it plans to deprecate the EWS API, yet has been very slow in addressing the gaps such deprecation would leave with the Graph API. With the October 2026 deadline approaching, we are finally seeing efforts to bridge said gaps, one of which just hit the Public preview milestone. Meet the “Admin API”, a name as misleading as it can possibly be… but more on that in a bit.
As clearly explained in the official blog post, the new set of endpoints is designed to only address specific EWS-related gaps. It includes the following:
- OrganizationConfig — Read tenant wide MailTips related configuration.
- AcceptedDomain — List accepted domains and core domain settings for the tenant.
- Mailbox — Read mailbox properties and manage Send on behalf delegates (view/update).
- MailboxFolderPermission — List, grant, modify, and remove folder level permissions (Inbox, Calendar, subfolders).
- DistributionGroupMember — Retrieve membership for distribution groups.
- DynamicDistributionGroupMember — Retrieve membership for dynamic distribution groups.
Of the above, only the two “mailbox” endpoints have support for non-read operations, namely for managing Send on behalf of permissions as well as mailbox folder permissions. Thus, calling this an “admin API” is quite the stretch, even for the likes of Microsoft’s marketing team. In fact, the corresponding EWS methods also enabled end users to manage their delegates, so we can even argue the primary goal was (client) support for self-management operations.
The implementation itself also raises some concerns. Microsoft appears to have strived to provide an experience very close to that of the InvokeCommand method, which is nowadays used to proxy all the Exchange Online PowerShell cmdlets, as we covered previously. Thus, each of the above endpoints only accepts a POST request with a JSON-formatted payload that lists a cmdlet and its parameters. This approach however raises the question why do we need six separate endpoints?
Next, we have the naming convention. The endpoints reside under https://outlook.office365.com/adminapi/v2.0/, as a separate endpoint was needed for some reason. Calling it /v2.0 will likely bring some confusion though, as there is no hint of any sort of backwards compatibility with anything that currently resides under /v1.0 (not to mention the latest version of the ExO PowerShell module still uses /beta).
Similarly, the permissions needed to access the new endpoints are named Exchange.ManageV2 (for delegate permissions scenarios) and Exchange.ManageAsAppV2 (for application permissions one). The moniker V2 again does not imply any sort of compatibility with the existing “V2”-less scopes (I had to :D), and will likely be a source of confusion, too. To finish up on permissions, much like any other Exchange-related endpoint we have available, you need to complement said scopes with a corresponding Role assignment on ExO side. You can use one of the built-in Roles, Role Groups, or Entra roles that map to them, or you can go granular and assign scoped access, as needed. As the new endpoints effectively call existing cmdlets, new roles have not been introduced on ExO side to support them.
The last bit we need to cover before trying some examples is the routing hint, aka the X-AnchorMailbox header. Do make sure to include it in your requests, otherwise you might observe some undesired behavior. For any details on it, the required permissions and the endpoints syntax, you can refer to the official documentation, I will not parrot it here.
Without further ado, here are some examples of using the new endpoints. Let’s start with the two “mailbox” ones, as they are the only endpoints that allow actual changes to be performed. More importantly, they can be also be leveraged by end users, as long as consent to the Exchange.ManageV2 scope is within their reach. This is made possible thanks to the default role assignment policy in Exchange Online, which allows for some self-service operations for end users, in turn powered by the corresponding (limited versions of the built-in) cmdlets.
The example below leverages the MSAL methods to obtain an access token in the delegate context for a given user. The scope is just the default one for the Exchange Online resource. We then set the authentication header, which also includes value for the routing hint. Lastly, we prepare the payload, which in our case is for the Get-MailboxFolderPermission cmdlet.
#App details
$app2 = [Microsoft.Identity.Client.PublicClientApplicationBuilder]::Create("xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx").WithRedirectUri("http://localhost").Build()
#Use the default scope for the ExO Admin API resource
$Scopes = New-Object System.Collections.Generic.List[string]
$Scope = "https://outlook.office365.com/.default"
$Scopes.Add($Scope)
#Get the token
$token2 = $app2.AcquireTokenInteractive($Scopes).ExecuteAsync().Result
# Auth header
$authHeader2 = @{
'Content-Type'='application/json'
'Authorization'="Bearer $($token2.AccessToken)"
'X-AnchorMailbox' = "UPN:user@domain.com"
}
# Cmdlet payload
$body = @{
CmdletInput = @{
CmdletName="Get-MailboxFolderPermission"
Parameters=@{Identity="user:\Calendar"}
}
} | ConvertTo-Json -Depth 5
# Sample request
$uri = 'https://outlook.office365.com/adminapi/v2.0/tenant.onmicrosoft.com/MailboxFolderPermission'
$res = Invoke-WebRequest -Method POST -Uri $uri -Headers $authHeader2 -Body $body
($res.Content | ConvertFrom-Json).Value
# Sample output
Identity : user:\Calendar
FolderName : Calendar
User : Default
AccessRights@odata.type : #Collection(String)
AccessRights : {LimitedDetails}
SharingPermissionFlags :
IsValid : True
Identity : user:\Calendar
FolderName : Calendar
User : AnotherUser
AccessRights@odata.type : #Collection(String)
AccessRights : {Reviewer}
SharingPermissionFlags :
IsValid : True
Identity : user:\Calendar
FolderName : Calendar
User : secgrp
AccessRights@odata.type : #Collection(String)
AccessRights : {Editor}
SharingPermissionFlags : Delegate, CanViewPrivateItems
IsValid : True
You might have noticed that we referenced the mailbox by Name (or Alias) in the above request, not by its SMTP address. In fact, any valid identifier recognized by the underlying PowerShell cmdlet will do just fine, thus giving us a bit of flexibility. To designate the folder, make sure to use the localized name though, i.e. “Kalender” for German and so on. Error handling is a bit of a mess, as you’d expect from cmdlets being proxied over multiple endpoints. On the same note, all of the examples in the official documentation use “\\” to escape the “\” char, which is not necessary for our PowerShell-based example above.
As mentioned already, end users can also manage their own delegation settings, that is the folder-level permissions, plus the Send on behalf of list, both courtesy of the built-in MyBaseOptions role. In our next example, we update the permissions on the Calendar folder. As noted in the documentation, for this scenario you can use the full set of “standard” parameters for the Set-MailboxFolderPermission cmdlet, including the -SharingPermissionFlags one to control access to private items and the -SendNotificationToUser one to trigger the notification email. And, you can even update the Default permissions level:
# Cmdlet payload
$body = @{
CmdletInput = @{
CmdletName="Set-MailboxFolderPermission"
Parameters=@{Identity="user@domain.com:\Calendar";User="Default";AccessRights=@("LimitedDetails")}
}
} | ConvertTo-Json -Depth 5
# Sample request
$uri = 'https://outlook.office365.com/adminapi/v2.0/tenant.onmicrosoft.com/MailboxFolderPermission'
$res = Invoke-WebRequest -Method POST -Uri $uri -Headers $authHeader2 -Body $body
Note that any of the non Get- cmdlets return an empty output on successful execution.
As another example, we can manage the Send on behalf of list for the user. This is done via the Mailbox endpoint and the (reduced version of the) Set-Mailbox cmdlet. End users need access to the MyMailboxDelegation role for this operation, whereas an admin must have the Mail Recipients or equivalent role assigned. We can even use the hash-table notation for adding/removing individual entries, without overwriting the list:
# Cmdlet payload
$body = @{
CmdletInput = @{
CmdletName="Set-Mailbox"
Parameters=@{
Identity="user@domain.com"
GrantSendOnBehalfTo=@{
remove=@("user@tenant.onmicrosoft.com")
'@odata.type' = "#Exchange.GenericHashTable"
}
}
}
} | ConvertTo-Json -Depth 5
# Sample request
$uri = 'https://outlook.office365.com/adminapi/v2.0/tenant.onmicrosoft.com/Mailbox'
$res = Invoke-WebRequest -Method POST -Uri $uri -Headers $authHeader2 -Body $body
As before, successful execution is indicated by status code of 200 and empty output.
We can explore examples for the remaining endpoints, but frankly, they are all rather boring and covered in sufficient detail in the documentation. Instead, let’s ask the big question: how does this all compare to what we have in the EWS API? Well, it works, which is the most important thing. In terms of usability things were certainly easier with the EWS API, where a single call was sufficient to give you all the relevant delegation details, for example. With the “admin API” implementation, you will have to issue a separate call to each “standard” folder to cover the same level of detail. In addition, we have no analog for one of the properties covered by EWS’ GetDelegates() method, namely MeetingRequestsDeliveryScope.
There is also an obvious disparity when it comes to end user access. While the chosen implementation still relies on the set of built-in cmdlets, as represented by the role assignment policy assigned to the user, they are now gated behind one additional obstacle, the Exchange.ManageV2 scope. Without it, the access token will not be honored by the backend and any request to the new endpoints will simply deny access. While said scope does not require admin consent, it is is highly unlikely that it will be left available to end users, given the current threat landscape.
At the end of the day, it’s nice to see Microsoft finally addressing some of the longstanding gaps with the EWS API. While the naming conventions and the overall implementation feel a bit sloppy, at the end of the day, the important thing is that it all works and can help close some of said gaps. Now let’s keep nagging for Graph API support for the Online Archive…
1 thought on “Quick look at the new Exchange Admin API additions”