This is a continuation of the topic discussed in our previous article, make sure to go over it first.
REST cmdlets and additional methods
If you made it this far, you know how to run any Exchange Online cmdlet without the need to use the module itself. But there is more to the Exchange Online Admin API than the InvokeCommand method, as we know from the presence of the REST cmdlets. In fact, each REST cmdlet has its own endpoint under https://outlook.office365.com/adminapi. For example:
$uri = "https://outlook.office365.com/adminapi/beta/tenant.onmicrosoft.com/Mailbox('user@domain.com')"
$res = Invoke-WebRequest -Uri $uri -Headers $authHeader
($res.Content | ConvertFrom-Json)
corresponds to the Get-EXOMailbox cmdlet, and so on. But that’s not all! A plethora of other methods are also available for us to tap into, all nested under the same “parent” endpoint. While there isn’t any publicly documented list of endpoints, you can get one by simply running a GET query against https://outlook.office365.com/adminapi/beta/tenant.onmicrosoft.com:
$uri = "https://outlook.office365.com/adminapi/beta/tenant.onmicrosoft.com" $res = Invoke-WebRequest -Uri $uri -Headers $authHeader $ApiEndpoints = ($res.Content | ConvertFrom-Json).value
Currently, there are a total of 51 entries returned, along with their URLs, as shown below:
$ApiEndpoints | sort Name name kind url ---- ---- --- ActiveSyncDeviceAccessRule EntitySet ActiveSyncDeviceAccessRule ActiveSyncDeviceClass EntitySet ActiveSyncDeviceClass ActiveSyncOrganizationSettings EntitySet ActiveSyncOrganizationSettings AddressBookPolicy EntitySet AddressBookPolicy AntiPhishPolicyPresentation EntitySet AntiPhishPolicyPresentation AntiPhishRule EntitySet AntiPhishRule AtpPolicyForO365Presentation EntitySet AtpPolicyForO365Presentation BasicInfo EntitySet BasicInfo CalendarProcessing EntitySet CalendarProcessing CasMailbox EntitySet CasMailbox CmdletInfo EntitySet CmdletInfo ConfigAnalyzerPolicyRecommendation EntitySet ConfigAnalyzerPolicyRecommendation DirectMobileDevice EntitySet DirectMobileDevice Divergence EntitySet Divergence DynamicDistributionGroup EntitySet DynamicDistributionGroup EligibleDistributionGroup EntitySet EligibleDistributionGroup ExchangeManagementScope EntitySet ExchangeManagementScope ExchangeRoleGroup EntitySet ExchangeRoleGroup ExchangeRoleGroupMember EntitySet ExchangeRoleGroupMember GraphConnectorGroup EntitySet GraphConnectorGroup GraphConnectorGroupMember EntitySet GraphConnectorGroupMember HistoricalSearch EntitySet HistoricalSearch HostedContentFilterPolicyPresentation EntitySet HostedContentFilterPolicyPresentation HostedContentFilterRule EntitySet HostedContentFilterRule InboundConnector EntitySet InboundConnector Mailbox EntitySet Mailbox MailboxAutoReplyConfiguration EntitySet MailboxAutoReplyConfiguration MailboxPlan EntitySet MailboxPlan MailboxRecoverableItem EntitySet MailboxRecoverableItem MalwareFilterPolicy EntitySet MalwareFilterPolicy MalwareFilterRule EntitySet MalwareFilterRule MobileDeviceMailboxPolicy EntitySet MobileDeviceMailboxPolicy OutboundConnector EntitySet OutboundConnector Place EntitySet Place Recipient EntitySet Recipient ReportSchedule EntitySet ReportSchedule RetentionPolicy EntitySet RetentionPolicy RoleAssignmentPolicy EntitySet RoleAssignmentPolicy RoleAssignments EntitySet RoleAssignments RoleDefinitions EntitySet RoleDefinitions SafeAttachmentPolicy EntitySet SafeAttachmentPolicy SafeAttachmentRule EntitySet SafeAttachmentRule SafeLinksPolicyPresentation EntitySet SafeLinksPolicyPresentation SafeLinksRule EntitySet SafeLinksRule SecurityPrincipal EntitySet SecurityPrincipal SharingPolicy EntitySet SharingPolicy UnifiedGroup EntitySet UnifiedGroup UnifiedRbacManagementScope EntitySet UnifiedRbacManagementScope UnifiedRbacRoleAssignment EntitySet UnifiedRbacRoleAssignment UnifiedRbacRoleDefinition EntitySet UnifiedRbacRoleDefinition User EntitySet User
If you examine the list above closely, you will notice that few methods that map to some of the existing REST cmdlets appear to be missing. One such example is the Get-EXOMailboxPermission cmdlet. So, what’s going on? Well, much like the Graph API, we have navigation properties which are not immediately visible from the above. We can however fetch the metadata document to expose them, as well as some additional methods and entities.
#Fetch the metadata document
$uri = 'https://outlook.office365.com/adminapi/beta/tenant.onmicrosoft.com/$metadata'
$res = Invoke-WebRequest -Uri $uri -Headers $authHeader
[xml]$metadata = $res.Content #cast to XML
#Explore the metadata
($metadata.Edmx.DataServices.Schema | ? {$_.Namespace -eq "Exchange"})
Namespace : Exchange
xmlns : http://docs.oasis-open.org/odata/ns/edm
ComplexType : {ComplexEntry, ByteArrayType, StringFieldDeltaUpdateData, MailboxRecoverableItemsQuery…}
EntityType : {BasicInfo, MailboxPermission, MailboxFolderPermission, MailboxFolder…}
EnumType : {ElcFolderType, OofState, ExternalAudience, RecipientAccessRight…}
Function : {GetMobileDeviceStatistics, GetMailboxStatistics, GetMailboxStatisticsV2, GetMailboxFolderStatistics…}
Action : {UpdateMailboxArchive, GetRecoverableItems, RestoreRecoverableItems, ValidateOutboundConnector…}
EntityContainer : EntityContainer
Note that you still need to be authenticated to query the metadata, the request will fail if you don’t pass a valid access token! The set of EntityType objects from the metadata map to the endpoint list above, with their total count being 57. This count includes the set of navigation properties we alluded to above, and here is how to get them:
($metadata.Edmx.DataServices.Schema | ? {$_.Namespace -eq "Exchange"}).EntityType | ? {$_.Name -eq "Mailbox"} | select -ExpandProperty NavigationProperty
Name Type ContainsTarget
---- ---- --------------
MailboxPermission Collection(Exchange.MailboxPermission) true
MailboxFolder Collection(Exchange.MailboxFolder) true
MobileDevice Collection(Exchange.MobileDevice) true
MailboxRecoverableItem Collection(Exchange.MailboxRecoverableItem) true
MailboxRegionalConfiguration Exchange.MailboxRegionalConfiguration true
Apart from the EntityType set, we also have a set of Action and Function to work with, both exposing some methods that are not accessible in cmdlet form. For example, we have the following set of Actions:
($metadata.Edmx.DataServices.Schema | ? {$_.Namespace -eq "Exchange"}).Action
Name IsBound Parameter
---- ------- ---------
UpdateMailboxArchive true {mailbox, archive}
GetRecoverableItems true {bindingParameter, value}
RestoreRecoverableItems true {bindingParameter, value}
ValidateOutboundConnector true {outboundConnector, recipients}
DeleteGroup true Parameter
DeleteGroupMember true {graphConnectorGroup, graphConnectorGroupMemberId}
UpgradeDistributionGroup true {EligibleDistributionGroup, DlIdentities}
ClearMobileDevice true {mobileId, cancel}
SearchMessageTrace Parameter
SearchMessageTraceDetail Parameter
GetMailDetailTransportRuleReport Parameter
GetMailTrafficPolicyReport Parameter
GetMailDetailDlpPolicyReport Parameter
RbacQuery Parameter
Initialize
InitializeLiteFRC
InvokeCommand Parameter
as well as the following set of Functions:
($metadata.Edmx.DataServices.Schema | ? {$_.Namespace -eq "Exchange"}).Function
Name IsBound Parameter ReturnType
---- ------- --------- ----------
GetMobileDeviceStatistics true Parameter ReturnType
GetMailboxStatistics true Parameter ReturnType
GetMailboxStatisticsV2 true {bindingParameter, mailboxGuid, databaseGuid} ReturnType
GetMailboxFolderStatistics true {mailboxfolder, folderscope} ReturnType
BySmtpAddress true {CasMailbox, SmtpAddress} ReturnType
BySmtpAddress true {Recipient, SmtpAddress} ReturnType
GetRecipientPermissionByFilters true {Recipient, identity, trustee, accessRights} ReturnType
GetMobileDeviceStatisticsByIdentity Parameter ReturnType
Unfortunately, the metadata document doesn’t reveal much detail on how exactly we can use any of the above. But if you are willing to spend some time on it, you can get some of these working. For example, GetMailDetailTransportRuleReport is the endpoint to use if you want to fetch the (detailed) Exchange Transport Rule report. What the metadata doesn’t tell us is how to construct the query itself, but I got you covered! Here’s a working example of fetching the Exchange Transport Rule report with most of the parameters it accepts:
#Prepare the request body. Must include QueryTable node
$body = @{
QueryTable = @{
Direction = @("Outbound","Inbound")
Action = @("SetAuditSeverityHigh","SetAuditSeverityMedium","SetAuditSeverityLow")
EventType = @("TransportRuleHits")
StartDate = "2025-11-12T00:00:00.000Z"
EndDate = "2025-11-18T23:59:59.000Z"
Page = 1
PageSize = 30
}
}
#Fetch the report
$uri = "https://outlook.office365.com/adminapi/beta/tenant.onmicrosoft.com/GetMailDetailTransportRuleReport"
$res = Invoke-WebRequest -Uri $uri -Headers $authHeader -Method Post -Body ($body | ConvertTo-Json -Depth 5)
($res.Content | ConvertFrom-Json).value
The output should now include the relevant details from the Exchange Transport Rule report, as available within the EAC.
Some PATCH examples
If you made it thus far, it is time for the big reveal! While Microsoft has been telling us that it only plans to support read-only REST cmdlets, the underlying methods do support PATCH, POST and DELETE operations. One should probably think twice before attempting a non-read operation against an unsupported and undocumented endpoint, but such are indeed possible. For example, we can use the code below to update the custom attribute of a given mailbox user:
#Prepare the request body
$body = @{
CustomAttribute1="TestValue"
} | ConvertTo-Json -Depth 5
#PATCH the mailbox object
$uri = "https://outlook.office365.com/adminapi/beta/tenant.onmicrosoft.com/Mailbox('user@domain.com')"
$res = Invoke-WebRequest -Uri $uri -Headers $authHeader -Method PATCH -Body $body
Successful execution of the above request will result in an 204 NoContent reply. You can use any of the available methods to confirm the change was indeed committed! Support for PATCH operations is not limited to the endpoints corresponding to the REST cmdlets either. Some of the other methods outlined above also support PATCH-ing. For example, we can leverage the CalendarProcessing method to change how booking requests for a given room mailbox are processed:
#Prepare the request body
$body = @{
AllBookInPolicy=$false
} | ConvertTo-Json -Depth 5
#PATCH the mailbox object
$uri = "https://outlook.office365.com/adminapi/beta/tenant.onmicrosoft.com/CalendarProcessing('room@domain.com')"
$res = Invoke-WebRequest -Uri $uri -Headers $authHeader -Method Patch -Body $body
($res.Content | ConvertFrom-Json)
SCC Endpoints
As you are probably aware, the Security and Compliance PowerShell cmdlets also use the same proxy behavior, and therefore most of what we examined above applies to them, too. The permission requirements on the API side are also the same, you still need the Exchange.Manage or Exchange.ManageAsApp scopes, which you need to complement with the required Purview admin roles for the operations you plan to execute. There are however few differences in the process.
First, the resource (audience) for which you need to obtain the access token is different, as represented by the following URI: https://ps.compliance.protection.outlook.com. Once you have a valid access token for said resource, you can prepare the authentication header and the body payload just like we described above. The request URL must accordingly be adjusted to match the URI as well. Here’s a full example:
#Get an access token
$app = [Microsoft.Identity.Client.PublicClientApplicationBuilder]::Create("xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx").WithRedirectUri("http://localhost").Build()
$Scopes = New-Object System.Collections.Generic.List[string]
$Scope = "https://ps.compliance.protection.outlook.com/.default"
$Scopes.Add($Scope)
$token = $app.AcquireTokenInteractive($Scopes).ExecuteAsync().Result
#Set the auth header and routing hint
$authHeader = @{
'Content-Type'='application/json'
'Authorization'="Bearer $($token.AccessToken)"
'X-ResponseFormat'= "json"
'X-AnchorMailbox' = "UPN:user@domain.com"
}
#Cmdlet payload
$body = @{
CmdletInput = @{
CmdletName="Get-RetentionCompliancePolicy"
}
} | ConvertTo-Json -Depth 5
#Sample Request
$uri = "https://eur02b.ps.compliance.protection.outlook.com/adminapi/beta/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/InvokeCommand"
$res = Invoke-WebRequest -Method POST -Uri $uri -Headers $authHeader -Body $body
($res.Content | ConvertFrom-Json).Value
You might have noticed that we use the region-specific URI in the above request, i.e. “eur02b”, but the global one also works.
Closing remarks and summary
One thing we haven’t mentioned thus far is that you can interchangeably use https://outlook.office.com/adminapi (without the “365” suffix) instead of the https://outlook.office365.com/adminapi across all the examples we covered. We’re following the notation used by the built-in cmdlets for our examples, but both should works equally fine.
On a similar note, you might have noticed that all examples leverage the /beta endpoint. This is again due to the behavior of the built-in cmdlets, and shouldn’t make a difference as there is no officially supported version of the API anyway. There is a /v1.0 endpoint as well, with some notable differences in the set of methods exposed therein. Stick to the /beta endpoint for the “full” experience. On the other hand, the recently announced additions to the Exchange Admin API are homed under a /v2.0 version of the endpoint. This one however has nothing to do with anything we’ve discussed here. TL;DR – best use the /beta endpoint and forget about the rest.
Another question that regularly pops up is restricting permissions, especially with the non-interactive scenario. Now that the RBAC for applications feature is available, you can treat service principals just like any other security principal in Exchange and manage role assignments for them as needed. The only thing you need to do is make sure the service principal object is “known” to Exchange, which unfortunately is still a manual task (i.e. you have to use New-ServicePrincipal to provision it). It is worth reiterating that the API scopes do not grant any access on their own, Exchange’s RBAC is responsible for that.
In summary, we explored the hidden depth behind the Exchange Online Admin API. On the “officially supported” front, said API powers the set of REST-based cmdlets and plays the role of a proxy service for all the classic cmdlets as means to bypass the WinRM dependency. Multiple other methods are available under the endpoint, enabling all sorts of useful operations, in a non-supported and non-documented manner. The high risk that comes with using such methods can be offset by the high reward of being able to perform operations via a RESTful interface, without any need to use PowerShell.
Sadly, to this day Microsoft has not communicated any plans to move all these to the Graph API, or expose them publicly in a supported manner. So while we can make some parallels with the Graph, for example the support for pagination or $select, many of the QoL improvements we have grown accustomed to with the Graph are not available, and likely never will be. Still, there are many scenarios where the existing admin API endpoints and methods can be useful, so hopefully this guide will get you started on your journey with them.
1 thought on “All you need to know about Exchange Online Admin API (or how to run cmdlets without PowerShell) – part 2”