caseHoldPolicies
Case Hold Policies. Legal-hold policies preserving content for an eDiscovery case. The caseHoldPolicies entity set lives under /exchange/v1.0/caseHoldPolicies in the eDiscovery & audit category.
| Fact | Value |
|---|---|
| Path | /exchange/v1.0/caseHoldPolicies |
| Category | eDiscovery & audit |
| Tier | Tier 5, Compliance |
| Read role | Compliance.IPPS.Read.All (Community) |
| Write role | none (read-only) |
| Backing cmdlets | Get-CaseHoldPolicy, Set-CaseHoldPolicy, New-CaseHoldPolicy, Remove-CaseHoldPolicy |
Routes
Section titled “Routes”| Method | Path | Synopsis | Role | Plan |
|---|---|---|---|---|
| GET | /exchange/v1.0/caseHoldPolicies |
List caseHoldPolicies entries within a specific compliance case. | Compliance.IPPS.Read |
Community |
| POST | /exchange/v1.0/caseHoldPolicies |
Create a new caseHoldPolicies entry. Body keys map 1:1 to New-CaseHoldPolicy parameter names (camelCased). Required: name + case (parent compliance case identifier). Returns 201 + Location + the freshly-fetched policy. | Compliance.IPPS.ReadWrite |
Enterprise |
| GET | /exchange/v1.0/caseHoldPolicies/{key} |
Get a single caseHoldPolicies entry by Identity (Name or GUID). | Compliance.IPPS.Read |
Community |
| PATCH | /exchange/v1.0/caseHoldPolicies/{key} |
Partial update of the caseHoldPolicies at /caseHoldPolicies/{key}. Body keys map 1:1 to Set-CaseHoldPolicy parameters. Use Add*/Remove* location params for delta updates rather than replacing whole lists. Returns 200 + the post-Set policy. | Compliance.IPPS.ReadWrite |
Enterprise |
| DELETE | /exchange/v1.0/caseHoldPolicies/{key} |
Remove the caseHoldPolicies at /caseHoldPolicies/{key}. Pre-checks distributionStatus and returns 409 PolicyDistributionPending while the policy is still distributing to workloads (per MS SCC docs - Remove-CaseHoldPolicy fails opaquely in that state). Returns 204 on success. | Compliance.IPPS.ReadWrite |
Enterprise |
GET /exchange/v1.0/caseHoldPolicies
Section titled “GET /exchange/v1.0/caseHoldPolicies”List caseHoldPolicies entries within a specific compliance case.
Backing cmdlet: Get-CaseHoldPolicy. Role: Compliance.IPPS.Read (Community).
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
case |
query | string | Compliance case Name or GUID. REQUIRED - the cmdlet rejects list-all without a case scope. | |
distributionDetail |
query | bool (switch) | Per-location membership breakdown. Slower; opt in only when needed. | |
includeBindings |
query | bool (switch) | Include policy bindings on locations. |
List caseHoldPolicies without ?case= - 400 MissingRequiredQuery
Section titled “List caseHoldPolicies without ?case= - 400 MissingRequiredQuery”Get the caseHoldPolicies list endpoint without scoping to a compliance case. Get-CaseHoldPolicy rejects list-all (raises ErrorMustSpecifyCaseOrPolicyException server-side); the controller pre-checks and returns 400 with a canonical envelope instead of letting the cmdlet error escape as 500. Fix: pass ?case=<caseName|caseGuid>, or fetch a single policy via GET /caseHoldPolicies/{key}.
GET /exchange/v1.0/caseHoldPolicies HTTP/1.1Authorization: Bearer {token}HTTP/1.1 400 Bad Request
{ "error": { "code": "MissingRequiredQuery", "message": "The 'case' query parameter is required for listing caseHoldPolicies. Get-CaseHoldPolicy rejects list-all - scope to a case (e.g. ?case=MyCase) or fetch a single policy via GET /caseHoldPolicies/{key}." }}Related: GET /exchange/v1.0/caseHoldPolicies/{key}
POST /exchange/v1.0/caseHoldPolicies
Section titled “POST /exchange/v1.0/caseHoldPolicies”Create a new caseHoldPolicies entry. Body keys map 1:1 to New-CaseHoldPolicy parameter names (camelCased). Required: name + case (parent compliance case identifier). Returns 201 + Location + the freshly-fetched policy.
Creates a legal-hold policy scoped to a parent compliance case. The case field is the Name or GUID of an existing compliance case - the API does NOT expose a /complianceCases entity set; callers must create the parent case via the Purview portal or Microsoft Graph eDiscovery API first. New-CaseHoldPolicy will fail if the referenced case doesn’t exist. At least one location (exchangeLocation, sharePointLocation, or publicFolderLocation) is typically required for a meaningful hold; an empty policy is allowed but holds nothing.After create, the policy distributes asynchronously - distributionStatus begins as “Pending”. DELETE is blocked until distribution completes (the controller’s pre-DELETE guard returns 409 PolicyDistributionPending); call PATCH with {“retryDistribution”: true} to expedite.IPPS-side - controller passes omitConfirm: true (per Phase E batch 1A finding that IPPS write cmdlets reject the universally-injected -Confirm:$false). Pass ?dryRun=true to map to -WhatIf.
Backing cmdlet: New-CaseHoldPolicy. Role: Compliance.IPPS.ReadWrite (Enterprise).
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
case |
body | yes | ||
name |
body | yes | ||
comment |
body | |||
enabled |
body | |||
exchangeLocation |
body | |||
publicFolderLocation |
body | |||
sharePointLocation |
body | |||
dryRun |
query | bool (switch) |
Create a legal-hold case policy
Section titled “Create a legal-hold case policy”Create a new case hold policy via New-CaseHoldPolicy on the IPPS pool. Required: name + case (parent compliance case Name or GUID). The parent case must exist before this call - the API does NOT expose a /complianceCases entity set, so create the case via the Purview portal or Microsoft Graph eDiscovery API first. At least one location (exchangeLocation / sharePointLocation / publicFolderLocation) is typically required for a meaningful hold. Tier 5 IPPS - backed by Compliance.IPPS.ReadWrite.All; the cmdlet ignores -Confirm so the controller omits it.
POST /exchange/v1.0/caseHoldPolicies HTTP/1.1Authorization: Bearer {token}Content-Type: application/json
{ "name": "Q1-2026-Litigation-Hold", "case": "case-litigation-houston", "comment": "Legal hold for Q1 2026 Houston litigation matter.", "enabled": true, "exchangeLocation": [ ], "sharePointLocation": [ "https://contoso.sharepoint.com/sites/legal-q1-2026" ]}HTTP/1.1 201 CreatedLocation: /exchange/v1.0/caseHoldPolicies/Q1-2026-Litigation-Hold
{ "@odata.context": "https://api.contoso.com/exchange/v1.0/$metadata#caseHoldPolicies/$entity", "Id": "hold-q1-2026-litigation", "Name": "Q1-2026-Litigation-Hold", "Identity": "hold-q1-2026-litigation", "Enabled": true, "Mode": "Enforce", "DistributionStatus": "Pending", "CaseId": "case-litigation-houston", "Comment": "Legal hold for Q1 2026 Houston litigation matter.", "ExchangeLocation": [ ], "SharePointLocation": [ "https://contoso.sharepoint.com/sites/legal-q1-2026" ], "OneDriveLocation": [], "PublicFolderLocation": []}Related: GET /exchange/v1.0/caseHoldPolicies?case={caseId}, GET /exchange/v1.0/caseHoldPolicies/{key}, PATCH /exchange/v1.0/caseHoldPolicies/{key}, DELETE /exchange/v1.0/caseHoldPolicies/{key}, POST /exchange/v1.0/caseHoldRules
GET /exchange/v1.0/caseHoldPolicies/{key}
Section titled “GET /exchange/v1.0/caseHoldPolicies/{key}”Get a single caseHoldPolicies entry by Identity (Name or GUID).
Backing cmdlet: Get-CaseHoldPolicy. Role: Compliance.IPPS.Read (Community).
Get a case hold policy by Identity
Section titled “Get a case hold policy by Identity”Get a single legal-hold case policy by Identity (Name or GUID). Backed by Get-CaseHoldPolicy on the IPPS pool. Note: the list endpoint at GET /caseHoldPolicies returns 400 unless ?case=<name|guid> is supplied - the cmdlet rejects list-all and requires either Identity OR Case scope.
GET /exchange/v1.0/caseHoldPolicies/{key} HTTP/1.1Authorization: Bearer {token}HTTP/1.1 200 OK
{ "@odata.context": "https://api.contoso.com/exchange/v1.0/$metadata#caseHoldPolicies/$entity", "Id": "hold-q1-2026", "Name": "Q1 2026 Hold Policy", "Identity": "hold-q1-2026", "Enabled": true, "Mode": "Enforce", "CaseId": "case-litigation-houston", "Comment": "Litigation hold for Q1 2026 estate matter.", "CreatedBy": "John Woods", "LastModifiedBy": "Ayla Kol", "WhenCreated": "2026-01-15T09:30:00Z", "WhenChanged": "2026-02-04T14:22:11Z"}Related: GET /exchange/v1.0/caseHoldPolicies?case={caseId}, GET /exchange/v1.0/caseHoldRules?policy={policyId}
PATCH /exchange/v1.0/caseHoldPolicies/{key}
Section titled “PATCH /exchange/v1.0/caseHoldPolicies/{key}”Partial update of the caseHoldPolicies at /caseHoldPolicies/{key}. Body keys map 1:1 to Set-CaseHoldPolicy parameters. Use Add/Remove location params for delta updates rather than replacing whole lists. Returns 200 + the post-Set policy.**
Set-CaseHoldPolicy has TWO mutually-exclusive parameter sets: Identity mode (default) - delta-style addExchangeLocation / removeExchangeLocation / etc. + comment + enabled. Use this to adjust the hold scope or toggle the policy on/off.RetryDistribution mode - pass ONLY {“retryDistribution”: true} to expedite SCC distribution if the policy is stuck in distributionStatus: “Pending”. The cmdlet rejects this switch when mixed with Identity-mode params.IPPS-side - controller passes omitConfirm: true.
Backing cmdlet: Set-CaseHoldPolicy. Role: Compliance.IPPS.ReadWrite (Enterprise).
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
addExchangeLocation |
body | |||
addPublicFolderLocation |
body | |||
addSharePointLocation |
body | |||
comment |
body | |||
enabled |
body | |||
removeExchangeLocation |
body | |||
removePublicFolderLocation |
body | |||
removeSharePointLocation |
body | |||
dryRun |
query | bool (switch) |
Update a case hold policy
Section titled “Update a case hold policy”Partial update of a legal-hold case policy via Set-CaseHoldPolicy. Body keys map 1:1 to Set-CaseHoldPolicy parameters (camelCased); use the Add*/Remove* location fields for delta scope changes and comment/enabled to adjust metadata or toggle the hold. Returns 200 + the post-Set policy.
PATCH /exchange/v1.0/caseHoldPolicies/{key} HTTP/1.1Authorization: Bearer {token}Content-Type: application/json
{ "comment": "Updated by Exonaut sample automation", "enabled": true,}HTTP/1.1 200 OKContent-Type: application/json; odata.metadata=minimal
{ "Id": "exonaut-sample-caseHoldPolicies", "Identity": "exonaut-sample-caseHoldPolicies", "Name": "exonaut-sample-caseHoldPolicies", "Comment": "Updated by Exonaut sample automation", "Enabled": true, "ExchangeLocation": [ ], "DistributionStatus": "Pending"}Notes: Doc-derived from Wiki + write-param allowlist; not live-captured. Body fields are Wiki-valid; values are illustrative. Set-CaseHoldPolicy has two mutually-exclusive parameter sets - pass ONLY {“retryDistribution”: true} to expedite a stuck distribution; that switch is rejected when mixed with the Add*/Remove*/comment/enabled fields shown here.
Related: GET /exchange/v1.0/caseHoldPolicies
DELETE /exchange/v1.0/caseHoldPolicies/{key}
Section titled “DELETE /exchange/v1.0/caseHoldPolicies/{key}”Remove the caseHoldPolicies at /caseHoldPolicies/{key}. Pre-checks distributionStatus and returns 409 PolicyDistributionPending while the policy is still distributing to workloads (per MS SCC docs - Remove-CaseHoldPolicy fails opaquely in that state). Returns 204 on success.
State-machine guard ahead of the cmdlet (same pattern as retentionCompliancePolicies and labelPolicies): a GET-CaseHoldPolicy fetch checks distributionStatus first. While distribution is “Pending”, the SCC backend refuses Remove operations with an opaque “server side error” - this guard converts that into a clear 409 envelope (code: “PolicyDistributionPending”) BEFORE the cmdlet is invoked. MS-recommended remediation: wait until Distribution finishes (typically minutes), or call PATCH with {“retryDistribution”: true}. Removing all rules (DELETE each /caseHoldRules/{key}) is NOT a prerequisite for Remove-CaseHoldPolicy - child rules are removed cascadingly.IPPS-side - omitConfirm: true. Note: this cmdlet uses -ForceDeletion (not the usual -Force); we don’t expose it in the surface - the standard DELETE flow covers the practical case.
Backing cmdlet: Remove-CaseHoldPolicy. Role: Compliance.IPPS.ReadWrite (Enterprise).
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
dryRun |
query | bool (switch) |
Delete blocked while DistributionStatus is Pending
Section titled “Delete blocked while DistributionStatus is Pending”Delete the case-hold policy at GET /caseHoldPolicies/{key}. The controller pre-checks DistributionStatus via Get-CaseHoldPolicy: if the policy is still distributing to workloads (Pending), the SCC backend refuses Remove-CaseHoldPolicy with an opaque “server side error”. The guard converts that into a clear 409 PolicyDistributionPending envelope BEFORE the cmdlet is invoked. Either wait for distribution to complete (typically minutes), or call PATCH /caseHoldPolicies/{key} with {“retryDistribution”: true} to expedite.
DELETE /exchange/v1.0/caseHoldPolicies/{key} HTTP/1.1Authorization: Bearer {token}HTTP/1.1 409 Conflict
{ "error": { "code": "PolicyDistributionPending", "message": "Cannot remove case hold policy 'Q1-2026-Litigation-Hold' while DistributionStatus is 'Pending'. SCC distributes case hold policies to Exchange / SharePoint / OneDrive asynchronously and refuses Remove operations during distribution. Wait for the policy to settle (typically a few minutes) or call PATCH /caseHoldPolicies/Q1-2026-Litigation-Hold with {\"retryDistribution\": true} to expedite, then retry the DELETE." }}Related: GET /exchange/v1.0/caseHoldPolicies/{key}, PATCH /exchange/v1.0/caseHoldPolicies/{key}
?dryRun=true previews of any write stay on Community.

