Skip to main content
Tools Reference

CIPP Tools

Written By Christopher Scaminaci

Last updated 7 days ago

CIPP Tools

cipp_ · 439 tools · Free 176 · Pro 263 CIPP hosted API; raw JSON passthrough.

All connector tools · CIPP setup guide

CIPP tool groups

Tenants

ToolPlanAccessSummary
cipp_add_domainProWriteAdd a custom domain to a tenant via POST /api/AddDomain.
cipp_add_spnProWriteAdd CIPP service principal permissions to partner tenant.
cipp_add_tenantProWriteMulti-action tenant endpoint at POST /api/AddTenant.
cipp_clear_tenant_cacheProWriteForce CIPP to bypass its tenant cache on the next enumeration, via POST /api/ListTenants carrying the body key 'ClearCache' as a real JSON boolean true.
cipp_delete_domain_actionProDestructiveRun a domain action against a tenant via DELETE /api/ExecDomainAction.
cipp_edit_tenantProWriteEdit CIPP tenant configuration via POST /api/EditTenant.
cipp_edit_tenant_offboarding_defaultsProWriteEdit the default user-offboarding settings for a tenant via POST /api/EditTenantOffboardingDefaults.
cipp_exec_exclude_licensesProDestructiveChange CIPP's excluded-licences setting (a CIPP instance setting affecting licence reporting for EVERY tenant), via POST /api/ExecExcludeLicenses.
cipp_get_organizationFreeRead-onlyGet organization profile information for a tenant including company name, address, technical contacts, and partner information.
cipp_get_tenant_detailsFreeRead-onlyGet detailed information for a specific M365 tenant including organization info, license counts, and domain details.
cipp_list_app_consent_requestsFreeRead-onlyList pending application consent requests from users in the tenant awaiting admin approval.
cipp_list_csp_licensesFreeRead-onlyList CSP (Cloud Solution Provider) license subscriptions for a tenant including subscription name, quantity, and billing cycle.
cipp_list_domainsFreeRead-onlyList all domains registered for a tenant including verification status, default domain flag, and DNS records.
cipp_list_excluded_licensesFreeRead-onlyList the SKUs CIPP excludes from its licence reporting, via GET /api/ListExcludedLicenses (no parameters — this is a CIPP instance setting, not per-tenant).
cipp_list_external_tenant_infoFreeRead-onlyLook up external tenant information by domain or tenant ID.
cipp_list_licensesFreeRead-onlyCIPP's licence report for a tenant: SKU name, total units, consumed units, and available units.
cipp_list_oauth_appsFreeRead-onlyList the OAuth application grants in a tenant.
cipp_list_service_healthFreeRead-onlyList current M365 service health status for a tenant.
cipp_list_tenant_alignmentFreeRead-onlyList tenant alignment status — how each tenant's configuration compares to the standards templates applied to it — via GET /api/ListTenantAlignment.
cipp_list_tenant_onboardingFreeRead-onlyList tenant onboarding status and progress via GET /api/ListTenantOnboarding.
cipp_list_tenantsFreeRead-onlyList all M365 tenants managed by this CIPP instance via POST /api/ListTenants.
cipp_onboard_tenantProDestructiveOnboard a tenant to CIPP via POST /api/ExecOnboardTenant.
cipp_remove_tenant_capabilities_cacheProWriteClear CIPP's cached tenant-capabilities data for ONE tenant via GET /api/RemoveTenantCapabilitiesCache, forcing CIPP to re-evaluate that tenant's features and permissions on the next request.
cipp_send_org_messageProDestructiveCreate an M365 organizational message in a tenant, TARGETED AT ONE ENTRA SECURITY GROUP, via GET /api/ExecSendOrgMessage.
cipp_set_auth_methodProDestructiveConfigure authentication method policies for a tenant via POST /api/SetAuthMethod.

[CIPP] Add a custom domain to a tenant via POST /api/AddDomain. CIPP body keys per spec: 'domain' (camelCase, the new domain to add) and 'tenantFilter' (camelCase, required). The domain will need DNS verification after adding. Pass keys verbatim — caller is responsible for exact spec casing.

ParamTypeRequiredDefaultDescription
fieldsJsonstringyesDomain configuration fields as JSON object. Required body key per spec: 'domain' (camelCase, the new custom domain to add). Example: {"domain":"newdomain.com"}. The tool also injects 'tenantFilter' from the typed parameter. Keys are passed verbatim — caller is responsible for exact spec casing.
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Sent as the 'tenantFilter' body field (camelCase, required) per the CIPP spec. The tool injects this for you. Use cipp_list_tenants to discover available tenants.

[CIPP] Add CIPP service principal permissions to partner tenant. Required for CIPP API access.

[CIPP] Multi-action tenant endpoint at POST /api/AddTenant. 'Action' is read as `$Request.Body.Action ?? $Request.Query.Action` and switches over EXACTLY FOUR values: 'ValidateDomain', 'GetOrganizationProfile', 'AddTenant', 'ValidateAddress'; anything else falls to the default arm and returns state='Error' (capital E — the success arms use lowercase 'success', and JSON string comparison is case-sensitive) with 'Invalid action specified: <value>'. WARNING — Action='AddTenant' DOES NOT CREATE A TENANT: the Partner Center customer-creation call is commented out upstream ('# not doing this yet') and the handler returns a HARDCODED sample response (userName 'test', password 'this_is_not_a_real_password') wrapped as state='success' with 'Tenant created successfully'. An agent that dispatched it would report a fabricated tenant and fake credentials to a human, so THIS TOOL REFUSES Action='AddTenant' before dispatch; the other three actions dispatch normally and this tool is the way to reach them. The refusal inspects the request BODY, which is total as wired because StackJack's client posts this endpoint with no query string — if a query-capable overload is ever added, the refusal must be extended to the query lane or the fabricated-success path reopens. Body keys (PascalCase): 'Action', 'TenantName', 'CompanyName', 'AddressLine1', 'AddressLine2', 'City', 'State', 'PostalCode', 'Country', 'FirstName', 'LastName', 'Email', 'PhoneNumber'. NOT scoped via tenantFilter. Pass keys verbatim.

ParamTypeRequiredDefaultDescription
fieldsJsonstringyesRequest payload as a JSON object. 'Action' selects the arm and MUST be one of 'ValidateDomain' (check a domain is available), 'GetOrganizationProfile' (read the partner organization profile) or 'ValidateAddress' (validate the address block) — 'AddTenant' is refused before dispatch because upstream fabricates a success without creating anything (see the tool description). Remaining PascalCase body keys: 'TenantName' (also accepted on the query string), 'CompanyName', 'AddressLine1', 'AddressLine2', 'City', 'State', 'PostalCode', 'Country', 'FirstName', 'LastName', 'Email', 'PhoneNumber'. Example: {"Action":"ValidateDomain","TenantName":"contoso.onmicrosoft.com"}. Keys are passed verbatim — caller is responsible for exact spec casing.

[CIPP] Force CIPP to bypass its tenant cache on the next enumeration, via POST /api/ListTenants carrying the body key 'ClearCache' as a real JSON boolean true. THIS TOOL WRITES to the CIPP instance: upstream tests `if ($Request.Body.ClearCache -eq $true)` and, when it matches, calls Remove-CIPPCache to discard the cached tenant list before rebuilding it from the partner relationships. Nothing is removed from any M365 tenant and no configuration changes — the cache is rebuilt by the refresh this call queues, so the call is safely repeatable. The response is NOT a tenant list: the cache-clear branch returns EARLY with a fixed acknowledgement, {"Results":[{"Results":"Cache has been cleared and a tenant refresh is queued."}],"Metadata":{"Details":[...Remove-CIPPCache progress strings such as 'Removed N tenants' / 'Cache cleanup complete'...]}}. No tenant appears in the payload. The rebuild runs asynchronously as the 'UpdateTenants' orchestration, so call cipp_list_tenants AFTERWARDS to read the freshly enumerated tenants. Use cipp_list_tenants for ordinary discovery: it is the read-only tool and it never touches the cache.

ParamTypeRequiredDefaultDescription
tenantsOnlystringnonullScope of the clear, sent as body key 'TenantsOnly' (PascalCase). CIPP hands this straight to Remove-CIPPCache, which tests `if ($TenantsOnly -eq $false)` to decide whether to clear the WIDER cache beyond the tenant list. OMITTING it is NOT the same as 'false': a missing value is $null, `$null -eq $false` is False in PowerShell, so the wider cache is left alone and only the tenant list is cleared. Send 'false' explicitly for a full cache clear.

[CIPP] Run a domain action against a tenant via DELETE /api/ExecDomainAction. This is a THREE-WAY switch, NOT a delete-only endpoint — the destructive arm is one of three: 'verify' issues a Graph POST to /beta/domains//verify (submits the domain for verification); 'delete' issues a Graph DELETE on /beta/domains/ — DESTRUCTIVE and irreversible, the domain is removed from the tenant; 'setDefault' issues a Graph PATCH on /beta/domains/ with isDefault=true. Every arm runs -AsApp against the tenant named by 'tenantFilter'. Body keys read: 'tenantFilter', 'domain', 'Action'; upstream throws 'Action is required' on a blank Action and 'Invalid action: <x>' for anything outside the three. This tool takes the action and domain as typed parameters and refuses an unrecognized, ambiguous or overridden verb BEFORE dispatch — an 'Action' supplied through fieldsJson may not change the operation this call was reviewed and authorized as — so neither a mistyped nor a smuggled action can fall through to the delete arm.

ParamTypeRequiredDefaultDescription
actionstringyesThe domain action: verify | delete | setDefault. Matched case-insensitively and sent as body key 'Action' in CIPP's exact casing. 'delete' PERMANENTLY removes the domain from the tenant via Graph DELETE and cannot be undone; 'verify' submits it for verification; 'setDefault' makes it the tenant's default domain. Any other value is refused before the request is sent.
domainstringyesThe domain name to act on, e.g. contoso.com. Sent as body key 'domain' (lowercase). Use cipp_list_domains to discover the tenant's domains and their current verification/default state.
fieldsJsonstringnonullOptional extra body keys as a JSON object, merged LAST. Upstream reads only 'tenantFilter', 'domain' and 'Action', so any other key is discarded. It CANNOT change WHICH operation runs or WHOSE domain it runs against. All three keys are re-validated on the MERGED body: an 'Action', 'domain' or 'tenantFilter' here that disagrees with the typed parameter — or a second, differently-cased spelling of any of the three — is refused before dispatch. A differently-cased key does not replace the seeded one, it ships alongside it, because CIPP resolves body keys without regard to case; and CIPP reads the tenant from the BODY only, so the query string this tool also sets could not pull a retargeted action back. An override naming the SAME verb, the SAME domain or the SAME tenant is accepted and forwarded as written. Keys are passed verbatim — caller is responsible for exact spec casing.
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Sent as the 'tenantFilter' body key (camelCase, required) — upstream reads it from the body. The tool injects this for you. Use cipp_list_tenants to discover available tenants.

[CIPP] Edit CIPP tenant configuration via POST /api/EditTenant. Invoke-EditTenant reads EXACTLY THREE body keys — 'customerId' (camelCase, the M365 customer/tenant GUID that identifies which tenant to edit), 'tenantAlias' (camelCase, the display alias) and 'tenantGroups' (camelCase). There is NO 'GroupId' body key: an earlier version of this description listed one and upstream never reads it. 'tenantGroups' is an ARRAY OF OBJECTS — the handler iterates it and reads '.groupId' off each element (plus '.groupName' for its log line) — and it is a REPLACE SET, not an addition: upstream adds the tenant to every listed static group AND removes it from every static group it currently belongs to that is not in the list, so always send the tenant's COMPLETE intended static-group list. WARNING — DATA LOSS: a comma-separated string, or an array of bare strings, has no '.groupId' on any element, so the add loop skips everything and the remove loop then matches EVERY current membership — the tenant is stripped from ALL of its static groups while the call still returns 200 with 'Tenant details updated successfully'. Send objects. WARNING — 'tenantAlias' is not optional in effect either: omitting it (or sending an empty string) DELETES the tenant's stored alias (the row keyed on the customerId you send), so re-send the current alias whenever you are only changing 'tenantGroups'. This endpoint does NOT take 'tenantFilter'. Pass keys verbatim — caller is responsible for exact spec casing.

ParamTypeRequiredDefaultDescription
fieldsJsonstringyesTenant configuration as a JSON object. The only keys upstream reads: 'customerId' (camelCase) — send the M365 customer/tenant GUID. A domain name is NOT rejected (Get-Tenants resolves defaultDomainName/initialDomainName too, so the access gate passes and the call ordinarily returns 200), but every row is keyed on the string you send, so the alias FAILS TO PERSIST rather than failing outright: upstream writes the new displayName onto the GUID-keyed Tenants row, so the alias IS applied immediately, while the durable Alias row lands under a PartitionKey of the exact string you sent and CIPP only ever reads aliases under the customerId GUID — the next tenant-cache rebuild therefore reverts displayName to the GDAP relationship's. The alias-DELETE branch below is keyed on that same raw string, so under a domain name it never fires either. Use the GUID. 'tenantAlias' (camelCase, display alias in CIPP) — WARNING, this key is NOT optional in effect: OMITTING it (or sending an empty string) DELETES the tenant's stored alias — the Alias row keyed on the customerId you send — and triggers a tenant refresh, so always re-send the current alias when you are only changing tenantGroups. Supplying an alias also overwrites the tenant's displayName in CIPP's Tenants table (the original is kept as 'originalDisplayName'). 'tenantGroups' (camelCase, an ARRAY OF OBJECTS each carrying 'groupId'; 'groupName' is used only for CIPP's log line) — a REPLACE SET, so send the tenant's complete intended static-group list. Example: {"customerId":"<tenant-guid>","tenantAlias":"Contoso Updated","tenantGroups":[{"groupId":"<group-guid>","groupName":"Managed"}]}. WARNING: passing tenantGroups as a comma-separated string, or as an array of bare strings, REMOVES the tenant from every static group it currently belongs to and still returns success. Any other key (including 'GroupId') is discarded. Keys are passed verbatim — caller is responsible for exact spec casing.

[CIPP] Edit the default user-offboarding settings for a tenant via POST /api/EditTenantOffboardingDefaults. Invoke-EditTenantOffboardingDefaults reads EXACTLY THREE body keys — 'customerId' (camelCase), 'defaultDomainName' (camelCase) and 'offboardingDefaults' (camelCase). There is NO 'Alias' key and NO 'Groups' key: both appeared in an earlier version of this description and are silently discarded upstream. 'offboardingDefaults' must be a JSON OBJECT, never a pre-serialized string — the handler serializes it ITSELF with `$jsonValue = [string]($offboardingDefaults | ConvertTo-Json -Compress)`. WARNING: passing an already-serialized JSON string double-encodes it — CIPP wraps it again, so the string "" is STORED as a quoted "" instead of clearing the defaults. That is recoverable, not permanent: the clear-defaults branch (`$jsonValue -and $jsonValue -ne '' -and $jsonValue -ne 'null' -and $jsonValue -ne ''`) is evaluated on THIS request's serialized value and never on the stored row, so sending a real empty object on a later call still deletes it. Send to CLEAR the stored defaults. WARNING — omitting 'offboardingDefaults' is NOT a partial update: a missing value serializes to 'null' and takes that same CLEAR branch, wiping the tenant's stored defaults. These defaults are applied by user-offboarding flows; this is NOT the tenant-offboard endpoint. This endpoint does NOT take 'tenantFilter'. Pass keys verbatim — caller is responsible for exact spec casing.

ParamTypeRequiredDefaultDescription
fieldsJsonstringyesOffboarding defaults as a JSON object. 'customerId' (camelCase, the M365 customer/tenant GUID) is REQUIRED — a missing one is HTTP 400 'Customer ID is required' — and is the ONLY key that addresses the saved row (PartitionKey = customerId). 'defaultDomainName' (camelCase) is read ONLY on the clear path, where it is unioned with customerId so that a legacy row keyed by domain name is deleted too; it is ignored on save. 'offboardingDefaults' (camelCase) MUST be a JSON OBJECT because CIPP runs ConvertTo-Json over it itself. Example: {"customerId":"<tenant-guid>","defaultDomainName":"contoso.onmicrosoft.com","offboardingDefaults":{"RemoveLicenses":true,"ConvertToShared":true}}. Send "offboardingDefaults": to CLEAR the stored defaults. WARNING — OMITTING 'offboardingDefaults' is NOT a partial update: a missing value serializes to 'null' and takes the CLEAR branch, wiping the tenant's stored defaults. WARNING: a pre-serialized JSON string double-encodes, so "" is stored as a quoted "" rather than clearing — recoverable by sending a real on a later call. Keys 'Alias' and 'Groups' are NOT read and are discarded. Keys are passed verbatim — caller is responsible for exact spec casing.

[CIPP] Change CIPP's excluded-licences setting (a CIPP instance setting affecting licence reporting for EVERY tenant), via POST /api/ExecExcludeLicenses. Actions: 'AddExclusion' (exclude a SKU everywhere; needs guid + skuName), 'AlertOnly' (exclude from alerts only; needs guid + skuName), 'SetShowInDropdown' (toggle a SKU's licence-dropdown visibility; needs guid + showInDropdown; 500s when the GUID is not already excluded), 'RemoveExclusion' (needs guid; a GUID that is not excluded also 500s), 'RestoreDefaults' (re-seed CIPP's 26 defaults — ADDITIVE by default; with fullReset=true it FIRST DELETES EVERY ROW, including every exclusion an operator added by hand, then re-seeds: do not send fullReset=true unless the operator explicitly wants their custom exclusions destroyed). OVERWRITE TRAP: AddExclusion and AlertOnly rebuild the row from scratch, silently dropping a previously set ShowInLicenseDropdown flag — re-apply it with SetShowInDropdown afterwards. skuName is stored as the display name (the response's Product_Display_Name). ACCESS NOTE: admin/superadmin CIPP API clients only.

ParamTypeRequiredDefaultDescription
actionstringyesThe operation: AddExclusion | AlertOnly | SetShowInDropdown | RemoveExclusion | RestoreDefaults.
fullResetbooleannonullFor RestoreDefaults ONLY: true DELETES every exclusion row (custom ones included) before re-seeding the 26 defaults. Omit or false for the additive re-seed.
guidstringnonullLicense SKU GUID (from cipp_list_licenses or cipp_list_excluded_licenses). Required for every action except RestoreDefaults.
showInDropdownbooleannonullFor SetShowInDropdown: whether the SKU shows in CIPP's licence dropdown.
skuNamestringnonullLicense display name, stored as Product_Display_Name. Required for AddExclusion and AlertOnly.
ParamTypeRequiredDefaultDescription
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Use cipp_list_tenants to discover available tenants.
ParamTypeRequiredDefaultDescription
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Use cipp_list_tenants to discover available tenants.
ParamTypeRequiredDefaultDescription
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Use cipp_list_tenants to discover available tenants.
ParamTypeRequiredDefaultDescription
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Use cipp_list_tenants to discover available tenants.

[CIPP] List the SKUs CIPP excludes from its licence reporting, via GET /api/ListExcludedLicenses (no parameters — this is a CIPP instance setting, not per-tenant). Returns rows with GUID, Product_Display_Name, ExcludedEverywhere, ShowInLicenseDropdown, and a derived ExclusionType ('Excluded Everywhere' or 'Excluded from Alerts Only'). CIPP auto-seeds its 26-entry default list (mostly free/trial SKUs) when the table is empty. ACCESS NOTE: CIPP's role model grants CIPP.AppSettings.* only to admin and superadmin API clients — a readonly- or editor-role CIPP credential gets Access denied on this endpoint even though it is a read.

[CIPP] Look up external tenant information by domain or tenant ID. Returns organization name, tenant ID, and federation status.

ParamTypeRequiredDefaultDescription
tenantstringyesDomain (e.g., contoso.com) or Microsoft tenant ID (GUID) to look up. Sent as the 'tenant' query string per the CIPP spec (required).

[CIPP] CIPP's licence report for a tenant: SKU name, total units, consumed units, and available units. NOT necessarily every licence in the tenant — the report reflects CIPP's excluded-licences setting (CIPP ships a 26-SKU default exclusion list, typically free/trial SKUs). Use cipp_list_excluded_licenses to see exactly which SKUs are excluded. includeExcluded is a TWO-CONDITION gate on CIPP's side: an excluded SKU appears only when includeExcluded is true AND that SKU is marked ShowInLicenseDropdown in CIPP's exclusion settings (no default exclusion row is) — mark specific SKUs with cipp_exec_exclude_licenses action SetShowInDropdown first, or the flag changes nothing.

ParamTypeRequiredDefaultDescription
includeExcludedbooleannofalseAlso include excluded SKUs that are marked ShowInLicenseDropdown in CIPP's exclusion settings. Excluded SKUs without that mark stay hidden regardless — see the tool description.
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Use cipp_list_tenants to discover available tenants.

[CIPP] List the OAuth application grants in a tenant. Each row carries Name, ApplicationID, ObjectID, Scope (the delegated permissions, comma-joined) and StartTime. Two optional filters narrow the response: 'nameFilter' is a case-insensitive SUBSTRING match on Name, and 'appId' is a case-insensitive EXACT match on either ApplicationID or ObjectID. BOTH are applied by StackJack to the response and are NOT sent to CIPP, which takes tenantFilter and nothing else on this endpoint. When both are given, a row must satisfy both. With neither set the response is byte-for-byte what CIPP sent. No match returns an empty array [], which means nothing matched your filter, NOT that the tenant has no OAuth grants.

ParamTypeRequiredDefaultDescription
appIdstringnonullOpt-in exact id match, applied by StackJack to the response, NOT sent to CIPP, which has no filter on this endpoint. When set, a row is kept only if this value equals its ApplicationID or its ObjectID, compared case-insensitively and in FULL (not a substring). Either id works because a customer may hold either one: ApplicationID is the application registration's client id, ObjectID is the service principal's id inside this tenant. Default null, which returns every row.
nameFilterstringnonullOpt-in substring search, applied by StackJack to the response, NOT sent to CIPP, which has no filter on this endpoint. When set, a row is kept only if this text appears in its Name, matched case-insensitively as a SUBSTRING (so 'graph' matches 'Microsoft Graph Command Line Tools'). There are no wildcards and no regex. A row that carries no Name cannot match and is dropped. Default null, which returns every row.
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Use cipp_list_tenants to discover available tenants.

[CIPP] List current M365 service health status for a tenant. Returns service name, status, and any active incidents or advisories.

ParamTypeRequiredDefaultDescription
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Use cipp_list_tenants to discover available tenants.

[CIPP] List tenant alignment status — how each tenant's configuration compares to the standards templates applied to it — via GET /api/ListTenantAlignment. This report is ESTATE-WIDE by design: CIPP's entrypoint never reads a tenant (its own alignment page renders with no tenant selected), so the result is one row per tenant per standard for every tenant CIPP manages. The tenantFilter argument is accepted and forwarded but CIPP does not apply it; filter the rows on your side. Returns raw CIPP JSON.

ParamTypeRequiredDefaultDescription
summarybooleannofalseOptional. When true, CIPP returns its estate roll-up instead of the per-tenant/per-standard rows: a single JSON OBJECT with Average (overall score), ScoredTenantCount, Buckets (tenant counts for Strong/Good/Weak/Poor), Lowest (up to four lowest-scoring tenants), PendingDeviations and PendingTenantCount. Note the shape flips from an array to an object. Default false, so nothing built against the row list changes.
tenantFilterstringyesAccepted for compatibility but NOT applied by CIPP: this endpoint always returns every tenant's alignment rows. Pass any managed tenant domain (e.g., contoso.onmicrosoft.com) and filter the result yourself.

[CIPP] List tenant onboarding status and progress via GET /api/ListTenantOnboarding. Spec marks the body required even on this GET endpoint — the request must carry a JSON object. Spec body keys (mixed casing): 'gdapRoles' (camelCase array), 'id' (lowercase, the onboarding job id), 'ignoreMissingRoles' (camelCase boolean), 'remapRoles' (camelCase boolean), 'standardsExcludeAllTenants' (camelCase boolean). Returns onboarding steps, completion state, and any pending actions.

ParamTypeRequiredDefaultDescription
fieldsJsonstringnonullOptional fields to merge into the request body as a JSON object. Spec body keys (preserve EXACTLY): 'gdapRoles' (camelCase array), 'ignoreMissingRoles' (camelCase boolean), 'remapRoles' (camelCase boolean), 'standardsExcludeAllTenants' (camelCase boolean).
idstringnonullOptional onboarding job ID to filter by. Sent as body key 'id' (lowercase).

[CIPP] List all M365 tenants managed by this CIPP instance via POST /api/ListTenants. Invoke-ListTenants reads EXACTLY TWO body keys — 'ClearCache' and 'TenantsOnly' — and this tool sends NEITHER, because clearing CIPP's cache is a write and this is the read-only discovery tool; use cipp_clear_tenant_cache when you need a fresh enumeration. Every real filter/refresh knob is read off the QUERY string instead (AllTenantSelector, IncludeOffboardingDefaults, TriggerRefresh, TenantFilter/tenantFilter, Mode), and StackJack's client sends NO query string on this endpoint, so none of them can be supplied through this tool — the CIPP call is always a full enumeration. NARROWING IS THEREFORE CLIENT-SIDE, and this tool now does it for you: 'domainFilter' drops non-matching tenants and 'slim' reduces each remaining tenant to displayName + defaultDomainName + customerId. Both run in StackJack AFTER the full response arrives — they change what you receive, never what CIPP computes or bills, and they cannot reach a tenant the connected CIPP API client is not already allowed to see. USE THEM: a large CIPP instance answers with hundreds of tenants and tens of thousands of characters, which can exceed an MCP result budget for what is usually a single-tenant lookup. The shape is unchanged either way — still the raw JSON array CIPP returned, just with fewer elements and/or fewer keys per element. With neither argument set the response is byte-for-byte what CIPP sent. WARNING: 'integrationCompany' is different from both — it IS sent to CIPP and is NOT read anywhere in the endpoint, so it filters nothing and still returns every tenant. Returns tenant display name, default domain, and customer ID (plus the rest of CIPP's tenant record unless 'slim' is set). Use this first to discover available tenants and their domain filters.

ParamTypeRequiredDefaultDescription
domainFilterstringnonullOpt-in substring search, applied by StackJack to the response — NOT sent to CIPP, which has no readable filter on this endpoint. When set, a tenant is kept only if this text appears in its displayName, defaultDomainName or customerId, matched case-insensitively as a SUBSTRING (so 'contoso' matches both 'Contoso Ltd' and 'contoso.onmicrosoft.com'; there are no wildcards and no regex). Narrowing to one tenant is the normal way to use this tool once you know the name. No match returns an empty array [] — that means nothing matched your text, NOT that the instance manages no tenants; re-run with no filter to confirm. Default null, which returns every tenant.
integrationCompanystringnonullWARNING — INERT. Invoke-ListTenants never reads 'integrationCompany'; the value IS placed in the request body, accepted and discarded, and the response is the unfiltered full tenant list. Do NOT use it to scope results — a caller who passes it will believe they filtered when they did not. Use 'domainFilter' below, which really does narrow the response. Retained only so existing callers do not hard-fail.
slimbooleannofalseOpt-in field reduction, applied by StackJack to the response — NOT sent to CIPP. When true, each tenant in the returned array is reduced to just displayName, defaultDomainName and customerId (any of those three that CIPP actually returned); the JSON stays the same array of objects, only with fewer keys per element. Use it for discovery, when you want the tenant's domain filter and nothing else; leave it false when you need the full tenant record (initialDomainName, domains, GraphErrorCount, portal links, offboardingDefaults, ...). Default false, which returns CIPP's bytes unchanged.

[CIPP] Onboard a tenant to CIPP via POST /api/ExecOnboardTenant. Invoke-ExecOnboardTenant reads EXACTLY these body keys: 'id', 'Cancel', 'Retry', 'gdapRoles', 'addMissingGroups', 'ignoreMissingRoles', 'autoMapRoles', 'standardsExcludeAllTenants'. WARNING: 'remapRoles' is NOT read anywhere in the endpoint — an earlier version of this description listed it, but it is accepted and discarded. 'id' is a SCALAR STRING, not a {label,value} object: it goes through `ConvertTo-CIPPODataFilterValue -Type String` and becomes the table RowKey, so an object stringifies to something that matches no row and you get 'Onboarding job not found' or an unrelated new record — use the typed 'id' parameter below. 'gdapRoles' is likewise consumed as-is (assigned straight onto the orchestrator item), never unwrapped from a {label,value} envelope. WARNING: 'Retry' is evaluated as `[bool]$Request.Body.Retry`, and in PowerShell `[bool]'false'` is TRUE — sending the STRING "false" FORCES a retry of the onboarding; use the typed 'retry' parameter, which sends a real JSON boolean. 'Cancel' is compared with `-eq $true`, which behaves correctly for a real boolean and for the strings 'true'/'false' alike. NOT scoped via tenantFilter — onboarding identifies the relationship via 'id'. Pass keys verbatim — caller is responsible for exact spec casing.

ParamTypeRequiredDefaultDescription
fieldsJsonstringyesOnboarding configuration as a JSON object, merged LAST so any key here overrides the typed parameters below. Keys upstream reads: 'gdapRoles' (consumed as-is onto the orchestrator item — do NOT wrap in {label,value}), 'addMissingGroups', 'autoMapRoles', 'ignoreMissingRoles', 'standardsExcludeAllTenants' (when true, excludes the tenant from all-tenant standards deployments), 'Cancel' (compared with -eq $true; cancels an in-progress onboard), and 'id'/'Retry' if you prefer them here over the typed parameters. Example: {"gdapRoles":["<role-id>"],"standardsExcludeAllTenants":false,"ignoreMissingRoles":false}. WARNING: 'remapRoles' is discarded, 'id' must be a scalar string, and a STRING "false" for 'Retry' is TRUE upstream and forces a retry. Keys are passed verbatim — caller is responsible for exact spec casing.
idstringnonullThe onboarding job / GDAP relationship id. Sent as body key 'id' as a SCALAR STRING — never a {label,value} object — because CIPP passes it through ConvertTo-CIPPODataFilterValue -Type String and uses the result as the table RowKey. Omit to let fieldsJson supply it.
retrybooleannonullRetry a failed onboarding. Sent as body key 'Retry' as a REAL JSON boolean, because upstream casts it with [bool] where the STRING "false" would be TRUE and would force the retry. Omit to leave the decision to CIPP — but note that CIPP's lookup only sees onboarding rows updated in the LAST 10 MINUTES. A row older than that counts as missing, so the call OVERWRITES it with a fresh 'queued' record and starts a new onboarding orchestration even with Retry absent. Do NOT use this tool to poll status: use cipp_list_tenant_onboarding for that.

[CIPP] Clear CIPP's cached tenant-capabilities data for ONE tenant via GET /api/RemoveTenantCapabilitiesCache, forcing CIPP to re-evaluate that tenant's features and permissions on the next request. The tenant is named by its DEFAULT DOMAIN NAME (e.g. contoso.onmicrosoft.com), not a customer id, and it rides the query string as 'defaultDomainName' — upstream reads the query only, never the body, and 400s with 'Missing required parameter: defaultDomainName' without it. A second upstream gate refuses with 403 when the calling CIPP client is not entitled to that tenant. Clearing the cache removes no configuration; the next request repopulates it.

ParamTypeRequiredDefaultDescription
defaultDomainNamestringyesThe tenant whose capabilities cache to clear, as its DEFAULT DOMAIN NAME (e.g. contoso.onmicrosoft.com) — not a tenant GUID or customer id. Required. Sent as the query key 'defaultDomainName'. Use cipp_list_tenants to find the default domain.

[CIPP] Create an M365 organizational message in a tenant, TARGETED AT ONE ENTRA SECURITY GROUP, via GET /api/ExecSendOrgMessage. Every input rides the query string with these exact casings: TenantFilter, ID, type, URL, freq — the endpoint never reads the body. It is NOT organization-wide: 'id' is the Entra group whose members receive the message (upstream puts it in targeting.includeIds under targetingType 'aadGroup'), so a value that is not an Entra group id targets nobody. messageType is refused here before dispatch unless it is one of 'taskbar', 'notification' or 'getStarted', because upstream's switch has NO default arm and an unrecognised value sends an EMPTY message rather than failing. WARNING: 'getStarted' is an unfinished stub upstream — its two cards carry hardcoded placeholder text ('My Message Value' / 'This message' / 'PlzClick') and clickUrl 'https://example.com/clickUrl/', and it sets no guidedContentId, so sending it publishes placeholder content into the customer tenant; only 'taskbar' and 'notification' produce a real message. Upstream performs no input validation, and its catch block maps EVERY failure to HTTP 403 — a genuine Graph permission error, a group id that does not exist, and an unaddressed call are indistinguishable by status. Read the normalized Graph error in the response body to tell them apart.

ParamTypeRequiredDefaultDescription
freqstringnonullOptional delivery frequency. Sent as the query key 'freq' (lowercase on this endpoint).
idstringyesThe Entra (Azure AD) SECURITY GROUP OBJECT ID whose members receive the message — NOT a message id. Required. Sent as the query key 'ID' (uppercase on this endpoint); upstream places it in targeting.includeIds with targetingType 'aadGroup'. The message's own identity (content.guidedContentId) is a hardcoded GUID selected by messageType and cannot be supplied.
messageTypestringyesMessage placement — one of exactly 'taskbar', 'notification' or 'getStarted' (matched case-insensitively, then sent in that canonical spelling as the query key 'type'). Any other value is refused before dispatch, because upstream's switch has no default arm and would send an empty message.
tenantFilterstringyesTarget tenant domain (e.g. contoso.onmicrosoft.com). Required. Sent as the query key 'TenantFilter' (PascalCase on this endpoint).
urlstringnonullOptional target URL for the message. Sent as the query key 'URL' (uppercase on this endpoint). Read ONLY by messageType 'taskbar' and 'notification' — the 'getStarted' arm ignores it entirely and hardcodes its own placeholder clickUrl.

[CIPP] Configure authentication method policies for a tenant via POST /api/SetAuthMethod. CIPP body keys are mixed: 'GroupIds' (PascalCase, string), 'Id' (PascalCase, the auth method policy ID, string), 'state' (lowercase, the enabled/disabled state, string), 'tenantFilter' (camelCase, required). Controls which Azure AD auth methods are available (FIDO2, Authenticator, SMS, etc.). Pass keys verbatim — caller is responsible for exact spec casing.

ParamTypeRequiredDefaultDescription
fieldsJsonstringyesAuth method configuration as JSON object. Body keys per spec: 'GroupIds' (PascalCase, string of group IDs the policy applies to), 'Id' (PascalCase, the authentication method policy identifier), 'state' (lowercase 's', the policy state — values follow Microsoft Graph 'enabled'/'disabled'). Example: {"Id":"microsoftAuthenticator","state":"enabled","GroupIds":"..."}. The tool also injects 'tenantFilter' from the typed parameter. Keys are passed verbatim — caller is responsible for exact spec casing.
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Sent as the 'tenantFilter' body field (camelCase, required) per the CIPP spec. The tool injects this for you. Use cipp_list_tenants to discover available tenants.

Users

ToolPlanAccessSummary
cipp_bec_checkFreeWriteStart OR poll a Business Email Compromise (BEC) background check for a specific user.
cipp_get_user_ca_policiesFreeRead-onlyEvaluate which conditional access policies would apply to a specific user, via GET /api/ListUserConditionalAccessPolicies.
cipp_get_user_devicesFreeRead-onlyGet devices registered or owned by a specific user including device name, OS, compliance status, and last sync time.
cipp_get_user_groupsFreeRead-onlyGet all group memberships for a specific user including security groups, distribution lists, and M365 groups.
cipp_get_user_mailboxFreeRead-onlyGet mailbox details for a specific user including mailbox type, size, forwarding rules, and archive status.
cipp_get_user_mfaFreeRead-onlyGet per-user MFA status and configuration for a specific user including MFA state, default method, and registered methods.
cipp_get_user_photoFreeRead-onlyGet the profile photo for a specific user.
cipp_get_user_signin_logsFreeRead-onlyGet recent sign-in log entries for a specific user including timestamp, IP address, location, app, and status.
cipp_list_basic_auth_usersFreeRead-onlyList users who have basic authentication (legacy auth) enabled.
cipp_list_deleted_usersFreeRead-onlyList soft-deleted users in the tenant recycle bin.
cipp_list_inactive_accountsFreeRead-onlyList user accounts that have not signed in recently.
cipp_list_mfa_usersFreeRead-onlyList all users with their MFA registration status and methods.
cipp_list_user_countsFreeRead-onlyGet user count statistics for a tenant including total users, licensed users, guests, and disabled accounts.
cipp_list_usersFreeRead-onlyList all users in a tenant including display name, UPN, license status, and account enabled state.

[CIPP] Start OR poll a Business Email Compromise (BEC) background check for a specific user. THIS TOOL WRITES — it is not a read: the start leg adds a 'Waiting' row to CIPP's BEC cache table and starts a BEC run orchestrator, and overwrite=true forces that queue-and-run branch even when a completed result is already cached. TWO-STEP: call it once WITHOUT guid to enqueue the job — that answers the acknowledgement envelope {"GUID":"<userId>"} — then call it again passing that same GUID value as guid to read the result. While the job is still running the poll answers {"Waiting":true}; once it completes the poll returns the findings. The job examines sign-in locations, inbox rules and rule changes, trusted/blocked sender and safelist changes, OneDrive/SharePoint sharing links, added applications, MFA methods, Intune devices, sent mail and tenant-wide password changes. It does NOT check mailbox-level forwarding (ForwardingSMTPAddress / ForwardingAddress) — check that separately; forwarding set via an inbox RULE does appear under the inbox-rule evidence. A completed check is served from CIPP's cache and is NOT re-run unless you pass overwrite=true. The full assessment is also readable in the CIPP UI.

ParamTypeRequiredDefaultDescription
guidstringnonullOptional. Omit on the first call to START the check. To POLL for the result, pass the GUID value the first call returned (it is the same user id). Sent as the query key 'GUID'.
overwritebooleannonullOptional. When true, forces a fresh check even if CIPP already has a cached result for this user. Sent as the query key 'overwrite'.
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Use cipp_list_tenants to discover available tenants.
userIdstringyesUser ID or UPN (e.g., user@contoso.com). Use cipp_list_users to find valid IDs.

[CIPP] Evaluate which conditional access policies would apply to a specific user, via GET /api/ListUserConditionalAccessPolicies. CIPP answers by POSTing Microsoft Graph's BETA Conditional Access What-If evaluation (identity/conditionalAccess/evaluate) app-only, and hands back whatever that returns. READ THE THREE EMPTY ANSWERS CAREFULLY, they mean different things: [] is a clean result meaning no policy applies to this user, while [] — an array holding one empty object — is CIPP reporting that its own Graph call FAILED. Upstream wraps that call in a catch which discards the error and substitutes an empty object, so the failure arrives as an HTTP 200 and nothing is written to CIPP's log; there is no record of the underlying error on either side. A third shape, [null], means the Graph call succeeded but returned no `value` member — upstream dereferences `.value` unconditionally and wraps the null. Only [] means 'no policy applies'; never read [] or [null] that way. The upstream body is passed through unchanged.

ParamTypeRequiredDefaultDescription
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Use cipp_list_tenants to discover available tenants.
userIdstringyesUser ID or UPN (e.g., user@contoso.com). Use cipp_list_users to find valid IDs.
ParamTypeRequiredDefaultDescription
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Use cipp_list_tenants to discover available tenants.
userIdstringyesUser ID or UPN (e.g., user@contoso.com). Use cipp_list_users to find valid IDs.
ParamTypeRequiredDefaultDescription
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Use cipp_list_tenants to discover available tenants.
userIdstringyesUser ID or UPN (e.g., user@contoso.com). Use cipp_list_users to find valid IDs.
ParamTypeRequiredDefaultDescription
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Use cipp_list_tenants to discover available tenants.
userIdstringyesUser ID or UPN (e.g., user@contoso.com). Use cipp_list_users to find valid IDs.
ParamTypeRequiredDefaultDescription
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Use cipp_list_tenants to discover available tenants.
userIdstringyesUser ID or UPN (e.g., user@contoso.com). Use cipp_list_users to find valid IDs.

[CIPP] Get the profile photo for a specific user. Photos are binary, so this returns a JSON envelope carrying a short-lived read-only SAS URL to the image in blob storage — sasUrl, contentType, suggestedFileName, sizeBytes and expiresAt — not the image itself. The URL stops working at expiresAt. A user who has no photo produces an upstream error rather than an empty result.

ParamTypeRequiredDefaultDescription
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Use cipp_list_tenants to discover available tenants.
userIdstringyesUser ID or UPN (e.g., user@contoso.com). Use cipp_list_users to find valid IDs.
ParamTypeRequiredDefaultDescription
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Use cipp_list_tenants to discover available tenants.
userIdstringyesUser ID or UPN (e.g., user@contoso.com). Use cipp_list_users to find valid IDs.

[CIPP] List users who have basic authentication (legacy auth) enabled. These accounts are security risks and should be migrated to modern auth.

ParamTypeRequiredDefaultDescription
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Use cipp_list_tenants to discover available tenants.

[CIPP] List soft-deleted users in the tenant recycle bin. These users can be restored within 30 days using cipp_restore_deleted_user.

ParamTypeRequiredDefaultDescription
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Use cipp_list_tenants to discover available tenants.

[CIPP] List user accounts that have not signed in recently. Returns last sign-in date and account details for identifying stale accounts.

ParamTypeRequiredDefaultDescription
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Use cipp_list_tenants to discover available tenants.

[CIPP] List all users with their MFA registration status and methods. Useful for identifying users without MFA configured.

ParamTypeRequiredDefaultDescription
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Use cipp_list_tenants to discover available tenants.
ParamTypeRequiredDefaultDescription
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Use cipp_list_tenants to discover available tenants.

[CIPP] List all users in a tenant including display name, UPN, license status, and account enabled state. Use this to discover user IDs for other user tools.

ParamTypeRequiredDefaultDescription
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Use cipp_list_tenants to discover available tenants.

User Management

ToolPlanAccessSummary
cipp_add_guestProWriteInvite an external guest user to the tenant via POST /api/AddGuest.
cipp_add_jit_admin_templateProWriteSave a new Just-In-Time (JIT) admin template via POST /api/AddJITAdminTemplate.
cipp_add_userProWriteCreate a new user in the tenant via POST /api/AddUser.
cipp_add_user_bulkProWriteCreate multiple users in a tenant in bulk via POST /api/AddUserBulk.
cipp_add_user_defaultsProWriteSave a new-user-creation defaults template for a tenant via POST /api/AddUserDefaults.
cipp_bec_remediateProDestructiveExecute Business Email Compromise remediation actions on a user via POST /api/ExecBECRemediate.
cipp_bulk_licenseProDestructiveChange license assignments for several users in one call via POST /api/ExecBulkLicense.
cipp_clear_immutable_idProDestructiveClear the on-premises immutable ID (sourceAnchor) for a user via POST /api/ExecClrImmId.
cipp_create_tapProDestructiveCreate a Temporary Access Pass (TAP) for a user via POST /api/ExecCreateTAP.
cipp_device_delete_identityProDestructiveChange or delete a device registration in Azure AD / Entra ID via POST /api/ExecDeviceDelete.
cipp_disable_userProDestructiveEnable or disable a user account via POST /api/ExecDisableUser.
cipp_dismiss_risky_userProDestructiveDismiss a user's risk state in Azure AD Identity Protection via POST /api/ExecDismissRiskyUser.
cipp_edit_jit_admin_templateProWriteUpdate an existing Just-In-Time (JIT) admin template via POST /api/EditJITAdminTemplate.
cipp_edit_userProWriteEdit properties of an existing user via PATCH /api/EditUser.
cipp_edit_user_aliasesProDestructiveAdd or remove email aliases (proxy addresses) for a user account via POST /api/EditUserAliases.
cipp_jit_adminProDestructiveEnable or configure Just-In-Time (JIT) admin access for a user via POST /api/ExecJITAdmin.
cipp_license_searchFreeWriteLook up Microsoft license SKU details by SKU IDs via POST /api/ExecLicenseSearch.
cipp_list_jit_adminFreeRead-onlyList currently active Just-In-Time admin sessions via GET /api/ListJITAdmin.
cipp_list_jit_admin_templatesFreeRead-onlyList available Just-In-Time admin templates.
cipp_list_new_user_defaultsFreeRead-onlyList saved new user creation default templates.
cipp_list_user_settingsFreeRead-onlyList CIPP user settings configuration including default behaviors for user management operations.
cipp_list_user_trusted_blocked_sendersFreeRead-onlyList the trusted and blocked senders configured for ONE user via GET /api/ListUserTrustedBlockedSenders.
cipp_offboard_userProDestructiveRun a multi-step user offboarding workflow against POST /api/ExecOffboardUser.
cipp_offboarding_job_statusFreeRead-onlyGet the status of queued CIPP offboarding jobs via GET /api/CIPPOffboardingJob.
cipp_onedrive_provisionProWriteProvision a OneDrive for Business site for a user via POST /api/ExecOnedriveProvision.
cipp_onedrive_shortcutProWriteCreate a OneDrive shortcut for a user to a SharePoint site via POST /api/ExecOneDriveShortCut.
cipp_password_never_expiresProDestructiveSet or unset the password-never-expires flag for a user account via POST /api/ExecPasswordNeverExpires.
cipp_patch_userProDestructiveApply partial updates to a user record via PATCH /api/PatchUser.
cipp_per_user_mfaProDestructiveEnable, disable, or enforce per-user (legacy) MFA for a specific user via POST /api/ExecPerUserMFA.
cipp_remove_deleted_objectProDestructivePermanently remove a soft-deleted object from the tenant recycle bin via POST /api/RemoveDeletedObject.
cipp_remove_jit_admin_templateProDestructiveDelete a Just-In-Time (JIT) admin template via POST /api/RemoveJITAdminTemplate.
cipp_remove_trusted_blocked_senderProDestructiveRemove an entry from a user's trusted or blocked senders list via POST /api/RemoveTrustedBlockedSender.
cipp_remove_userProDestructiveDelete a user from the tenant via POST /api/RemoveUser.
cipp_remove_user_default_templateProDestructiveRemove a saved new-user defaults template via POST /api/RemoveUserDefaultTemplate.
cipp_reprocess_user_licensesProWriteReprocess license assignments for a user to fix license provisioning errors or stale service plan states, via POST /api/ExecReprocessUserLicenses.
cipp_reset_mfaProDestructiveReset MFA registration for a user via POST /api/ExecResetMFA, requiring them to re-register their authentication methods on next sign-in.
cipp_reset_passwordProDestructiveReset a user's password via POST /api/ExecResetPass.
cipp_restore_deleted_userProWriteRestore a soft-deleted user from the tenant recycle bin via POST /api/ExecRestoreDeleted.
cipp_revoke_sessionsProDestructiveRevoke all active sessions and refresh tokens for a user via POST /api/ExecRevokeSessions, forcing re-authentication on all devices.
cipp_send_pushProDestructiveSend a test push notification to a user's registered Microsoft Authenticator MFA device via POST /api/ExecSendPush.
cipp_set_cloud_managedProDestructiveSet a directory object's on-premises sync behaviour via POST /api/ExecSetCloudManaged — CIPP PATCHes /beta/{users|groups|contacts}//onPremisesSyncBehavior with {"isCloudManaged": <bool>}.
cipp_set_user_photoProWriteSet or remove the profile photo for a user via POST /api/ExecSetUserPhoto.

[CIPP] Invite an external guest user to the tenant via POST /api/AddGuest. The guest's email is sent as the spec key `mail` (NOT `email`); `displayName`, `redirectUri` and `message` are read as-is, and an omitted redirectUri defaults server-side to https://myapps.microsoft.com. WARNING about the invite email: CIPP computes it as `[System.Convert]::ToBoolean($Request.Body.sendInvite) ?? $true`, and `??` only replaces null — never false. An ABSENT sendInvite therefore converts to false and the `?? $true` cannot rescue it, so CIPP creates the guest object, sends NO invitation email, and still returns HTTP 200. That silent no-invite is the opposite of what an operator inviting a guest expects, so this tool ALWAYS sends `sendInvite` as a real JSON boolean (defaulting to true) rather than letting it be omitted. CIPP forwards the value to Graph as `sendInvitationMessage`.

ParamTypeRequiredDefaultDescription
additionalFieldsJsonstringnonullForward-compatibility escape hatch: a JSON object whose properties are merged into the request body LAST, overriding any of the typed parameters above. Use only for new CIPP fields that this tool does not yet expose. Keys are passed verbatim — caller is responsible for exact spec casing.
displayNamestringyesDisplay name for the guest user.
mailstringyesEmail address of the guest to invite. Sent as the spec key 'mail'.
messagestringnonullOptional custom message included in the invitation email.
redirectUristringnonullOptional URL the guest is redirected to after accepting the invite. Sent as the spec key 'redirectUri'. When omitted, CIPP defaults it server-side to https://myapps.microsoft.com.
sendInvitebooleannonullWhether to send the Microsoft guest invitation email. Sent as the spec key 'sendInvite' as a REAL JSON boolean, and ALWAYS emitted — defaulting to true when you omit it. There is no CIPP or tenant policy default: omitting the key entirely makes CIPP compute false (see the tool description), creating the guest with no invitation email and still returning HTTP 200. Pass false explicitly to create the guest object without emailing them.
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Use cipp_list_tenants to discover available tenants.

[CIPP] Save a new Just-In-Time (JIT) admin template via POST /api/AddJITAdminTemplate. JIT templates pre-populate the cipp_jit_admin form (default user, roles, duration, expire action, etc.) so operators can elevate access with a single click. Body keys per spec: 'tenantFilter' (required), 'templateName' (string), 'defaultUserAction' (string enum: 'create' | 'select'), 'defaultFirstName' / 'defaultLastName' / 'defaultUserName' (strings), 'defaultDomain' / 'defaultDuration' / 'defaultExistingUser' / 'defaultExpireAction' / 'defaultRoles' (LabelValue {label,value} objects), 'defaultForTenant' (boolean), 'defaultNotificationActions' (string array, e.g., ['webhook','email','psa']), 'generateTAPByDefault' (boolean), 'reasonTemplate' (string). Use additionalFieldsJson to populate the LabelValue and array fields verbatim.

ParamTypeRequiredDefaultDescription
additionalFieldsJsonstringnonullForward-compatibility escape hatch: a JSON object whose properties are merged LAST into the request body, overriding the typed parameters above. Use for the LabelValue object fields ('defaultDomain', 'defaultDuration', 'defaultExistingUser', 'defaultExpireAction', 'defaultRoles' — each {label,value}) and 'defaultNotificationActions' (string array). Keys are passed verbatim — caller is responsible for exact spec casing.
defaultFirstNamestringnonullDefault first name when defaultUserAction='create'. Sent as the spec key 'defaultFirstName'.
defaultForTenantbooleannonullDefault for tenant flag — when true, this template applies to the tenant by default. Sent as the spec key 'defaultForTenant' (boolean).
defaultLastNamestringnonullDefault last name when defaultUserAction='create'. Sent as the spec key 'defaultLastName'.
defaultUserActionstringnonullPre-fill the JIT user-creation flow. Sent as the spec key 'defaultUserAction' (enum: 'create' | 'select'). 'create' provisions a new admin user; 'select' picks an existing user.
defaultUserNamestringnonullDefault username (mailNickname) when defaultUserAction='create'. Sent as the spec key 'defaultUserName'.
generateTapByDefaultbooleannonullIf true, the template will issue a Temporary Access Pass (TAP) by default when elevating. Sent as the spec key 'generateTAPByDefault' (boolean).
reasonTemplatestringnonullDefault reason text written into the audit log when the template is applied. Sent as the spec key 'reasonTemplate' (string).
templateNamestringyesDisplay name for the saved JIT admin template. Sent as the spec key 'templateName' (camelCase).
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Use cipp_list_tenants to discover available tenants. Sent as body key 'tenantFilter' (required by spec).

[CIPP] Create a new user in the tenant via POST /api/AddUser. CIPP composes the UPN from `username` + `PrimDomain.value` (LabelValue object). The displayName parameter maps to the spec key `DisplayName` (PascalCase). When userPrincipalName is provided as a full address (e.g., john.smith@contoso.com), the tool splits on '@' to populate `username` and `PrimDomain` automatically. Use additionalFieldsJson for license assignment, copyFrom (LabelValue), userTemplate (LabelValue), Scheduled, jobTitle, department, etc.

ParamTypeRequiredDefaultDescription
displayNamestringyesFull display name for the new user (e.g., 'John Smith'). Sent as the spec key 'DisplayName' (PascalCase).
fieldsJsonstringnonullAdditional fields as JSON object merged LAST into the request body (overrides the typed parameters above). Use this for fields like jobTitle, department, usageLocation (LabelValue), licenses, copyFrom (LabelValue), userTemplate (LabelValue), Scheduled, MustChangePass, password, mobilePhone, businessPhones, companyName, country, state, city, postalCode, streetAddress, givenName, surname, reference. Keys are passed verbatim — caller is responsible for exact spec casing.
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Use cipp_list_tenants to discover available tenants.
userPrincipalNamestringyesUser principal name / sign-in address (e.g., john.smith@contoso.com). The tool splits on '@' to populate the spec keys `username` (mailNickname) and `PrimDomain` (LabelValue {value: <domain>}).

[CIPP] Create multiple users in a tenant in bulk via POST /api/AddUserBulk. Body keys: 'tenantFilter' (required), 'BulkUser' (PascalCase), 'licenses', 'usageLocation'. WARNING — `BulkUser` IS AN ARRAY OF JSON OBJECTS, NOT AN ARRAY OF STRINGS OR CSV ROWS. CIPP does `foreach ($User in $BulkUsers)` and reads typed properties off each element: `mailNickName` (required), `domain` (required), and at least one of `displayName` / `givenName` / `surname`; it also reads `password` and `businessPhones`. String elements expose none of those properties, so every element fails the missing-required-fields check and NO user is created — the call still returns HTTP 200 with per-user "Required fields missing for..." text in Results, so always read Results. Read it DEFENSIVELY: Results is an ARRAY of per-user entries only when BulkUser is non-empty. Omit BulkUser (or send an empty array) and CIPP answers 200 with Results as a single bare OBJECT {resultText:'No users specified to import', state:'error'}; a batch where every element fails validation yields a lone {resultText:'No users to import'} entry. Both 'licenses' and 'usageLocation' are dereferenced as `.value ?? <the value itself>`, so each accepts either a LabelValue {label,value} object or a bare value.

ParamTypeRequiredDefaultDescription
fieldsJsonstringyesBulk user creation data as JSON object merged LAST into the request body. Spec body keys (exact casing): 'BulkUser' (PascalCase ARRAY OF OBJECTS — never strings or CSV rows; each object needs 'mailNickName' and 'domain', plus at least one of 'displayName'/'givenName'/'surname', and may carry 'password' and 'businessPhones'); 'licenses' (SKU IDs applied to every user created — a LabelValue {label,value} or a bare value, since CIPP reads `.value ?? ` it); 'usageLocation' (country code, same LabelValue-or-bare handling). Example: {"BulkUser":[{"mailNickName":"jsmith","domain":"contoso.com","displayName":"John Smith"}]}. Keys are passed verbatim — caller is responsible for exact spec casing.
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Use cipp_list_tenants to discover available tenants. Sent as body key 'tenantFilter'.

[CIPP] Save a new-user-creation defaults template for a tenant via POST /api/AddUserDefaults. Templates pre-populate licenses, groups, and profile settings for new users created through CIPP. Body keys per spec mix camelCase, PascalCase, and LabelValue objects: 'tenantFilter' (required), 'templateName' (camelCase), 'GUID' (PascalCase, optional template ID for updates), 'usernameFormat' (camelCase string template like '.'), 'defaultForTenant' (string), 'MustChangePass' (PascalCase boolean), 'removeLicenses' (camelCase boolean), 'licenses' (array of SKU IDs), 'displayName', 'givenName', 'surname', 'jobTitle', 'department', 'companyName', 'streetAddress', 'city', 'state', 'postalCode', 'country', 'mobilePhone', 'password', 'addedAliases', 'otherMails' (array). LabelValue objects: 'primDomain' {label,value}, 'usageLocation' {label,value}, 'copyFrom' {label,value}, 'setManager' {label,value}, 'setSponsor' {label,value}.

ParamTypeRequiredDefaultDescription
fieldsJsonstringyesUser defaults template as JSON object merged LAST into the request body. Spec body keys (exact casing): 'templateName' (camelCase string), 'GUID' (PascalCase template ID for updates), 'usernameFormat' (camelCase username format string), 'defaultForTenant' (string), 'MustChangePass' (PascalCase boolean), 'removeLicenses' (camelCase boolean), 'licenses' (array of SKU IDs), profile fields (camelCase: 'displayName', 'givenName', 'surname', 'jobTitle', 'department', 'companyName', 'streetAddress', 'city', 'state', 'postalCode', 'country', 'mobilePhone', 'password', 'addedAliases', 'otherMails' as array). LabelValue objects (each {label,value}): 'primDomain', 'usageLocation', 'copyFrom', 'setManager', 'setSponsor'. Keys are passed verbatim — caller is responsible for exact spec casing.
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Use cipp_list_tenants to discover available tenants. Sent as body key 'tenantFilter'.

[CIPP] Execute Business Email Compromise remediation actions on a user via POST /api/ExecBECRemediate. The user's object ID maps to the spec key `userid` (lowercase) and the optional UPN maps to `username` (lowercase). Run cipp_bec_check first to assess the situation. Note: the spec body contains only userid/username/tenantFilter; specific remediation actions (reset password, revoke sessions, etc.) are configured via separate CIPP endpoints.

ParamTypeRequiredDefaultDescription
additionalFieldsJsonstringnonullForward-compatibility escape hatch: a JSON object whose properties are merged into the request body LAST, overriding any of the typed parameters above. Use only for new CIPP fields that this tool does not yet expose. Keys are passed verbatim — caller is responsible for exact spec casing.
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Use cipp_list_tenants to discover available tenants.
userIdstringyesUser object ID (GUID) of the compromised user. Sent as the spec key 'userid' (lowercase). Use cipp_list_users to find valid IDs.
usernamestringnonullOptional UPN of the compromised user (e.g., user@contoso.com). Sent as the spec key 'username' (lowercase). CIPP uses it for audit/notification purposes.

[CIPP] Change license assignments for several users in one call via POST /api/ExecBulkLicense. The body is a JSON ARRAY — one entry per user — forwarded verbatim; entries carry their OWN tenantFilter, so one call can span tenants. Per entry: tenantFilter (tenant domain or GUID — required; 'AllTenants' is documented in CIPP's spec but NOT implemented by the backend and is refused here), userIds (array — required; CIPP acts on only the FIRST entry, the rest are ignored), LicenseOperation ('Add' | 'Remove' | 'Replace' — required and validated here, because an unrecognized value makes the CIPP backend silently reuse the PREVIOUS entry's computed licenses against this user), RemoveAllLicenses / ReplaceAllLicenses (booleans — each is honored ONLY with its matching operation: RemoveAllLicenses with LicenseOperation 'Remove', ReplaceAllLicenses with 'Replace'; on any other operation CIPP silently ignores the flag, so this tool refuses the mismatch), and the license buckets Licenses (the add-set for both Add and Replace), LicensesToRemove, LicensesToReplace — each an array of {label, value} OBJECTS where value is the SKU id: CIPP reads .value, so bare SKU strings do not apply as licenses. Per-entry failures come back as HTTP 200 with text in Results — always read Results. Discover SKU ids with cipp_list_licenses.

ParamTypeRequiredDefaultDescription
entriesJsonstringyesJSON ARRAY of per-user entries, forwarded verbatim. Each entry: {"tenantFilter":"contoso.onmicrosoft.com","userIds":["<user-guid>"],"LicenseOperation":"Add","Licenses":[{"label":"Office 365 E3","value":"<sku-guid>"}]}. See the tool description for the full field contract and traps.

[CIPP] Clear the on-premises immutable ID (sourceAnchor) for a user via POST /api/ExecClrImmId. Required when migrating from AD sync to cloud-only or when fixing sync conflicts. Body keys per spec: 'tenantFilter' (required) and 'ID' (UPPERCASE per spec, the user's object ID). The same 'ID' and 'tenantFilter' values also appear as query arguments per the spec.

ParamTypeRequiredDefaultDescription
fieldsJsonstringyesConfiguration as JSON object merged LAST into the request body. Spec body keys (exact casing): 'ID' (UPPERCASE, the user's object ID — required for the operation to target a specific user). Keys are passed verbatim — caller is responsible for exact spec casing.
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Use cipp_list_tenants to discover available tenants. Sent as both query argument and body key 'tenantFilter'.

[CIPP] Create a Temporary Access Pass (TAP) for a user via POST /api/ExecCreateTAP. TAPs allow passwordless onboarding or MFA recovery with a time-limited code. The user's object ID maps to the spec key `ID` (uppercase) and the validity duration maps to `lifetimeInMinutes` (NOT `lifetimeMinutes`). All values are string-typed per spec.

ParamTypeRequiredDefaultDescription
additionalFieldsJsonstringnonullForward-compatibility escape hatch: a JSON object whose properties are merged into the request body LAST, overriding any of the typed parameters above. Use only for new CIPP fields that this tool does not yet expose. Keys are passed verbatim — caller is responsible for exact spec casing.
isUsableOncebooleannonullIf true, the TAP can only be used once. Sent as the spec key 'isUsableOnce' (string-typed per spec).
lifetimeInMinutesintegernonullTAP validity duration in minutes. Sent as the spec key 'lifetimeInMinutes' (string-typed per spec). If omitted, uses the tenant default.
startDateTimestringnonullOptional ISO 8601 start datetime when the TAP becomes valid. Sent as the spec key 'startDateTime'.
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Use cipp_list_tenants to discover available tenants.
userIdstringyesUser object ID (GUID) or UPN of the user. Sent as the spec key 'ID' (uppercase). Use cipp_list_users to find valid IDs.

[CIPP] Change or delete a device registration in Azure AD / Entra ID via POST /api/ExecDeviceDelete. CIPP reads three body keys: `ID` (UPPERCASE — `$Request.Body.ID ?? $Request.Query.ID`), `action`, and `tenantFilter`. There is NO `deviceId` key, so a body naming the device as deviceId is silently dropped. `action` is NOT optional: Set-CIPPDeviceState declares it `[Parameter(Mandatory = $true)][ValidateSet('Enable','Disable','Delete')]`, so a call without it fails on parameter binding — this tool therefore always sends one, defaulting to 'Delete' to match this tool's name. 'Delete' issues a Graph DELETE on the device; 'Disable' and 'Enable' PATCH accountEnabled false/true instead, which is the reversible alternative to deletion. The ID must be a valid GUID — Set-CIPPDeviceState validates it and throws 'DeviceID must be a valid GUID.' otherwise.

ParamTypeRequiredDefaultDescription
actionstringno"Delete"What to do with the device. Accepted values (case-insensitive): 'Delete' (Graph DELETE — irreversible), 'Disable' (PATCH accountEnabled=false — reversible), 'Enable' (PATCH accountEnabled=true). Normalized to those exact literals and sent as the spec key 'action'. Defaults to 'Delete'. Required upstream — CIPP's helper declares it Mandatory with a ValidateSet, so it is always sent; any other value is refused before the call is made.
deviceIdstringyesObject ID of the device, which MUST be a valid GUID (CIPP validates it and throws 'DeviceID must be a valid GUID.' otherwise). Sent as the spec key 'ID' — UPPERCASE, the only casing CIPP reads ('deviceId' is never read).
fieldsJsonstringnonullForward-compatibility escape hatch: a JSON object whose properties are merged into the request body LAST, overriding the typed parameters above. Only 'ID', 'action' and 'tenantFilter' are read by CIPP. One key is re-validated on the MERGED body: 'action', which may NOT be overridden here — an override that disagrees with the typed 'action' parameter, or a second, differently-cased spelling of it, is refused before dispatch, because it would escalate a reversible 'Disable' into an irreversible Graph DELETE. 'ID' is unaffected and still overrides. Keys are passed verbatim — caller is responsible for exact spec casing.
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Use cipp_list_tenants to discover available tenants. Sent as body key 'tenantFilter'.

[CIPP] Enable or disable a user account via POST /api/ExecDisableUser. The spec uses `Enable` (PascalCase, string-typed) to indicate the desired state — true enables, false disables. The user's object ID maps to the spec key `ID` (uppercase). Disabled users cannot sign in but their data is preserved.

ParamTypeRequiredDefaultDescription
additionalFieldsJsonstringnonullForward-compatibility escape hatch: a JSON object whose properties are merged into the request body LAST, overriding any of the typed parameters above. Use only for new CIPP fields that this tool does not yet expose. Keys are passed verbatim — caller is responsible for exact spec casing.
disablebooleannotrueTrue to disable the account, false to re-enable it. Defaults to true. Sent as the spec key 'Enable' (string-typed) with the inverse value: disable=true => Enable='false', disable=false => Enable='true'.
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Use cipp_list_tenants to discover available tenants.
userIdstringyesUser object ID (GUID) or UPN of the user. Sent as the spec key 'ID' (uppercase). Use cipp_list_users to find valid IDs.

[CIPP] Dismiss a user's risk state in Azure AD Identity Protection via POST /api/ExecDismissRiskyUser. Use after investigating and confirming the user is not compromised. Body keys per spec: 'tenantFilter' (required), 'userId' (camelCase), 'userDisplayName' (camelCase, optional, used for audit/notification).

ParamTypeRequiredDefaultDescription
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Use cipp_list_tenants to discover available tenants. Sent as both query argument and body key 'tenantFilter'.
userIdstringyesUser ID or UPN of the risky user to dismiss (e.g., user@contoso.com). Use cipp_list_users to find valid IDs. Sent as body key 'userId' (camelCase).

[CIPP] Update an existing Just-In-Time (JIT) admin template via POST /api/EditJITAdminTemplate. Body keys per spec are identical to AddJITAdminTemplate plus 'GUID' (PascalCase, the template ID — required to identify which template to update). Use cipp_list_jit_admin_templates to find the GUID. Body keys: 'tenantFilter' (required), 'GUID' (PascalCase, required for edit), 'templateName' (string), 'defaultUserAction' (string enum: 'create' | 'select'), 'defaultFirstName' / 'defaultLastName' / 'defaultUserName' (strings), 'defaultDomain' / 'defaultDuration' / 'defaultExistingUser' / 'defaultExpireAction' / 'defaultRoles' (LabelValue {label,value} objects), 'defaultForTenant' (boolean), 'defaultNotificationActions' (string array), 'generateTAPByDefault' (boolean), 'reasonTemplate' (string). Use additionalFieldsJson to populate the LabelValue and array fields verbatim.

ParamTypeRequiredDefaultDescription
additionalFieldsJsonstringnonullForward-compatibility escape hatch: a JSON object whose properties are merged LAST into the request body, overriding the typed parameters above. Use for the LabelValue object fields ('defaultDomain', 'defaultDuration', 'defaultExistingUser', 'defaultExpireAction', 'defaultRoles' — each {label,value}) and 'defaultNotificationActions' (string array). Keys are passed verbatim — caller is responsible for exact spec casing.
defaultFirstNamestringnonullDefault first name when defaultUserAction='create'. Sent as the spec key 'defaultFirstName'.
defaultForTenantbooleannonullDefault for tenant flag — when true, this template applies to the tenant by default. Sent as the spec key 'defaultForTenant' (boolean).
defaultLastNamestringnonullDefault last name when defaultUserAction='create'. Sent as the spec key 'defaultLastName'.
defaultUserActionstringnonullPre-fill the JIT user-creation flow. Sent as the spec key 'defaultUserAction' (enum: 'create' | 'select'). 'create' provisions a new admin user; 'select' picks an existing user.
defaultUserNamestringnonullDefault username (mailNickname) when defaultUserAction='create'. Sent as the spec key 'defaultUserName'.
generateTapByDefaultbooleannonullIf true, the template will issue a Temporary Access Pass (TAP) by default when elevating. Sent as the spec key 'generateTAPByDefault' (boolean).
guidstringyesGUID of the JIT admin template to update. Sent as the spec key 'GUID' (PascalCase). Use cipp_list_jit_admin_templates to find available template IDs.
reasonTemplatestringnonullDefault reason text written into the audit log when the template is applied. Sent as the spec key 'reasonTemplate' (string).
templateNamestringyesDisplay name for the saved JIT admin template. Sent as the spec key 'templateName' (camelCase).
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Use cipp_list_tenants to discover available tenants. Sent as body key 'tenantFilter' (required by spec).

[CIPP] Edit properties of an existing user via PATCH /api/EditUser. READ THIS BEFORE INTERPRETING THE RESPONSE. Set-CIPPUser runs NINE independent halves — profile, password, licenses, aliases, CopyFrom group copy, group add, group remove, set manager, set sponsor — and none of them aborts the others. Most append their own outcome line to Results; the CopyFrom copy, set manager and set sponsor have no local catch and report through the helper they call instead. Results is therefore a MIXED report, not a verdict: never read the word 'Failed' appearing anywhere in it as 'my edit did not happen'. Branch on the line for the half you actually asked for — a successful licence change always begins 'Successfully set licenses for '. STATUS CODES: the call answers HTTP 200 carrying every half's outcome as text in Results, with exactly two exceptions — a blank or missing 'id' is refused up front with HTTP 400 {"Results":["Failed to edit user. No user ID provided"]} before anything runs, and HTTP 500 is returned when the scheduling branch's Add-CIPPScheduledTask throws or when Set-CIPPUser throws out of one of the three halves that have no local catch. SCHEDULED EDITS TAKE A DIFFERENT PATH ENTIRELY: send Scheduled.Enabled truthy and CIPP queues a task and answers 'Successfully created scheduled task to edit user <name>' — Set-CIPPUser does not run now, so none of the behaviour below applies until that task fires. KNOWN UPSTREAM DEFECT — A SPURIOUS PROFILE FAILURE ON EVERY EDIT THAT DOES NOT CHANGE THE PROFILE (CIPP-API @df3738d, Set-CIPPUser.ps1): CIPP composes the UPN UNCONDITIONALLY as username@(Domain, falling back to primDomain.value), with no guard on whether you sent those keys, and its non-empty-property filter drops only null/blank values — so with none of them supplied the literal string '@' survives into the Graph profile PATCH. Graph rejects that, and Results gains 'Failed to edit user. The domain portion of the userPrincipalName property is invalid...'. StackJack sends NO username, Domain, primDomain or userPrincipalName key unless YOU supply one, so that line is upstream noise rather than a sign your call was malformed. WHICH HALVES THAT EMPTY UPN ACTUALLY REACHES — this is what decides whether you may ignore it. (1) SAFE, ignore the line: group add and group remove key on the user's object id, not the UPN, so they are unaffected; licences also apply correctly and only the message is disfigured, reading 'Successfully set licenses for @.'. (2) NOT SAFE, PARTIAL APPLICATION: an alias edit adds each alias and THEN issues one final PATCH restoring the primary address to that same empty '@'. The restore fails, so Results shows 'Failed to add aliases to user <name>' and NO 'Success. Added aliases to user.' line — even though the aliases themselves are already on the account. Do not retry blindly; re-adding the aliases is not what is missing. (3) NOT SAFE: CopyFrom (group copy), setManager and setSponsor are each keyed on the empty UPN and cannot identify the user at all. DO NOT 'FIX' ANY OF THIS BY ADDING username + Domain TO A NON-PROFILE EDIT: that makes the UPN valid, which makes the profile PATCH SUCCEED, and it then writes userPrincipalName AND mailNickname (upstream sets mailNickname from username whenever you supply a non-empty one) on a call you meant as licences-only. mailNickname legitimately differs from the UPN local part after any rename, so that 'workaround' can silently change a sign-in address or alias. WHEN YOU ARE GENUINELY EDITING THE PROFILE (displayName, jobTitle, department, mobilePhone, address, company, otherMails, business phones, or a deliberate rename) you MUST include `username` (the user's CURRENT sign-in local part, before the @) and `Domain` (the sign-in domain), or that same composition sends a malformed UPN and Graph discards the ENTIRE profile half while the other halves run on independently. Pass the CURRENT local part unless you intend to rename the sign-in address, and do NOT pass a userPrincipalName field — it is not part of this endpoint's contract; the UPN comes from username@Domain. The user's object ID maps to the spec key `id` (lowercase) — pass the GUID/objectId or UPN. Spec key casing is mixed and intentional: top-level booleans like `Autopassword`, `MustChangePass`, `removeLicenses`, `sherweb` use varied casing per CIPP's contract. The `Scheduled` block carries {enabled, date}. Use fieldsJson for fields like DisplayName, jobTitle, department, mobilePhone, licenses (an array of {label,value} OBJECTS — CIPP reads .value, so bare SKU strings do not apply), AddToGroups (array), RemoveFromGroups (array), primDomain (LabelValue), usageLocation (LabelValue), setManager (LabelValue), setSponsor (LabelValue).

ParamTypeRequiredDefaultDescription
fieldsJsonstringyesFields to update as JSON object merged LAST into the request body. WHETHER TO INCLUDE username + Domain DEPENDS ENTIRELY ON WHAT YOU ARE EDITING — read the profile-vs-non-profile split in the tool description before deciding, and do NOT add them reflexively. Short form: include BOTH `username` (the CURRENT sign-in local part, before the @) and `Domain` (the sign-in domain) when you are editing PROFILE fields (DisplayName, jobTitle, department, mobilePhone, address, company, otherMails, business phones) or deliberately renaming — omitting them there makes Graph discard the whole profile half. OMIT them on a licences-only, groups-only, aliases-only, CopyFrom, setManager or setSponsor edit: supplying them makes the profile PATCH succeed and write userPrincipalName AND mailNickname on a call you did not mean as an identity change, which can silently alter a sign-in address. On those edits you will see a spurious 'Failed to edit user. The domain portion of the userPrincipalName property is invalid...' line in Results; the tool description says which halves that actually harms. Keys are passed verbatim — caller is responsible for exact spec casing (e.g., username, Domain, DisplayName, jobTitle, department, AddToGroups, RemoveFromGroups, licenses as an array of {label,value} objects (CIPP reads .value), primDomain {label,value}, usageLocation {label,value}, setManager {label,value}, Scheduled {enabled,date}, postExecution {webhook,email,psa}).
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Use cipp_list_tenants to discover available tenants.
userIdstringyesUser object ID (GUID) or UPN of the user to edit. Sent as the spec key 'id' (lowercase). Use cipp_list_users to find valid IDs.

[CIPP] Add or remove email aliases (proxy addresses) for a user account via POST /api/EditUserAliases. Body keys: 'tenantFilter' (read from the BODY only — `$UserObj.tenantFilter`, with no query fallback), 'id' (lowercase, the user's object ID or UPN — CIPP rejects a blank/absent id), 'AddedAliases' and 'RemovedAliases' (PascalCase strings), 'MakePrimary' (PascalCase, alias address to promote to primary SMTP). WARNING — MULTIPLE ALIASES ARE COMMA-SEPARATED, NOT NEWLINE-SEPARATED. CIPP splits both lists with `($UserObj.AddedAliases -split ',')` and trims each part; it never splits on a newline. A newline-delimited string such as "a@contoso.com\nb@contoso.com" therefore survives as ONE element containing an embedded newline, so CIPP attempts a single malformed alias instead of the two you intended — and the failure lands as text inside the HTTP 200 Results envelope rather than as an error. Use "a@contoso.com,b@contoso.com" and read Results.

ParamTypeRequiredDefaultDescription
fieldsJsonstringyesAlias configuration as JSON object merged LAST into the request body. Spec body keys (exact casing): 'id' (lowercase, user object ID or UPN — required; CIPP rejects a blank or absent id); 'AddedAliases' (PascalCase string, COMMA-separated aliases to add — e.g., 'a@contoso.com,b@contoso.com'; CIPP splits on ',' and trims, and NEVER on a newline, so a newline-delimited value is treated as one malformed alias); 'RemovedAliases' (PascalCase string, COMMA-separated aliases to remove, split identically); 'MakePrimary' (PascalCase string, alias to promote to primary SMTP). Keys are passed verbatim — caller is responsible for exact spec casing.
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Use cipp_list_tenants to discover available tenants. Sent as body key 'tenantFilter', which CIPP reads from the body only.

[CIPP] Enable or configure Just-In-Time (JIT) admin access for a user via POST /api/ExecJITAdmin. Grants temporary elevated privileges that expire after a set duration. The body uses 'tenantFilter' (required) plus a mix of PascalCase and camelCase keys; 'userAction' is an enum: 'create' | 'select'. Use fieldsJson to populate the body.

ParamTypeRequiredDefaultDescription
fieldsJsonstringyesJIT admin configuration as JSON object merged LAST into the request body. Spec body keys (exact casing): 'userAction' (enum: 'create' | 'select') controls whether to create a new user or select an existing one; 'existingUser' (LabelValue {label,value}) when userAction='select'; 'FirstName', 'LastName', 'Username', 'Domain' (LabelValue) when userAction='create'; 'AdminRoles' (LabelValue array/object), 'GroupMemberships' (string), 'useGroups' (boolean), 'useRoles' (boolean) — pick role-based or group-based elevation; 'StartDate' / 'EndDate' (ISO 8601 date-time); 'ExpireAction' (LabelValue) determines what happens at end-of-elevation; 'UseTAP' (boolean) issues a TAP for the elevation; 'Reason' (string) for audit; 'PostExecution' is an array of channel strings (e.g., ['webhook','email','psa']); 'jitAdminTemplate' (LabelValue) to apply a saved template. Keys are passed verbatim — caller is responsible for exact spec casing.
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Use cipp_list_tenants to discover available tenants. Sent as body key 'tenantFilter'.

[CIPP] Look up Microsoft license SKU details by SKU IDs via POST /api/ExecLicenseSearch. Returns SKU display names, included service plans, and pricing metadata for each SKU ID supplied. NOTE: this endpoint is NOT tenant-scoped — it queries the global Microsoft license catalog, so no tenantFilter is sent. Body keys per spec: 'skuIds' (string — typically a comma-separated list of SKU GUIDs).

ParamTypeRequiredDefaultDescription
skuIdsstringyesMicrosoft license SKU IDs to look up. Sent as the spec key 'skuIds' (string). Typically a comma-separated list of SKU GUIDs (e.g., '6fd2c87f-b296-42f0-b197-1e91e994b900,18181a46-0d4e-45cd-891e-60aabd171b4e').

[CIPP] List currently active Just-In-Time admin sessions via GET /api/ListJITAdmin. Returns user, role, expiration, and status. Query: 'tenantFilter' (camelCase, REQUIRED). A single tenant is answered inline. Pass 'AllTenants' for the estate-wide view — at CIPP-API master @df3738d that value queues a DURABLE FAN-OUT across every managed tenant and answers ASYNCHRONOUSLY: unless CIPP already holds rows newer than 60 minutes, the first call (and every call while the job runs) returns an EMPTY 'Results' array plus a QueueId in 'Metadata'. On that path an empty Results means 'still loading', not 'no JIT admins'; poll cipp_get_queue_status with the QueueId and call again once it completes.

ParamTypeRequiredDefaultDescription
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Use cipp_list_tenants to discover available tenants. Pass 'AllTenants' to queue the estate-wide fan-out.

[CIPP] List available Just-In-Time admin templates. Returns template names, roles, and default durations.

ParamTypeRequiredDefaultDescription
fieldsJsonstringnonullFilter configuration as JSON object (e.g., {"tenantFilter": "contoso.onmicrosoft.com"}).

[CIPP] List saved new user creation default templates. Returns template names, assigned licenses, groups, and profile settings.

ParamTypeRequiredDefaultDescription
fieldsJsonstringyesFilter configuration as JSON object (e.g., {"tenantFilter": "contoso.onmicrosoft.com"}).

[CIPP] List the trusted and blocked senders configured for ONE user via GET /api/ListUserTrustedBlockedSenders. Returns sender addresses and list type (trusted or blocked) from that mailbox's junk-email configuration. Query: 'tenantFilter' (camelCase) and 'UserID' (upstream's casing) — BOTH REQUIRED. Verified against CIPP-API master @df3738d, which anchors the Exchange lookup on the UserID; the tool sent neither value before 2026-09-03 and could not succeed under any input.

ParamTypeRequiredDefaultDescription
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Use cipp_list_tenants to discover available tenants.
userIdstringyesREQUIRED. The mailbox to read, as a UPN (user@contoso.com) or a directory object id. Sent as CIPP's 'UserID'. Use cipp_list_users or cipp_list_mailboxes to find valid values.

[CIPP] Run a multi-step user offboarding workflow against POST /api/ExecOffboardUser. Each action (license removal, mailbox conversion, access delegation, forwarding, session revocation, deletion, etc.) is a separate flag — set only the actions you want performed. This tool is destructive: it can permanently strip licenses, disable sign-in, convert the mailbox to shared, and delete the user. Confirm the UPN with cipp_list_users and inspect the user with cipp_get_user_mailbox before running. PRE-FLIGHT REFUSAL: CIPP validates the body before creating anything and answers HTTP 400 {"Results":[<errors>]} if tenantFilter is blank, if `user` resolves to no userPrincipalName, if Scheduled.enabled is true with an unparseable date, or if NO action flag was set — set at least one action or the call is refused outright. `user` accepts either bare UPN strings or {label,value} objects (upstream reads `$.value ?? $`); this tool sends the object form. WARNING — the response NEVER contains per-action outcomes, in EITHER mode. CIPP always routes this endpoint through Add-CIPPScheduledTask; scheduling only changes whether a ScheduledTime is attached (scheduled) or RunNow is requested (immediate). Both modes answer a task-CREATION acknowledgement, never license/mailbox/deletion results — but the two strings differ. An IMMEDIATE offboard (no scheduledRunAt) sets RunNow, so Add-CIPPScheduledTask creates the task AND queues Start-UserTasksOrchestrator for it straight away, answering "Task Offboarding: <upn> scheduled to run now". A SCHEDULED offboard answers "Successfully added task: <name>. It will run in <relative time>." Neither carries per-action outcomes: poll cipp_offboarding_job_status and re-check the user with cipp_list_users / cipp_get_user_mailbox. Use additionalFieldsJson only for forward-compatibility with new CIPP fields not yet exposed as typed parameters.

ParamTypeRequiredDefaultDescription
accessAutomapUpnstringnonullUPN to grant FullAccess to this user's mailbox WITH automap (mailbox auto-mounts in their Outlook profile on next start). Use for permanent successors. Sent as the LabelValue object {"label":<upn>,"value":<upn>}.
accessNoAutomapUpnstringnonullUPN to grant FullAccess to this user's mailbox WITHOUT automap (mailbox does NOT auto-mount in their Outlook). Common for short-term coverage. Sent as the LabelValue object {"label":<upn>,"value":<upn>}.
additionalFieldsJsonstringnonullForward-compatibility escape hatch: a JSON object whose properties are merged into the request body LAST, overriding any of the typed parameters above. Use only for new CIPP fields that this tool does not yet expose. Keys are passed verbatim — caller is responsible for exact spec casing.
clearImmutableIdbooleannonullClear the on-premises immutableId / sourceAnchor so the cloud account becomes cloud-managed. Use when severing AD Connect for this user.
convertToSharedbooleannonullConvert the mailbox to a shared mailbox so the data remains accessible without consuming an Exchange license. Mutually exclusive with deleteUser semantically — if both are set, CIPP will convert then delete.
deleteUserbooleannonullPermanently delete the user account (soft-delete: recoverable for 30 days via cipp_restore_deleted_user). Last step of a typical termination flow; pair with convertToShared first if the mailbox should survive.
disableForwardingbooleannonullDisable any existing forwarding rule on the mailbox. Use this OR forward — not both.
disableSignInbooleannonullBlock the user from signing in by setting AccountEnabled = false. The user retains their data and licenses but cannot authenticate.
forwardstringnonullEmail address to set as a forwarding target on the leaver's mailbox so future incoming mail flows to a successor. Sent as the LabelValue object {"label":<address>,"value":<address>} because the offboarding job reads `$Options.forward.value` with NO bare-string fallback — a plain string would queue the forwarding step and run it against a null address, doing nothing at HTTP 200. Setting this counts as an offboarding action for CIPP's pre-flight check. Pair with keepCopyOfForward to also retain a copy in the original mailbox.
hideFromGalbooleannonullHide the user from the Global Address List so they no longer appear in Outlook address picks. Reversible.
keepCopyOfForwardbooleannonullWhen forward is set, also keep a copy of forwarded mail in the leaver's mailbox (sets DeliverToMailboxAndForward). Without this, mail is forwarded only and the original mailbox sees nothing.
oneDriveAccessUpnstringnonullUPN to grant access to this user's OneDrive root so the recipient can copy/move files before the OneDrive is purged. Sent as the LabelValue object {"label":<upn>,"value":<upn>}.
outOfOfficeMessagestringnonullOut-of-office auto-reply text to set on the mailbox (typically a bounce/contact message for senders).
postExecutionEmailbooleannonullAfter completion, send the CIPP-configured notification email. One channel of the PostExecution notification block.
postExecutionPsabooleannonullAfter completion, post a ticket/note via the configured PSA integration. One channel of the PostExecution notification block.
postExecutionWebhookbooleannonullAfter completion, fire the configured CIPP webhook(s). One channel of the PostExecution notification block.
referencestringnonullFree-form reference string written into the audit log alongside the offboard (e.g., a ticket number or change-request ID). Visible in CIPP history.
removeCalendarInvitesbooleannonullCancel/decline calendar meetings the user organized so attendees see the meetings drop off their calendars. Irreversible for already-sent invites.
removeCalendarPermissionsbooleannonullRemove all calendar folder permissions other users held on this user's calendar.
removeGroupsbooleannonullRemove the user from every group (security, distribution, M365). Recommended when offboarding so the user loses all delegated access.
removeLicensesbooleannonullUnassign every Microsoft 365 / Office 365 license from the user. Frees up SKUs but stops mailbox/SharePoint access — pair with convertToShared if data must remain reachable.
removeMailboxPermissionsbooleannonullRemove all mailbox FullAccess/SendAs/SendOnBehalf permissions other users had on this mailbox. Required hygiene step when the user leaves.
removeMfaDevicesbooleannonullDelete every registered MFA method (authenticator app, phone, FIDO key) so the user must re-register on next sign-in. Use during BEC.
removeMobilebooleannonullWipe and remove the user's enrolled mobile devices (Intune-managed). Use when the device should not be reused or when terminating an employee.
removeRulesbooleannonullDelete every server-side inbox rule on the user's mailbox. Important during BEC remediation to remove auto-forwarding/auto-delete rules left by an attacker.
removeTeamsPhoneDidbooleannonullRelease the user's Teams Phone Direct Inward Dial number back to the tenant pool so it can be reassigned.
resetPasswordbooleannonullReset the user's password to a new random value. Use during BEC or when locking the user out before disable.
revokeSessionsbooleannonullRevoke all active OAuth refresh tokens and sign-in sessions, forcing the user off every device immediately. Critical for BEC and termination flows.
scheduledRunAtstringnonullUnix epoch seconds or ISO 8601 timestamp (e.g., 1777629600 or 2026-05-01T10:00:00Z) at which to run the offboard. The tool sends Unix epoch seconds because CIPP's runtime scheduler expects that shape. Sent as Scheduled {enabled,date}; leaving it null sends Scheduled.enabled=false, which sets RunNow so CIPP dispatches the task immediately rather than waiting for a scheduler tick. NOTE: the response is a task-creation acknowledgement either way (see the tool description) — setting this changes only the acknowledgement wording and when the task runs, never the envelope shape and never into per-action outcomes. Check outcomes with cipp_offboarding_job_status.
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Use cipp_list_tenants to discover available tenants.
userstringyesUPN of the user to offboard (e.g., user@contoso.com). The tool wraps this into the 'user' array as a {label,value} object automatically. Upstream resolves each entry as `$.value ?? $`, so a bare UPN string is equally accepted; a value that does not resolve to a UPN containing '@' is refused with HTTP 400. Use cipp_list_users to confirm the UPN before running.

[CIPP] Get the status of queued CIPP offboarding jobs via GET /api/CIPPOffboardingJob. When cipp_offboard_user is called with a scheduledRunAt, the response is only a queue acknowledgement; use this tool to check the actual per-action results once the job runs. UPSTREAM-BROKEN: at CIPP-API master @df3738d there is no HTTP entrypoint by this name — the only CIPP function so named is an internal worker whose parameters cannot accept what CIPP's HTTP dispatcher passes it — so this tool is expected to FAIL until CIPP ships a route for it, and no parameter added on the StackJack side would change that.

[CIPP] Provision a OneDrive for Business site for a user via POST /api/ExecOnedriveProvision. Required before the user can access OneDrive. CIPP reads exactly two keys: `UserPrincipalName` (PASCALCASE — `$Request.Body.UserPrincipalName ?? $Request.Query.UserPrincipalName`) and `tenantFilter` (camelCase). There is NO `userId` key — a body identifying the user as userId is silently dropped and the provision runs with no target user, so this tool seeds the PascalCase key from its typed parameter.

ParamTypeRequiredDefaultDescription
fieldsJsonstringnonullForward-compatibility escape hatch: a JSON object whose properties are merged into the request body LAST, overriding the typed parameters above. Only 'UserPrincipalName' and 'tenantFilter' are read by CIPP. Keys are passed verbatim — caller is responsible for exact spec casing.
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Use cipp_list_tenants to discover available tenants. Sent as body key 'tenantFilter' (camelCase).
userPrincipalNamestringyesUPN of the user whose OneDrive should be provisioned (e.g., user@contoso.com). Sent as the spec key 'UserPrincipalName' — PASCALCASE, which is the only casing CIPP reads. Use cipp_list_users to confirm the UPN.

[CIPP] Create a OneDrive shortcut for a user to a SharePoint site via POST /api/ExecOneDriveShortCut. CIPP reads exactly four body keys — `tenantFilter`, `username`, `userid` (ALL LOWERCASE — a camelCase `userId` is never read) and `siteUrl` — and forwards them unvalidated to New-CIPPOneDriveShortCut. TWO TRAPS. (1) `username` is THE identifier: the helper builds its Graph call as POST /beta/users//drive/root/children, so the UPN in `username` is what the shortcut is created for. `userid` is logged in the helper's opening message and NEVER referenced again — it cannot target the user on its own, which is why this tool requires username and treats userid as optional context. (2) `siteUrl` must be an OBJECT — it is dereferenced as `$Request.Body.siteUrl.value`, so a plain STRING resolves to $null; CIPP then matches no SharePoint site and fails loudly with HTTP 500 'Could not add OneDrive shortcut to <user> : Could not find a SharePoint site matching URL:'. Nothing is created. This tool always wraps the typed siteUrl into {label,value} for you. There is NO `libraryName` key — CIPP never reads one, so a library cannot be selected by name; pass the full site/library URL in siteUrl instead.

ParamTypeRequiredDefaultDescription
fieldsJsonstringnonullForward-compatibility escape hatch: a JSON object whose properties are merged into the request body LAST, overriding the typed parameters above. Only 'tenantFilter', 'username', 'userid' and 'siteUrl' are read by CIPP. Keys are passed verbatim — caller is responsible for exact spec casing.
siteUrlstringyesFull URL of the SharePoint site/library to point the shortcut at. The tool wraps it into CIPP's required object shape {"label":<url>,"value":<url>} because CIPP dereferences siteUrl.value; a bare string produces a null target URL and an HTTP 500 site-not-found failure.
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Use cipp_list_tenants to discover available tenants. Sent as body key 'tenantFilter'.
userIdstringnonullObject ID (GUID) of the same user. Sent as the spec key 'userid' — ALL LOWERCASE, the only casing CIPP reads. LOG-ONLY: the helper writes it to its opening log line and never uses it again, so it cannot substitute for username.
usernamestringyesUPN of the user who receives the shortcut (e.g., user@contoso.com). Sent as the spec key 'username' (lowercase). THIS is the identifier that reaches Graph — CIPP builds POST /beta/users//drive/root/children from it. Use cipp_list_users to confirm the UPN.

[CIPP] Set or unset the password-never-expires flag for a user account via POST /api/ExecPasswordNeverExpires. Body keys per spec (all camelCase): 'tenantFilter' (required), 'userId', 'userPrincipalName', 'PasswordPolicy' (PascalCase, controls the policy applied — typically 'DisablePasswordExpiration' or empty/null to re-enable expiration). Use fieldsJson to populate the body.

ParamTypeRequiredDefaultDescription
fieldsJsonstringyesConfiguration as JSON object merged LAST into the request body. Spec body keys (exact casing): 'userId' (camelCase, user object ID); 'userPrincipalName' (camelCase, optional UPN); 'PasswordPolicy' (PascalCase string — typically 'DisablePasswordExpiration' to enable never-expires, or empty to revert). Keys are passed verbatim — caller is responsible for exact spec casing.
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Use cipp_list_tenants to discover available tenants. Sent as body key 'tenantFilter'.

[CIPP] Apply partial updates to a user record via PATCH /api/PatchUser. The body schema is NOT open: CIPP normalizes the body to a list of user entries and hard-validates every one of them with `[string]::IsNullOrWhiteSpace(\(_.id) -or [string]::IsNullOrWhiteSpace(\)_.tenantFilter)`, answering HTTP 400 'Failed to patch user(s). Some users are missing id or tenantFilter' if either is blank or absent. Both are therefore REQUIRED, and this tool seeds them from typed parameters. `userPrincipalName` is NOT an accepted identifier — only `id` is. Everything else you pass is forwarded to Microsoft Graph as a user PATCH, EXCEPT four keys stripped by `Select-Object -ExcludeProperty id, tenantFilter, manager, sponsor`: id and tenantFilter are routing-only and never reach Graph, while `manager` and `sponsor` are pulled out and applied separately through CIPP's Set-CIPPManager / Set-CIPPSponsor helpers rather than being passed through as Graph properties. This tool sends ONE user object per call. NO-OP WARNING: after the strip, an entry with no remaining properties is SKIPPED and CIPP still answers HTTP 200. Since this tool always seeds exactly id + tenantFilter and both are stripped, an empty fieldsJson issues NO Graph PATCH at all and answers 'Successfully patched 0 users across 1 tenant' — note the singular 'tenant', CIPP pluralizes both nouns off their own counts. A body carrying only manager/sponsor likewise issues no Graph PATCH, but takes a different branch and answers the 'Successfully updated N manager assignment(s) across 1 tenant' variant instead. Read the count in Results, not just the 200.

ParamTypeRequiredDefaultDescription
fieldsJsonstringyesProperties to update, as a JSON object merged LAST into the user entry (overriding the typed parameters above). Any user property Graph's PATCH /users/ accepts — e.g. {"jobTitle":"Engineer","department":"R&D"}. Two keys behave specially: 'manager' and 'sponsor' are stripped from the Graph body and applied through CIPP's dedicated Set-CIPPManager / Set-CIPPSponsor helpers instead. Keys are passed verbatim — caller is responsible for exact spec casing.
idstringyesObject ID (GUID) of the user to patch. Sent as the body key 'id' (lowercase), which CIPP hard-requires per user entry (HTTP 400 without it). 'userPrincipalName' is NOT accepted as an identifier here. Routing-only — CIPP strips it before the Graph PATCH. Use cipp_list_users to find valid IDs.
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Use cipp_list_tenants to discover available tenants. Sent as the body key 'tenantFilter', which CIPP hard-requires per user entry (HTTP 400 without it). It is routing-only — CIPP strips it before the Graph PATCH.

[CIPP] Enable, disable, or enforce per-user (legacy) MFA for a specific user via POST /api/ExecPerUserMFA. Note: Conditional Access policies are the preferred MFA approach in modern tenants. IDENTIFIER CONTRACT — `userPrincipalName` is THE identifier, not a nicety: CIPP resolves the target as `$Request.Body.userPrincipalName -match '#EXT#' ? $Request.Body.userId : $Request.Body.userPrincipalName`, so `userId` is consulted ONLY for guest accounts whose UPN contains '#EXT#'. A body carrying only userId hands the downstream helper a null user for every non-guest, which is why this tool requires userPrincipalName and treats userId as the guest-only fallback. STATE — read as `$Request.Body.State.value ? $Request.Body.State.value : $Request.Body.State`, so a plain string works (this tool sends one). Set-CIPPPerUserMFA validates it against exactly 'enabled' | 'disabled' | 'enforced' and forwards it to Graph as `perUserMFAstate`. The helper's own `$State = 'enabled'` default is UNREACHABLE from this endpoint — the entrypoint always splats a State key, and an explicitly-passed null never falls back to a parameter default — so an omitted state is a hard failure (parameter-binding error surfaced as HTTP 500), not a silent enable. This tool requires an explicit state and refuses anything outside the three literals.

ParamTypeRequiredDefaultDescription
fieldsJsonstringnonullForward-compatibility escape hatch: a JSON object whose properties are merged into the request body LAST, overriding the typed parameters above. One key is re-validated on the MERGED body: 'State', which may NOT be overridden here — an override that disagrees with the typed 'state' parameter, or a second, differently-cased spelling of it, is refused before dispatch, because 'disabled' TURNS OFF the user's MFA and is the opposite of the reviewed request. Keys are passed verbatim — caller is responsible for exact spec casing.
statestringyesDesired per-user MFA state. Accepted values (case-insensitive): 'enabled', 'disabled', 'enforced' — normalized to the lowercase literals Set-CIPPPerUserMFA validates and sent as the spec key 'State'. Any other value is refused before the call is made. Required: upstream always forwards this key, so omitting it fails at CIPP's parameter validation (HTTP 500) rather than defaulting.
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Use cipp_list_tenants to discover available tenants. Sent as body key 'tenantFilter'.
userIdstringnonullObject ID (GUID) of the user. Sent as the spec key 'userId'. CIPP uses this ONLY when userPrincipalName contains '#EXT#' (guest accounts); for every other user it is ignored, so supply it only for guests.
userPrincipalNamestringyesUPN of the target user (e.g., user@contoso.com). Sent as the spec key 'userPrincipalName' — this is THE identifier CIPP uses for every non-guest account. Use cipp_list_users to confirm the UPN.

[CIPP] Permanently remove a soft-deleted object from the tenant recycle bin via POST /api/RemoveDeletedObject. WARNING: this permanently purges the object; it cannot be recovered afterwards. CIPP reads `ID` (UPPERCASE — `$Request.Query.ID ?? $Request.Body.ID`) as the identifier, plus `tenantFilter`, `userPrincipalName` and `displayName`. There is NO `objectId` key and NO `objectType` key — objectType is never read anywhere in the endpoint, so the object type cannot be selected through this call, and a body naming the target as objectId is silently dropped. Use cipp_list_deleted_users to find the ID.

ParamTypeRequiredDefaultDescription
displayNamestringnonullDisplay name of the deleted object. Sent as the spec key 'displayName' (camelCase); CIPP reads it for audit/notification and it does not identify the target.
fieldsJsonstringnonullForward-compatibility escape hatch: a JSON object whose properties are merged into the request body LAST, overriding the typed parameters above. Keys are passed verbatim — caller is responsible for exact spec casing.
idstringyesObject ID (GUID) of the soft-deleted object to purge. Sent as the spec key 'ID' — UPPERCASE, the only casing CIPP reads. Use cipp_list_deleted_users to find valid IDs.
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Use cipp_list_tenants to discover available tenants. Sent as body key 'tenantFilter'.
userPrincipalNamestringnonullUPN of the deleted object, when it is a user. Sent as the spec key 'userPrincipalName' (camelCase); CIPP reads it for audit/notification and it does not identify the target.

[CIPP] Delete a Just-In-Time (JIT) admin template via POST /api/RemoveJITAdminTemplate. Body keys per spec: 'ID' (PascalCase, the template ID — required). NOTE: this endpoint does NOT take a tenantFilter (templates are partner-scope, not tenant-scope). Use cipp_list_jit_admin_templates to find the template ID before removing.

ParamTypeRequiredDefaultDescription
idstringyesID of the JIT admin template to delete. Sent as the spec key 'ID' (PascalCase). Use cipp_list_jit_admin_templates to find available template IDs.

[CIPP] Remove an entry from a user's trusted or blocked senders list via POST /api/RemoveTrustedBlockedSender. CIPP reads exactly four body keys: `tenantFilter`, `userPrincipalName` (the mailbox to edit), `value` (the sender address or domain to remove), and `typeProperty` (which Set-MailboxJunkEmailConfiguration parameter to remove it from). NONE of 'userId', 'sender' or 'listType' is read by CIPP — a body using those names leaves all three parameters null and the call does nothing useful, which is why this tool seeds the real keys from typed parameters. Use cipp_list_user_trusted_blocked_senders to see the current entries before removing one.

ParamTypeRequiredDefaultDescription
fieldsJsonstringnonullForward-compatibility escape hatch: a JSON object whose properties are merged into the request body LAST, overriding the typed parameters above. Only 'tenantFilter', 'userPrincipalName', 'value' and 'typeProperty' are read by CIPP. Keys are passed verbatim — caller is responsible for exact spec casing.
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Use cipp_list_tenants to discover available tenants. Sent as body key 'tenantFilter'.
typePropertystringyesThe Exchange junk-email-configuration property to remove the entry from — CIPP uses this string as a PARAMETER NAME on the Set-MailboxJunkEmailConfiguration call it builds (the value becomes {'@odata.type':'#Exchange.GenericHashTable', Remove:<value>}), so the accepted vocabulary is that cmdlet's list-valued parameters (e.g. TrustedSendersAndDomains / BlockedSendersAndDomains / TrustedRecipientsAndDomains), NOT Set-Mailbox's. Sent as the spec key 'typeProperty'; CIPP does NOT read 'listType'. FORWARDED VERBATIM AND NOT VALIDATED by this tool: Remove-CIPPTrustedBlockedSender declares typeProperty as a plain [string] with no ValidateSet, so the accepted literals are not pinned in CIPP's source. Read the property name off an existing entry returned by cipp_list_user_trusted_blocked_senders rather than guessing.
userPrincipalNamestringyesUPN of the mailbox whose list is being edited (e.g., user@contoso.com). Sent as the spec key 'userPrincipalName' — CIPP does NOT read 'userId' here.
valuestringyesThe sender address or domain to remove from the list (e.g., spam@example.com). Sent as the spec key 'value' — CIPP does NOT read 'sender' here.

[CIPP] Delete a user from the tenant via POST /api/RemoveUser. The user is soft-deleted and recoverable for 30 days through cipp_restore_deleted_user. WARNING: this permanently deletes the user account. Use cipp_disable_user for temporary suspension instead. Body keys per spec: 'tenantFilter' (required), 'ID' (UPPERCASE, the user's object ID), 'userPrincipalName' (camelCase, optional UPN used for audit/notification). The same 'ID', 'tenantFilter', and 'userPrincipalName' values can also appear as query arguments per the spec.

ParamTypeRequiredDefaultDescription
fieldsJsonstringyesConfiguration as JSON object merged LAST into the request body. Spec body keys (exact casing): 'ID' (UPPERCASE, user object ID — required for the operation to target a specific user); 'userPrincipalName' (camelCase, optional UPN used for audit/notification). Keys are passed verbatim — caller is responsible for exact spec casing.
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Use cipp_list_tenants to discover available tenants. Sent as both query argument and body key 'tenantFilter'.

[CIPP] Remove a saved new-user defaults template via POST /api/RemoveUserDefaultTemplate. CIPP reads exactly ONE key — `ID` (UPPERCASE), as `$Request.Query.ID ?? $Request.Body.ID`. There is NO `templateId` key: a body naming the template as templateId leaves ID unset, and CIPP then fails the GUID sanitizer with HTTP 500 'Invalid GUID format for OData filter' rather than acting on anything. ID must be a hyphenated 8-4-4-4-12 GUID, in either case — the sanitizer matches with `-notmatch`, not `-cnotmatch`, so an uppercase GUID passes; what it rejects is a blank, brace-wrapped, or otherwise non-GUID value. A well-formed ID matching no template answers HTTP 404. `tenantFilter` is not referenced anywhere in this endpoint — templates are partner-scope, so no tenant is sent. Use cipp_list_new_user_defaults to find the template ID.

ParamTypeRequiredDefaultDescription
fieldsJsonstringnonullForward-compatibility escape hatch: a JSON object whose properties are merged into the request body LAST, overriding the typed parameter above. Only 'ID' is read by CIPP. Keys are passed verbatim — caller is responsible for exact spec casing.
idstringyesID of the new-user defaults template to delete. Sent as the spec key 'ID' — UPPERCASE, the only key CIPP reads. Must be a hyphenated 8-4-4-4-12 GUID, in either case (CIPP's sanitizer regex is applied with `-notmatch`, which is case-insensitive): CIPP pushes it through that strict GUID sanitizer before the table lookup, and a blank, brace-wrapped, or otherwise non-GUID value throws (HTTP 500). Use cipp_list_new_user_defaults to find valid IDs.

[CIPP] Reprocess license assignments for a user to fix license provisioning errors or stale service plan states, via POST /api/ExecReprocessUserLicenses. CIPP reads `ID` (UPPERCASE — `$Request.Query.ID ?? $Request.Body.ID`), `tenantFilter`, and `userPrincipalName`. Only ID reaches Graph (it builds /beta/users//reprocessLicenseAssignment); `userPrincipalName` is interpolated into the result text for logging ONLY and never identifies the user. There is NO `userId` key — a body identifying the user as userId is silently dropped and the call runs with an empty user segment, so this tool seeds the uppercase key from its typed parameter.

ParamTypeRequiredDefaultDescription
fieldsJsonstringnonullForward-compatibility escape hatch: a JSON object whose properties are merged into the request body LAST, overriding the typed parameters above. Keys are passed verbatim — caller is responsible for exact spec casing.
idstringyesObject ID (GUID) of the user whose licenses should be reprocessed. Sent as the spec key 'ID' — UPPERCASE, the only casing CIPP reads, and the only value that reaches the Graph call. Use cipp_list_users to find valid IDs.
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Use cipp_list_tenants to discover available tenants. Sent as body key 'tenantFilter'.
userPrincipalNamestringnonullUPN of the same user. Sent as the spec key 'userPrincipalName'. LOG-ONLY: CIPP interpolates it into the success/failure text and never uses it to identify the user, so it cannot substitute for id.

[CIPP] Reset MFA registration for a user via POST /api/ExecResetMFA, requiring them to re-register their authentication methods on next sign-in. The user's object ID maps to the spec key `ID` (uppercase).

ParamTypeRequiredDefaultDescription
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Use cipp_list_tenants to discover available tenants.
userIdstringyesUser object ID (GUID) or UPN of the user. Sent as the spec key 'ID' (uppercase). Use cipp_list_users to find valid IDs.

[CIPP] Reset a user's password via POST /api/ExecResetPass. CIPP generates a new random password server-side; the spec does not accept a caller-supplied password. The user's object ID maps to the spec key `ID` (uppercase). The mustChange flag (sent as `MustChange` PascalCase, string-typed per spec) controls whether the user must change the password on next sign-in.

ParamTypeRequiredDefaultDescription
additionalFieldsJsonstringnonullForward-compatibility escape hatch: a JSON object whose properties are merged into the request body LAST, overriding any of the typed parameters above. Use only for new CIPP fields that this tool does not yet expose. Keys are passed verbatim — caller is responsible for exact spec casing.
displayNamestringnonullOptional display name of the user, sent as the spec key 'displayName' (camelCase). CIPP uses it for audit/notification purposes.
mustChangebooleannonullIf true, the user must change their password on next sign-in. Sent as the spec key 'MustChange' (string-typed per spec — 'true'/'false').
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Use cipp_list_tenants to discover available tenants.
userIdstringyesUser object ID (GUID) or UPN of the user. Sent as the spec key 'ID' (uppercase). Use cipp_list_users to find valid IDs.

[CIPP] Restore a soft-deleted user from the tenant recycle bin via POST /api/ExecRestoreDeleted. Users can be restored within 30 days of deletion. The user's object ID maps to the spec key `ID` (uppercase). Use cipp_list_deleted_users to find restorable users.

ParamTypeRequiredDefaultDescription
additionalFieldsJsonstringnonullForward-compatibility escape hatch: a JSON object whose properties are merged into the request body LAST, overriding any of the typed parameters above. Use only for new CIPP fields that this tool does not yet expose. Keys are passed verbatim — caller is responsible for exact spec casing.
displayNamestringnonullOptional display name of the user, sent as the spec key 'displayName' (camelCase). CIPP uses it for audit/notification purposes.
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Use cipp_list_tenants to discover available tenants.
userIdstringyesObject ID (GUID) of the deleted user to restore. Sent as the spec key 'ID' (uppercase). Use cipp_list_deleted_users to find valid IDs.
userPrincipalNamestringnonullOptional UPN of the user, sent as the spec key 'userPrincipalName' (camelCase). CIPP uses it for audit/notification purposes.

[CIPP] Revoke all active sessions and refresh tokens for a user via POST /api/ExecRevokeSessions, forcing re-authentication on all devices. The spec uses `id` (lowercase) for the user object ID and `Username` (PascalCase) for the optional UPN. Pass userId for the GUID and optionally username for the UPN.

ParamTypeRequiredDefaultDescription
additionalFieldsJsonstringnonullForward-compatibility escape hatch: a JSON object whose properties are merged into the request body LAST, overriding any of the typed parameters above. Use only for new CIPP fields that this tool does not yet expose. Keys are passed verbatim — caller is responsible for exact spec casing.
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Use cipp_list_tenants to discover available tenants.
userIdstringyesUser object ID (GUID) of the user. Sent as the spec key 'id' (lowercase). Use cipp_list_users to find valid IDs.
usernamestringnonullOptional UPN of the user. Sent as the spec key 'Username' (PascalCase). CIPP uses it for audit/notification purposes.

[CIPP] Send a test push notification to a user's registered Microsoft Authenticator MFA device via POST /api/ExecSendPush. Body keys per spec (PascalCase): 'TenantFilter' (required, PascalCase!) and 'UserEmail' (PascalCase, the UPN of the target user). NOTE: this endpoint's body uses PascalCase 'TenantFilter' while the query argument added by the client is camelCase 'tenantFilter' — the tool merges fieldsJson so the caller can override with the correct key.

ParamTypeRequiredDefaultDescription
fieldsJsonstringyesPush configuration as JSON object merged LAST into the request body. Spec body keys (exact casing): 'TenantFilter' (PascalCase, required by spec), 'UserEmail' (PascalCase, the UPN of the user receiving the test push). Keys are passed verbatim — caller is responsible for exact spec casing.
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Use cipp_list_tenants to discover available tenants. Sent as the camelCase query argument; the tool also seeds it into the body as 'tenantFilter' but the spec actually expects 'TenantFilter' (PascalCase) — supply via fieldsJson if CIPP rejects the camelCase variant.

[CIPP] Set a directory object's on-premises sync behaviour via POST /api/ExecSetCloudManaged — CIPP PATCHes /beta/{users|groups|contacts}//onPremisesSyncBehavior with {"isCloudManaged": <bool>}. Besides `tenantFilter`, CIPP reads four body keys: `ID` (UPPERCASE), `type`, `isCloudManaged`, `displayName` — there is NO `userId` key, so a body naming the target as userId is silently dropped. TWO DANGEROUS DEFAULTS this tool now closes. (1) DIRECTION: CIPP computes `[System.Convert]::ToBoolean($Request.Body.isCloudManaged)`, and Convert.ToBoolean(null) is FALSE — so an ABSENT isCloudManaged means 'on-premises managed', the exact OPPOSITE of converting an object to cloud-managed. This tool always sends a real JSON boolean, defaulting to true. (2) TARGET: Set-CIPPCloudManaged declares `[ValidateSet('User','Group','Contact')][string]$Type` and picks its Graph URI from a switch over those three with no default arm. An absent or unrecognized type is rejected at parameter binding before the switch runs, so the call fails with HTTP 500 rather than acting — this tool defaults type to 'User', normalizes the casing, and refuses anything outside that set. That matters because fieldsJson merges LAST and can override `type`. Despite the upstream variable being named $GroupID, the endpoint serves users, groups and contacts alike — the type key is what selects which.

ParamTypeRequiredDefaultDescription
displayNamestringnonullDisplay name of the object. Sent as the spec key 'displayName'; CIPP reads it for audit/notification and it does not identify the target.
fieldsJsonstringnonullForward-compatibility escape hatch: a JSON object whose properties are merged into the request body LAST, overriding the typed parameters above — EXCEPT the three that decide what this call does. 'type' (which Graph collection is PATCHed), 'ID' (which object) and 'isCloudManaged' (which DIRECTION the sync authority moves) are re-validated on the MERGED body and may not be changed here; an override that disagrees with the typed parameter that seeded one of them, or a second differently-cased spelling of the key, is refused before dispatch. That is what makes the typed validation above unbypassable — without it a reviewed 'sever the on-premises anchor' could ship as its opposite, since CIPP coerces isCloudManaged with [System.Convert]::ToBoolean where the STRING "false" is TRUE. 'displayName' and any new CIPP field keep their last-wins behavior. Keys are passed verbatim — caller is responsible for exact spec casing.
idstringyesObject ID (GUID) of the user, group, or contact to change. Sent as the spec key 'ID' — UPPERCASE, the only casing CIPP reads ('userId' is never read). Use cipp_list_users to find valid IDs.
isCloudManagedbooleannotrueTarget sync authority, sent as the spec key 'isCloudManaged' as a REAL JSON boolean. true = cloud-managed (severs the on-premises sync anchor — the usual intent). false = on-premises managed (hands authority BACK to AD Connect). Defaults to true; the key is always emitted, because omitting it makes CIPP convert null to false and silently apply the opposite direction.
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Use cipp_list_tenants to discover available tenants. Sent as body key 'tenantFilter'.
typestringno"User"Which directory object type the ID refers to, selecting the Graph endpoint. Accepted values (case-insensitive): 'User', 'Group', 'Contact' — normalized to those exact literals and sent as the spec key 'type'. Defaults to 'User'. Any other value is refused before the call is made, because CIPP declares a ValidateSet over exactly those three and rejects anything else at parameter binding (HTTP 500).

[CIPP] Set or remove the profile photo for a user via POST /api/ExecSetUserPhoto. Body keys (all camelCase, each also accepted as a query argument except photoData): 'tenantFilter', 'userId' (the user's object ID or UPN), 'action', 'photoData' (base64-encoded image bytes, read from the BODY only and required when action='set'). ACTION VALUES — CIPP branches on exactly `if ($action -eq 'remove')` / `elseif ($action -eq 'set')` and otherwise hits `throw "Invalid action. Must be 'set' or 'remove'"`. The only two accepted values are therefore 'set' and 'remove'; 'upload' and 'delete' are NOT accepted and every such call throws. This tool maps the legacy 'upload'/'delete' spellings onto 'set'/'remove' and refuses anything else before dispatch.

ParamTypeRequiredDefaultDescription
actionstringyesWhat to do with the photo. Accepted values (case-insensitive): 'set' or 'remove' — the only two CIPP recognises. 'upload' is accepted here as an alias for 'set' and 'delete' as an alias for 'remove'; both are normalized before sending, because CIPP itself throws "Invalid action. Must be 'set' or 'remove'" on either. Any other value is refused before the call is made.
fieldsJsonstringnonullForward-compatibility escape hatch: a JSON object whose properties are merged into the request body LAST, overriding the typed parameters above. One key is re-validated on the MERGED body: 'action', which may NOT be overridden here — an override that disagrees with the typed 'action' parameter, or a second, differently-cased spelling of it, is refused before dispatch, because 'remove' DELETES the existing photo while 'set' uploads one. Keys are passed verbatim — caller is responsible for exact spec casing.
photoDatastringnonullBase64-encoded image bytes. Sent as the spec key 'photoData', which CIPP reads from the BODY only (no query fallback). Required when action resolves to 'set'; ignored for 'remove'.
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Use cipp_list_tenants to discover available tenants. Sent as body key 'tenantFilter'.
userIdstringyesObject ID (GUID) or UPN of the user. Sent as the spec key 'userId' (camelCase). Use cipp_list_users to find valid IDs.

Groups

ToolPlanAccessSummary
cipp_add_groupProWriteCreate a new group in the tenant via POST /api/AddGroup.
cipp_add_group_templateProDestructiveSave a group template for reuse via POST /api/AddGroupTemplate.
cipp_convert_group_to_teamProDestructiveConvert an existing M365 group into a Microsoft Teams team via POST /api/AddGroupTeam.
cipp_delete_groupProDestructiveDelete a group from a tenant via POST /api/ExecGroupsDelete.
cipp_edit_groupProWriteEdit properties of an existing group via PATCH /api/EditGroup.
cipp_group_delivery_managementProDestructiveSet whether a distribution or Microsoft 365 group accepts mail only from internal senders via POST /api/ExecGroupsDeliveryManagement (it writes RequireSenderAuthenticationEnabled).
cipp_group_hide_from_galProWriteShow or hide a group in the Global Address List via POST /api/ExecGroupsHideFromGAL (it writes HiddenFromAddressListsEnabled).
cipp_list_group_sender_authFreeRead-onlyReport whether external senders may email ONE group, via GET /api/ListGroupSenderAuthentication.
cipp_list_group_templatesFreeRead-onlyList saved group templates.
cipp_list_groupsFreeRead-onlyList all groups in a tenant including security groups, distribution lists, M365 groups, and mail-enabled security groups.
cipp_list_rolesFreeRead-onlyList all directory roles in a tenant including role name, description, and assigned members.
cipp_remove_group_templateProDestructiveRemove a saved group template via POST /api/RemoveGroupTemplate.

[CIPP] Create a new group in the tenant via POST /api/AddGroup. Upstream normalizes 'groupType' with a case-insensitive wildcard switch into exactly seven kinds, and the kind decides which API creates the group. Graph POST /groups: 'generic' (plain Entra security group — securityEnabled, NOT mail-enabled), 'azurerole' (role-assignable), 'dynamic' (membershipRule), 'm365' (Unified). Exchange Online: 'distribution' (New-DistributionGroup), 'dynamicdistribution' (New-DynamicDistributionGroup), and 'security'. TRAP: 'security' does NOT create a plain security group — it goes through New-DistributionGroup -Type Security, i.e. a MAIL-ENABLED security group; the plain Entra security group is 'generic'. Kinds needing an address ('m365', 'distribution', 'dynamicdistribution', 'security') build it as username@primDomain.value, or take 'primaryEmailAddress' verbatim. Optional body keys upstream actually reads: allowExternal, aliases, description, disableNesting, hideFromGAL, licenses, members, membershipRules, owners, primaryEmailAddress, primDomain, subscribeMembers, username. CIPP caches creations for 10 minutes per tenant+displayName (cache RowKey = '<tenant>_<displayName with every character other than a letter, digit or hyphen replaced by _>') and SKIPS a repeat instead of creating a second group — but the response does NOT say so: the endpoint answers 'Successfully created group <displayName> for <tenant>' either way, so a de-duplicated call is indistinguishable on the wire from a real creation. Confirm with cipp_list_groups if the distinction matters.

ParamTypeRequiredDefaultDescription
displayNamestringyesDisplay name for the new group. Seeded onto the body as 'displayName'.
fieldsJsonstringnonullOptional fields as a JSON object merged into the request body LAST (overriding the typed parameters above). Keys upstream reads, with their real shapes: 'description' (string); 'username' (string, the mail nickname / address local part) and 'primDomain' (object {"label":"<domain>","value":"<domain>"} — only '.value' is read) which together build the address for m365/distribution/dynamicdistribution/security; 'primaryEmailAddress' (string, used instead when primDomain.value is absent); 'members' and 'owners' (arrays of UPN strings OR of {"value":"<upn>"} objects — members are SKIPPED for dynamic groups); 'membershipRules' (string rule expression); 'licenses' (array of SKU ids as strings or {"value":"<skuId>"} objects — applied to 'generic' groups ONLY); 'aliases' (a SINGLE newline-delimited STRING, one alias per line, not an array — applied on 'distribution' and 'security' ONLY, never on 'dynamicdistribution'); 'hideFromGAL' (truthy → hidden; 'distribution' and 'security' ONLY, never 'dynamicdistribution'); 'disableNesting' (JSON boolean true; Graph path only); 'subscribeMembers' (truthy; meaningful only on 'm365' — but NOT ignored on the other Graph kinds: it fires Set-UnifiedGroup for 'generic'/'azurerole'/'dynamic' as well, and that failure makes the endpoint answer 'Failed to create group ...' at HTTP 500 for a group it HAS already created); 'allowExternal' — send a REAL JSON boolean: on 'distribution'/'security' upstream computes RequireSenderAuthenticationEnabled = ![bool]allowExternal, and in PowerShell the STRING "false" is truthy, so "false" is read as allowExternal=true and silently allows external senders; on 'dynamicdistribution' the Set is gated on `allowExternal -eq $true`, so only a true value writes anything — false or absent leaves Exchange's default in place. TRAP: a 'dynamicdistribution' group is built from displayName, membershipRules and the computed address ALONE — 'description', 'members', 'owners', 'aliases' and 'hideFromGAL' are never read on that kind. Keys are passed verbatim; casing is not load-bearing (CIPP reads the body case-insensitively).
groupTypestringyesGroup kind, passed to CIPP verbatim and normalized there case-insensitively. Accepted: 'generic' (plain Entra security group), 'azurerole', 'dynamic', 'dynamicdistribution', 'm365', 'distribution', 'security' (MAIL-ENABLED security group — not a plain security group), plus the two legacy aliases 'DynamicMembership' (= dynamic) and 'Mail-Enabled Security' (= security). Any other value is refused here before dispatch. Note: supplying 'membershipRules' turns a Graph-path group into a dynamic one regardless of this value.
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Seeded onto the body as 'tenantFilter' and also sent as the 'tenantFilter' query argument. Use cipp_list_tenants to discover available tenants.

[CIPP] Save a group template for reuse via POST /api/AddGroupTemplate. The body has NO 'tenantFilter' — templates are tenant-agnostic. 'displayName' and 'groupType' are BOTH required: displayName is validated explicitly (upstream throws 'You must enter a displayname'), and groupType is required implicitly because upstream calls .ToLower() on it unguarded — omit it and the call answers HTTP 200 with Results = 'Group Template Creation failed: You cannot call a method on a null-valued expression.'. CIPP reads the body case-insensitively, so displayName / displayname / Displayname are the SAME key and none of them is case-critical. The stored template keeps exactly: displayName, description, groupType, membershipRules, allowExternal, username, licenses, aliases, hideFromGAL and GUID — anything else you send (notably 'subscribeMembers', 'members', 'owners', 'primDomain') is silently DROPPED, so a template can never carry it. groupType is normalized upstream to one of 'generic' | 'security' | 'azureRole' | 'dynamic' | 'dynamicDistribution' | 'm365' | 'distribution'; a value matching none of the wildcard arms is stored VERBATIM rather than normalized, and New-CIPPGroup's own default arm would later send that stored value down the Exchange distribution path. Note that supplying a non-empty membershipRules FORCES groupType to 'dynamic' unless it resolved to 'dynamicDistribution'. 'GUID' is optional and defaults to a fresh one — passing an EXISTING template's GUID overwrites that template in place rather than adding one. Failures are returned at HTTP 200 as a 'Group Template Creation failed: ...' Results string, so read Results rather than the status code.

ParamTypeRequiredDefaultDescription
fieldsJsonstringyesRequired JSON object sent as the request body. Keys upstream persists: 'displayName' (string, REQUIRED — CIPP throws 'You must enter a displayname' without it; casing is not load-bearing), 'description' (string), 'groupType' (string, REQUIRED — upstream calls .ToLower() on it with no guard, so omitting it fails the template creation inside an HTTP 200; see the tool description for the normalized set), 'membershipRules' (string rule expression — a non-empty value forces the template to 'dynamic'; the literal 'membershipRule' is treated as empty), 'allowExternal' (boolean), 'username' (string, may contain variables such as @%tenantfilter%), 'licenses' (array of SKU ids), 'aliases' (a newline-delimited string, one alias per line, variables allowed), 'hideFromGAL' (boolean), 'GUID' (string — omit for a new template; supplying an existing GUID OVERWRITES that template). 'subscribeMembers' and any other key are accepted by the endpoint but never stored. NO 'tenantFilter'.

[CIPP] Convert an existing M365 group into a Microsoft Teams team via POST /api/AddGroupTeam. The group must be an M365 (Unified) group. CIPP PUTs the TeamSettings value straight to https://graph.microsoft.com/beta/groups/{GroupId}/team as the request body, so TeamSettings must be a JSON OBJECT — omit it and CIPP supplies its own default object (memberSettings.allowCreatePrivateChannels/allowCreateUpdateChannels, messagingSettings.allowUserEditMessages/allowUserDeleteMessages, funSettings.allowGiphy/giphyContentRating='strict'). Body keys read: TenantFilter, GroupId, TeamSettings (CIPP reads the body case-insensitively). A group created less than ~15 minutes ago commonly fails with a replication-delay 404 that CIPP reports as 'The group may have been created too recently' — retry later rather than recreating the group. This endpoint ALWAYS answers HTTP 200: every failure, the replication 404 included, arrives only as a text line inside Results, so read Results rather than the status code.

ParamTypeRequiredDefaultDescription
additionalFieldsJsonstringnonullForward-compatibility escape hatch: a JSON object whose properties are merged into the request body LAST, overriding any of the typed parameters above. Use only for new CIPP fields that this tool does not yet expose. Keys are passed verbatim — caller is responsible for exact spec casing.
groupIdstringyesM365 group ID to convert to a Team. Seeded onto the body as 'GroupId'. Use cipp_list_groups to find valid M365 group IDs.
teamSettingsstringnonullOptional Teams settings as a JSON OBJECT (passed as JSON text, e.g. {"memberSettings":{"allowCreateUpdateChannels":true},"funSettings":{"giphyContentRating":"strict"}}). It is parsed and placed on the body as a real JSON object under 'TeamSettings', because CIPP hands the value to Graph as the team PUT body — a bare JSON string would serialize as a string literal and Graph would reject it. Anything that is not a JSON object is refused before dispatch. Omit to accept CIPP's own default settings object.
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Sent as the 'tenantFilter' query argument AND seeded onto the body as 'TenantFilter'. Use cipp_list_tenants to discover available tenants.

[CIPP] Delete a group from a tenant via POST /api/ExecGroupsDelete. WARNING: this permanently deletes the group; for M365 groups Microsoft also removes the associated SharePoint site, Teams team and mailbox. groupType is a CLOSED vocabulary — 'Distribution List' and 'Mail-Enabled Security' are removed with Remove-DistributionGroup (BypassSecurityGroupManagerCheck), 'Microsoft 365' and 'Security' with a Graph DELETE (a 'Security' group has its assigned licenses stripped first). WARNING: upstream matches that vocabulary with a plain if/elseif and has NO else — any other value deletes NOTHING, raises no error and still answers HTTP 200 with a null result, which is indistinguishable from a real deletion. That is why an unrecognized groupType is refused here before dispatch. Body keys: tenantFilter, GroupType, id, displayName (CIPP reads the body case-insensitively; only tenantFilter is also on the query string).

ParamTypeRequiredDefaultDescription
displayNamestringnonullOptional group display name. Seeded onto the body as 'displayName'; upstream uses it only for the audit log line and the returned message text.
fieldsJsonstringnonullOptional forward-compatibility escape hatch: a JSON object merged into the request body LAST, overriding the typed parameters above. Keys are passed verbatim.
groupTypestringyesGroup kind — one of exactly 'Distribution List', 'Mail-Enabled Security', 'Microsoft 365', 'Security' (matched case-insensitively, then sent in that canonical spelling as body key 'GroupType'). This is the calculatedGroupType value cipp_list_groups returns. Any other value is refused before dispatch because upstream would silently delete nothing and still report success.
idstringyesGroup ID (object id for Graph kinds, identity for Exchange kinds). Seeded onto the body as 'id'. Use cipp_list_groups to find it.
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Sent as the 'tenantFilter' query argument AND seeded onto the body as 'tenantFilter'. Use cipp_list_tenants to discover available tenants.

[CIPP] Edit properties of an existing group via PATCH /api/EditGroup. Required body key: 'tenantFilter'. Key casing is NOT load-bearing — CIPP re-serializes the request so every body lookup is case-insensitive — but the spellings below are CIPP's own. Membership change keys — 'AddContact', 'AddMember', 'AddOwner', 'RemoveContact', 'RemoveMember', 'RemoveOwner', and 'AddDevice' — are arrays of OBJECTS carrying {"value":"<directory object id>"}, NOT arrays of UPN strings; ONLY 'AddMember' also accepts a bare UPN string, which CIPP resolves to an object id with a Graph lookup. 'AddLicenses' / 'RemoveLicenses' (arrays of {"value":"<skuId>"} objects or bare sku-id strings) are processed ONLY when the group resolves to 'Security'. Other body keys: 'allowExternal' (boolean — REQUIRES 'mail' in the same body), 'description' (string), 'displayName' (string), 'groupId' (object, the target group identifier), 'groupName' (string), 'groupType' (string — advisory: CIPP re-derives the kind from a live Graph lookup of the group), 'hideFromOutlookClients' (boolean), 'mail' (string), 'mailNickname' (string), 'membershipRules' (string), 'securityEnabled' (boolean — written only alongside displayName / description / mailNickname / membershipRules), 'sendCopies' (boolean), 'visibility' (string). Read Results, not the status code — and read each line's TEXT, never its prefix: every per-operation failure rides inside HTTP 200, most as an 'Error - ...' line, but the allowExternal, visibility, sendCopies and hideFromOutlookClients stages and the RemoveContact refusal report a bare 'Failed to ...' / 'You cannot ...' line with no prefix, and several successes carry no 'Success - ' prefix either, so never gate on the prefix. A body that matches no block writes nothing and still answers 200 with an empty Results array. 'tenantId' (string) is ALSO a body key but it is NOT a group property: it is a tenant-routing selector — upstream reads `tenantId ?? tenantFilter` for the member/device/owner/contact work, the licence assignment, allowExternal, visibility, sendCopies and hideFromOutlookClients, and for BOTH bulk executions themselves, while only the initial group lookup and the Graph property PATCH read tenantFilter. Leave it unset; the tool seeds tenantFilter so both reads agree.

ParamTypeRequiredDefaultDescription
fieldsJsonstringyesRequired JSON object merged into the request body, and it must carry at least one actionable key: an EMPTY object () matches no block upstream, so the call writes nothing and still returns HTTP 200 with an empty Results array. Spec-listed body keys. Membership changes — 'AddContact', 'AddMember', 'AddOwner', 'RemoveContact', 'RemoveMember', 'RemoveOwner' — are arrays of OBJECTS, not of UPN strings: each element must carry {"value":"<directory object id>"} (an {"addedFields":{"userPrincipalName":"..."}} label may accompany it). ONLY 'AddMember' additionally accepts a bare UPN string, which CIPP resolves to an object id with a Graph lookup; a bare string in any of the other five resolves to null, the Graph/Exchange call is built against an empty id, and the failure is reported as an 'Error - ...' line inside HTTP 200. 'AddDevice' takes the same object shape — {"value":"<device object id>"} or {"addedFields":{"azureADDeviceId":"..."}}, which CIPP resolves through the devices alternate key — and is refused with an error line on 'Distribution List'/'Mail-Enabled Security'. 'AddLicenses' / 'RemoveLicenses' are arrays of {"value":"<skuId>"} objects or bare sku-id strings, processed ONLY when the group resolves to 'Security'. Properties — 'allowExternal' (boolean; REQUIRES 'mail' in the same body, because CIPP identifies the group for this one operation by its mail address rather than by groupId: without 'mail' it answers 'Failed to allow/block external senders for .' inside HTTP 200, and the whole block is skipped when the group resolves to 'Security'), 'description' (string), 'displayName' (string), 'groupName' (string), 'groupType' (string; advisory only — CIPP re-derives the kind from a live Graph lookup of the group and overrides the posted value whenever that lookup succeeds), 'hideFromOutlookClients' (boolean), 'mail' (string), 'mailNickname' (string), 'membershipRules' (string), 'securityEnabled' (boolean; applied ONLY alongside one of displayName / description / mailNickname / membershipRules — the Graph property PATCH that carries it is gated on those four keys, so a securityEnabled-only edit writes NOTHING and still returns HTTP 200 with an empty Results array), 'sendCopies' (boolean), 'visibility' (string). 'tenantId' (string) is a spec body key too, but it is NOT a group property and does not belong in that run: it is a TENANT-ROUTING SELECTOR. Upstream computes `tenantId ?? tenantFilter` and routes the member/device/owner/contact changes, the licence assignment, allowExternal, visibility, sendCopies and hideFromOutlookClients through it — and BOTH bulk executions themselves — while only the initial group lookup and the Graph property PATCH read tenantFilter, so a tenantId that resolves to a DIFFERENT tenant than tenantFilter splits one edit across two tenants, with the per-operation outcomes buried inside an HTTP 200. Leave it unset; the tool seeds tenantFilter and both reads then agree. Key casing is not load-bearing (CIPP reads the body case-insensitively); the spellings above are CIPP's own.
groupIdstringyesGroup ID to edit. Sent as 'groupId' (camelCase) per CIPP spec — note the spec types this as object, not string. Use cipp_list_groups to find valid IDs.
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Sent on the body as 'tenantFilter' (camelCase) per CIPP spec. Use cipp_list_tenants to discover available tenants.

[CIPP] Set whether a distribution or Microsoft 365 group accepts mail only from internal senders via POST /api/ExecGroupsDeliveryManagement (it writes RequireSenderAuthenticationEnabled). groupType is a CLOSED vocabulary: 'Distribution List' and 'Mail-Enabled Security' go through Set-DistributionGroup, 'Microsoft 365' through Set-UnifiedGroup, and 'Security' is REFUSED upstream with a loud error (the setting does not exist on a security group). WARNING: any value outside that vocabulary matches no branch upstream, runs no cmdlet, and still returns 'Successfully set ... group ...' at HTTP 200 — a silent no-op that reads as success; such values are therefore refused here before dispatch. onlyAllowInternal is sent as a REAL JSON boolean: upstream binds it to a [bool] parameter, and PowerShell casts ANY non-empty string to true, so the string "false" would enable sender authentication — the exact opposite of what it says. Body keys: tenantFilter, GroupType, ID, OnlyAllowInternal (CIPP reads the body case-insensitively; only tenantFilter is also on the query string).

ParamTypeRequiredDefaultDescription
fieldsJsonstringnonullOptional forward-compatibility escape hatch: a JSON object merged into the request body LAST, overriding the typed parameters above. Keys are passed verbatim. Supplying 'OnlyAllowInternal' here as a STRING re-arms the PowerShell truthiness trap described above.
groupTypestringyesGroup kind — one of exactly 'Distribution List', 'Mail-Enabled Security', 'Microsoft 365', 'Security' (matched case-insensitively, then sent in that canonical spelling as body key 'GroupType'). This is the calculatedGroupType value cipp_list_groups returns. 'Security' is accepted here but upstream refuses it with an error, because a security group has no delivery-management setting. Any other value is refused before dispatch because upstream would change nothing and still report success.
idstringyesGroup ID / identity. Seeded onto the body as 'ID'. Use cipp_list_groups to find it.
onlyAllowInternalbooleanyestrue = accept mail only from senders inside the organisation (RequireSenderAuthenticationEnabled = true); false = accept mail from inside and outside. Seeded onto the body as a real JSON boolean under 'OnlyAllowInternal' — never as a quoted string, which PowerShell would coerce to true whatever it said.
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Sent as the 'tenantFilter' query argument AND seeded onto the body as 'tenantFilter'. Use cipp_list_tenants to discover available tenants.

[CIPP] Show or hide a group in the Global Address List via POST /api/ExecGroupsHideFromGAL (it writes HiddenFromAddressListsEnabled). groupType is a CLOSED vocabulary: 'Distribution List' and 'Mail-Enabled Security' go through Set-DistributionGroup, 'Microsoft 365' through Set-UnifiedGroup, and 'Security' returns a refusal string at HTTP 200 ('cannot have this setting changed'). WARNING: any value outside that vocabulary matches no branch upstream, runs no cmdlet, and still returns 'Successfully hidden/unhidden ... from GAL' at HTTP 200 — a silent no-op that reads as success; such values are therefore refused here before dispatch. hideFromGal is sent as the string literal 'true' or 'false'. Upstream binds the value to a [string] parameter and tests `-eq 'true'` case-insensitively, so the string 'true' AND a real JSON boolean true (which binds as 'True') both HIDE, while everything else — 'yes', '1', an empty value, a JSON boolean false — SHOWS. Body keys: tenantFilter, GroupType, ID, HideFromGAL (CIPP reads the body case-insensitively; only tenantFilter is also on the query string).

ParamTypeRequiredDefaultDescription
fieldsJsonstringnonullOptional forward-compatibility escape hatch: a JSON object merged into the request body LAST, overriding the typed parameters above. Keys are passed verbatim. Supplying 'HideFromGAL' here as the string 'true' or as a JSON boolean true HIDES; every other value — 'yes', '1', an empty value, a JSON boolean false — SHOWS.
groupTypestringyesGroup kind — one of exactly 'Distribution List', 'Mail-Enabled Security', 'Microsoft 365', 'Security' (matched case-insensitively, then sent in that canonical spelling as body key 'GroupType'). This is the calculatedGroupType value cipp_list_groups returns. 'Security' is accepted here but upstream answers with a refusal message instead of changing anything. Any other value is refused before dispatch because upstream would change nothing and still report success.
hideFromGalbooleanyestrue = hide the group from the Global Address List; false = show it. Seeded onto the body as the string literal 'true' or 'false' under 'HideFromGAL' — the spelling CIPP's own UI sends, and the one upstream's case-insensitive -eq 'true' comparison matches.
idstringyesGroup ID / identity. Seeded onto the body as 'ID'. Use cipp_list_groups to find it.
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Sent as the 'tenantFilter' query argument AND seeded onto the body as 'tenantFilter'. Use cipp_list_tenants to discover available tenants.

[CIPP] Report whether external senders may email ONE group, via GET /api/ListGroupSenderAuthentication. This answers for a single named group, not a list: all three inputs are required and ride the query string — tenantFilter (query key 'TenantFilter'), groupId (query key 'groupid') and groupType (query key 'Type'). Verified against CIPP-API master @df3738d: upstream acts only on groupType 'Distribution List' (Exchange, Get-DistributionGroup) and 'Microsoft 365' (Exchange too, Get-UnifiedGroup) — both read the SAME RequireSenderAuthenticationEnabled property, which the endpoint flips into `allowedToReceiveExternal`. Every other value falls to a default arm that answers the CONSTANT {"allowedToReceiveExternal": false} without looking at the group at all, and so does the catch, so a value outside those two — or an Exchange failure — produces a fabricated answer rather than an error. Use cipp_list_groups to obtain a group id and its calculatedGroupType.

ParamTypeRequiredDefaultDescription
groupIdstringyesThe group to report on — its id or, for Exchange groups, its identity. Required. Sent as the query key 'groupid' (all lowercase on this endpoint). Use cipp_list_groups to find one.
groupTypestringyesGroup kind. Required. Sent as the query key 'Type'. Only 'Distribution List' and 'Microsoft 365' are acted on upstream; any other value returns a constant false without reading the group. This is the calculatedGroupType value cipp_list_groups returns.
tenantFilterstringyesTarget tenant domain (e.g. contoso.onmicrosoft.com). Required. Sent as the query key 'TenantFilter' (PascalCase on this endpoint).

[CIPP] List saved group templates. Returns template names, group types, and configured settings.

[CIPP] List all groups in a tenant including security groups, distribution lists, M365 groups, and mail-enabled security groups. Returns group name, type, email, and member count.

ParamTypeRequiredDefaultDescription
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Use cipp_list_tenants to discover available tenants.

[CIPP] List all directory roles in a tenant including role name, description, and assigned members. Useful for auditing admin access.

ParamTypeRequiredDefaultDescription
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Use cipp_list_tenants to discover available tenants.

[CIPP] Remove a saved group template via POST /api/RemoveGroupTemplate. Templates are tenant-agnostic, so there is no tenantFilter. The only key read is 'ID' — the template's GUID (upstream sanitizes it as a Guid before building the table filter), which is the template row's RowKey as returned by cipp_list_group_templates. This client sends NO query string at all, so the value travels in the body only; CIPP reads the body case-insensitively, so 'ID' and 'id' are the same key.

ParamTypeRequiredDefaultDescription
fieldsJsonstringyesRequired JSON object sent as the request body. The only key upstream reads is 'ID' — the template GUID from cipp_list_group_templates. Example: {"ID":"6f9619ff-8b86-d011-b42d-00c04fc964ff"}.

Mailboxes

ToolPlanAccessSummary
cipp_exec_mail_testFreeRead-onlyRun an Exchange Online mail-flow / connectivity diagnostic via GET /api/ExecMailTest.
cipp_get_calendar_permissionsFreeRead-onlyGet calendar sharing permissions for a specific user's mailbox including delegate access levels and shared calendar settings.
cipp_get_contact_permissionsFreeRead-onlyGet contacts folder sharing permissions for a specific user's mailbox.
cipp_get_mailbox_casFreeRead-onlyGet Client Access Settings (CAS) for a specific mailbox including OWA, ActiveSync, POP, IMAP, and MAPI protocol enablement status.
cipp_get_mailbox_mobile_devicesFreeRead-onlyGet mobile devices connected to a specific mailbox via ActiveSync or Outlook Mobile.
cipp_get_mailbox_permissionsFreeRead-onlyGet permission assignments for a specific mailbox including Full Access, Send As, and Send on Behalf delegates.
cipp_get_mailbox_rulesFreeRead-onlyInbox rules for EVERY mailbox in a tenant, via GET /api/ListMailboxRules.
cipp_get_oooFreeRead-onlyGet the out-of-office (automatic reply) settings for a specific user including internal/external messages and schedule.
cipp_list_exo_requestFreeRead-onlyRun a read-only Exchange Online PowerShell cmdlet via POST /api/ListExoRequest and return its raw output.
cipp_list_global_address_listFreeRead-onlyList entries in ONE tenant's Global Address List (GAL) via GET /api/ListGlobalAddressList.
cipp_list_mailbox_forwardingFreeRead-onlyList mailbox forwarding configuration across the tenant via GET /api/ListMailboxForwarding.
cipp_list_mailbox_restoresFreeRead-onlyList ONE tenant's pending and completed mailbox restore requests via GET /api/ListMailboxRestores.
cipp_list_mailboxesFreeRead-onlyList all mailboxes in a tenant including user, shared, and resource mailboxes.
cipp_list_quarantine_messageFreeRead-onlyRetrieve ONE quarantined message via GET /api/ListMailQuarantineMessage.
cipp_list_restricted_usersFreeRead-onlyList users who have been restricted from sending email due to suspected spam or compromise.
cipp_list_shared_mailbox_account_enabledFreeRead-onlyList the shared mailboxes in one tenant that still have sign-in enabled.
cipp_list_shared_mailbox_statsFreeRead-onlyList all shared mailboxes with usage statistics including size, item count, last activity date, and permission assignments.
cipp_list_user_mailbox_rulesFreeRead-onlyInbox rules for ONE mailbox, via GET /api/ListUserMailboxRules.

[CIPP] Run an Exchange Online mail-flow / connectivity diagnostic via GET /api/ExecMailTest. Per spec the endpoint accepts a single query parameter 'Action' selecting which test to run, but the current CIPP client method is parameterless — this tool calls the endpoint without arguments and CIPP runs its default test. Read-only diagnostic; no mailbox state is changed.

ParamTypeRequiredDefaultDescription
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Use cipp_list_tenants to discover available tenants.
userIdstringyesUser ID or UPN of the mailbox owner (e.g., user@contoso.com). Use cipp_list_mailboxes to find valid IDs.
ParamTypeRequiredDefaultDescription
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Use cipp_list_tenants to discover available tenants.
userIdstringyesUser ID or UPN of the mailbox owner (e.g., user@contoso.com). Use cipp_list_mailboxes to find valid IDs.
ParamTypeRequiredDefaultDescription
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Use cipp_list_tenants to discover available tenants.
userIdstringyesUser ID or UPN of the mailbox owner (e.g., user@contoso.com). Use cipp_list_mailboxes to find valid IDs.

[CIPP] Get mobile devices connected to a specific mailbox via ActiveSync or Outlook Mobile. Returns device name, OS, last sync time, and access state.

ParamTypeRequiredDefaultDescription
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Use cipp_list_tenants to discover available tenants.
userIdstringyesUser ID or UPN of the mailbox owner (e.g., user@contoso.com). Use cipp_list_mailboxes to find valid IDs.
ParamTypeRequiredDefaultDescription
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Use cipp_list_tenants to discover available tenants.
userIdstringyesUser ID or UPN of the mailbox owner (e.g., user@contoso.com). Use cipp_list_mailboxes to find valid IDs.

[CIPP] Inbox rules for EVERY mailbox in a tenant, via GET /api/ListMailboxRules. This is a TENANT-WIDE cached report, not a per-mailbox lookup — for one mailbox use cipp_list_user_mailbox_rules instead. ASYNCHRONOUS by default: when CIPP holds no cached rows less than an hour old it starts a background job that walks every mailbox in the tenant and answers immediately with {"Metadata":{"QueueMessage":"...","QueueId":"..."},"Results":[]}. Poll by REPEATING THIS EXACT CALL until Results is populated; the cache then serves for an hour. Getting the SAME QueueId back on a repeat means CIPP's job is STILL RUNNING — a newly started job would carry a new id — and a tenant-wide enumeration routinely takes minutes; pass that id to cipp_get_queue_status for its task counts and status. Set useReportDB=true to read CIPP's reporting database synchronously instead, which skips the queue entirely — but note that the RESPONSE SHAPE CHANGES on that branch: it returns a BARE JSON ARRAY of rows with no 'Results' key and no 'Metadata' at all, so the polling rule above does not apply to it, and a failure there comes back as HTTP 500 rather than the 403 the cached/queued path answers with. Verified against CIPP-API master @df3738d.

ParamTypeRequiredDefaultDescription
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Use cipp_list_tenants to discover available tenants.
useReportDBbooleannonullWhen true, serve CIPP's reporting database synchronously instead of the live cached/queued path. The answer is then a BARE JSON ARRAY of rows — no 'Results' key, no 'Metadata', no QueueId to poll — and it is only as fresh as CIPP's last report run; a failure on that branch is HTTP 500, where the cached/queued path answers 403. Only true changes anything: false and omitted both give the default cached/queued behaviour.
ParamTypeRequiredDefaultDescription
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Use cipp_list_tenants to discover available tenants.
userIdstringyesUser ID or UPN of the mailbox owner (e.g., user@contoso.com). Use cipp_list_mailboxes to find valid IDs.

[CIPP] Run a read-only Exchange Online PowerShell cmdlet via POST /api/ListExoRequest and return its raw output. Lets callers query EXO data not exposed by a dedicated CIPP endpoint. THE TENANT IS BODY-ONLY: CIPP reads it from the 'TenantFilter' body key and never reads any query argument, so the tenant cannot be supplied out-of-band. Body keys (mixed casing, verified against upstream): 'TenantFilter' (PascalCase, required — the only tenant source), 'Cmdlet' (PascalCase, the EXO cmdlet name, e.g., 'Get-Mailbox', 'Get-CASMailbox'), 'cmdParams' (camelCase, a JSON OBJECT of cmdlet parameter name/value pairs that CIPP splats onto the cmdlet), 'Select' (PascalCase, comma-separated property selector), 'AsApp' (PascalCase, real JSON boolean — runs as the app identity), 'Compliance' (PascalCase, real JSON boolean — routes via the Security/Compliance endpoint instead of EXO), 'Anchor' (PascalCase, anchor mailbox identifier for cmdlet routing), 'AvailableCmdlets' (PascalCase, real JSON boolean — see the WARNING below), 'UseSystemMailbox' (PascalCase — accepted but effectively INERT upstream; forwarded only when true, with no observable effect on the cmdlet that runs). The casing split between Cmdlet (Pascal) and cmdParams (camel) is upstream's, not a typo. VERB REALITY — CIPP accepts ONLY two verbs, Get-* and Search-, and answers anything else with HTTP 400 'Invalid cmdlet: <name>'. StackJack narrows that further: only canonical Get-<Noun> is usable. Search- is refused locally on purpose (Search-Mailbox -DeleteContent purges mail). WARNING: Find-<Noun> still passes StackJack's local gate for historical reasons but NO Find-* cmdlet can ever succeed — CIPP rejects every one with HTTP 400. Do not use Find-; use Get-. Aliases, module qualifiers and shell metacharacters are refused before the request is sent. Mutating cmdlets (Set-/Remove-/Add-/New-/Disable-/Enable-) require a dedicated tool. WARNING — 'AvailableCmdlets' is a MODE SWITCH, not a hint: when true, CIPP ignores Cmdlet, cmdParams, Select, Anchor and UseSystemMailbox entirely, never runs the requested cmdlet, and returns HTTP 200 carrying a list of cmdlet NAMES. That success-shaped response is indistinguishable from a real result to a caller who did not expect it, so leave availableCmdlets unset unless you specifically want to enumerate cmdlet names.

ParamTypeRequiredDefaultDescription
additionalFieldsJsonstringnonullForward-compatibility escape hatch for NEW CIPP fields this tool does not yet expose: a JSON object whose properties are added to the request body after the typed parameters above. It ADDS fields only — it cannot override the ones the typed parameters own. Nine reserved keys are silently ignored if you supply them here: 'TenantFilter', 'Cmdlet', 'cmdParams', 'Select', 'AsApp', 'Compliance', 'Anchor', 'AvailableCmdlets' and 'UseSystemMailbox' (matched without regard to casing). Set those through the typed parameters instead — they are the validated surface: the tenant, the cmdlet and the routing/auth mode are checked before the request is sent, and a value smuggled past those checks would change what runs, not just what is returned. Every other key is passed verbatim — caller is responsible for exact spec casing.
anchorstringnonullOptional anchor mailbox identifier used by EXO cmdlet routing in some scenarios. Sent as body key 'Anchor' (PascalCase, string).
asAppbooleannonullWhen true, CIPP runs the cmdlet as the registered app identity instead of a delegated user context. Sent as body key 'AsApp' (PascalCase, real JSON boolean — upstream tests it for exactly true).
availableCmdletsbooleannonullWARNING — this is a MODE SWITCH, not a hint. Set true ONLY to enumerate available cmdlet NAMES: CIPP then ignores cmdlet, cmdParams, select, anchor and useSystemMailbox, never runs your cmdlet, and returns HTTP 200 with a list of cmdlet names — a success-shaped response that looks like a normal result. Leave unset to actually run the cmdlet. Sent as body key 'AvailableCmdlets' (PascalCase) as a real JSON boolean, and OMITTED entirely when false, because upstream tests raw truthiness: any non-empty string there — including "false" or "no" — would divert the whole call into listing mode.
cmdParamsstringnonullOptional cmdlet arguments as a JSON OBJECT of name/value pairs CIPP splats onto the cmdlet (e.g., '{"Identity":"user@contoso.com","ResultSize":100}'). Pass JSON text; this tool parses it and sends a real JSON object under body key 'cmdParams' (camelCase), because CIPP forwards the value unparsed into the Exchange CmdletInput.Parameters slot — a quoted string there is not a parameter set and the cmdlet would receive no arguments. Anything that is not a JSON object is refused before the request is sent.
cmdletstringyesExchange Online cmdlet name to run. Use a canonical Get-<Noun> form: CIPP itself accepts only Get-* and Search-, and StackJack refuses Search- (Search-Mailbox -DeleteContent purges mail). Find-<Noun> is still let through by StackJack's gate but ALWAYS fails upstream with HTTP 400 'Invalid cmdlet' — it can never work. Aliases, module qualifiers and shell metacharacters are refused. Sent as body key 'Cmdlet' (PascalCase, string).
compliancebooleannonullWhen true, CIPP routes the cmdlet through the Security/Compliance PowerShell endpoint instead of standard Exchange Online. Required for cmdlets like Get-ComplianceCase, Get-RetentionCompliancePolicy. Sent as body key 'Compliance' (PascalCase, real JSON boolean — upstream tests it for exactly true).
selectstringnonullOptional comma-separated property selector to project from the cmdlet output (e.g., 'DisplayName,PrimarySMTPAddress'). Sent as body key 'Select' (PascalCase, string).
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Sent as the 'TenantFilter' body key (PascalCase, required) — this is the ONLY place CIPP reads the tenant from; it never reads a query argument. Use cipp_list_tenants to discover available tenants.
useSystemMailboxstringnonullAccepted by CIPP but effectively INERT: CIPP forwards it to its Exchange request helper only when it equals true, and it has no observable effect on the cmdlet that runs. Do not rely on it to change routing. Sent as body key 'UseSystemMailbox' (PascalCase, string).

[CIPP] List entries in ONE tenant's Global Address List (GAL) via GET /api/ListGlobalAddressList. Returns all mail-enabled recipients visible in that organization's address book. Query: 'tenantFilter' (camelCase, REQUIRED). Verified against CIPP-API master @df3738d: with no tenant CIPP's Exchange helper substitutes the CIPP installation's own partner tenant, so a call without one returns partner data or nothing at all — never the tenant that was meant.

ParamTypeRequiredDefaultDescription
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Use cipp_list_tenants to discover available tenants.

[CIPP] List mailbox forwarding configuration across the tenant via GET /api/ListMailboxForwarding. Reports each mailbox's ForwardingAddress / ForwardingSmtpAddress and whether DeliverToMailboxAndForward is set, plus inbox-rule-driven forwarding. Critical for BEC investigation. Spec query: 'tenantFilter' (required), 'UseReportDB' (string 'true'/'false' — when 'true', CIPP serves the cached/aggregated report DB instead of querying live; default lives behavior).

ParamTypeRequiredDefaultDescription
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Sent as the 'tenantFilter' query parameter (camelCase, required per spec). Use cipp_list_tenants to discover available tenants.
useReportDBstringnonullWhen 'true', serve cached/aggregated data from CIPP's report DB instead of querying live Exchange — faster but may be stale. Sent as the 'UseReportDB' query parameter (PascalCase, STRING-typed per spec — pass 'true' or 'false').

[CIPP] List ONE tenant's pending and completed mailbox restore requests via GET /api/ListMailboxRestores. Returns restore status, source, target, and completion percentage. Query: 'tenantFilter' (camelCase, REQUIRED). Verified against CIPP-API master @df3738d: the tenant is passed straight to Exchange Online, and without one CIPP substitutes its own partner tenant, so the intended tenant is never queried.

ParamTypeRequiredDefaultDescription
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Use cipp_list_tenants to discover available tenants.

[CIPP] List all mailboxes in a tenant including user, shared, and resource mailboxes. Returns display name, primary SMTP address, mailbox type, and size.

ParamTypeRequiredDefaultDescription
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Use cipp_list_tenants to discover available tenants.

[CIPP] Retrieve ONE quarantined message via GET /api/ListMailQuarantineMessage. Returns the raw message content for the quarantined item named by identity. Query: 'tenantFilter' (camelCase) and 'Identity' (PascalCase upstream) — BOTH REQUIRED. Verified against CIPP-API master @df3738d, which runs Export-QuarantineMessage anchored on that identity; the tool sent neither value before 2026-09-03 and could not succeed under any input. Use cipp_list_quarantine to list a tenant's quarantined messages and read the identity of the one you want.

ParamTypeRequiredDefaultDescription
identitystringyesREQUIRED. The quarantined message's Identity, exactly as cipp_list_quarantine reports it. Sent as CIPP's 'Identity' (PascalCase).
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Use cipp_list_tenants to discover available tenants.

[CIPP] List users who have been restricted from sending email due to suspected spam or compromise. Use cipp_remove_restricted_user to unblock.

ParamTypeRequiredDefaultDescription
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Use cipp_list_tenants to discover available tenants.

[CIPP] List the shared mailboxes in one tenant that still have sign-in enabled. A shared mailbox with an enabled account is a security risk because it can be used for a direct login. The tenant is required: CIPP builds its Exchange and Graph reads from it and fails with a 500 when it is missing.

ParamTypeRequiredDefaultDescription
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Use cipp_list_tenants to discover available tenants.
ParamTypeRequiredDefaultDescription
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Use cipp_list_tenants to discover available tenants.

[CIPP] Inbox rules for ONE mailbox, via GET /api/ListUserMailboxRules. This is THE per-mailbox inbox-rule route and it answers SYNCHRONOUSLY — no queue and no cache: CIPP runs Get-InboxRule -IncludeHidden against the mailbox named by userId and returns its rules, hidden ones included. It then drops two built-ins from the answer, the 'Junk E-Mail Rule' and the out-of-office rules, so a count one or two short of the Exchange admin console is expected rather than a gap. Both tenantFilter and userId are REQUIRED: with no user CIPP anchors on an empty mailbox, Exchange Online answers 404 and the call comes back as HTTP 500. For the tenant-wide cached report across every mailbox, use cipp_get_mailbox_rules.

ParamTypeRequiredDefaultDescription
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Use cipp_list_tenants to discover available tenants.
userEmailstringnonullOptional. The mailbox's SMTP address, sent as CIPP's 'userEmail'. CIPP accepts the key but anchors the lookup on userId alone, so it never changes which mailbox is read — passing it is harmless and passing it instead of a correct userId does not work.
userIdstringyesREQUIRED. The mailbox to read, as a UPN (user@contoso.com) or a directory object id. Sent as CIPP's 'UserID'. Use cipp_list_mailboxes or cipp_list_users to find valid values.

Mailbox Management

ToolPlanAccessSummary
cipp_add_shared_mailboxProWriteCreate a new shared mailbox via POST /api/AddSharedMailbox.
cipp_convert_mailboxProDestructiveConvert a mailbox between types via POST /api/ExecConvertMailbox.
cipp_copy_for_sentProWriteConfigure whether items sent on behalf of a mailbox by a delegate are also copied to the mailbox owner's Sent Items folder via POST /api/ExecCopyForSent.
cipp_edit_calendar_permissionsProWriteGrant, change, or revoke a delegate's access to a mailbox's calendar folder via POST /api/ExecEditCalendarPermissions.
cipp_edit_mailbox_permissionsProDestructiveAdd or remove mailbox permissions (Full Access, Send As, Send on Behalf) on a mailbox via POST /api/ExecEditMailboxPermissions.
cipp_enable_archiveProDestructiveEnable the online archive mailbox for a user via POST /api/ExecEnableArchive.
cipp_enable_auto_expanding_archiveProDestructiveEnable auto-expanding archive for a mailbox via POST /api/ExecEnableAutoExpandingArchive.
cipp_exec_mailbox_mobile_devicesProDestructivePerform an admin action on a mailbox-attached mobile device via GET /api/ExecMailboxMobileDevices.
cipp_hide_from_galProWriteShow or hide a mailbox from the Global Address List (GAL) via POST /api/ExecHideFromGAL.
cipp_hve_userProDestructiveManage a High Volume Email (HVE) user account via POST /api/ExecHVEUser.
cipp_mailbox_restoreProDestructiveManage mailbox restore requests via POST /api/ExecMailboxRestore.
cipp_message_traceProWriteTrace email messages via POST /api/ListMessageTrace by sender, recipient, and date range.
cipp_modify_calendar_permsProWriteModify calendar folder permissions via POST /api/ExecModifyCalPerms — the cmdlet-style batch alternative to cipp_edit_calendar_permissions.
cipp_modify_contact_permsProWriteModify a mailbox owner's Contacts-folder permissions via POST /api/ExecModifyContactPerms (cmdlet-style batch).
cipp_modify_mailbox_permsProDestructiveModify mailbox-level permissions via POST /api/ExecModifyMBPerms — the cmdlet-style batch alternative to cipp_edit_mailbox_permissions.
cipp_remove_mailbox_ruleProDestructiveRemove a specific inbox rule from a mailbox via POST /api/ExecRemoveMailboxRule.
cipp_remove_restricted_userProDestructiveUnblock a user who has been restricted from sending email via POST /api/ExecRemoveRestrictedUser.
cipp_schedule_mailbox_vacationProWriteSchedule a mailbox-vacation workflow against POST /api/ExecScheduleMailboxVacation.
cipp_schedule_ooo_vacationProWriteSchedule a future out-of-office (auto-reply) window for one or more users via POST /api/ExecScheduleOOOVacation.
cipp_set_calendar_processingProDestructiveConfigure calendar processing settings for a resource mailbox (room or equipment) via POST /api/ExecSetCalendarProcessing — auto-accept, booking window, conflict resolution, processing of external…
cipp_set_email_forwardProDestructiveConfigure email forwarding for a mailbox via POST /api/ExecEmailForward.
cipp_set_litigation_holdProDestructiveEnable or disable litigation hold on a mailbox via POST /api/ExecSetLitigationHold.
cipp_set_mailbox_email_sizeProWriteSet the maximum send/receive message size for a mailbox via POST /api/ExecSetMailboxEmailSize.
cipp_set_mailbox_localeProWriteSet the language and regional settings for a mailbox via POST /api/ExecSetMailboxLocale.
cipp_set_mailbox_quotaProWriteSet ONE storage quota threshold for a mailbox via POST /api/ExecSetMailboxQuota.
cipp_set_mailbox_ruleProDestructiveEnable or disable an existing server-side inbox rule on a mailbox via POST /api/ExecSetMailboxRule.
cipp_set_oooProWriteConfigure out-of-office (automatic reply) settings for a mailbox via POST /api/ExecSetOoO with separate internal and external messages.
cipp_set_recipient_limitsProWriteSet the maximum number of recipients per outbound email message for a mailbox via POST /api/ExecSetRecipientLimits.
cipp_set_retention_holdProDestructiveEnable or disable retention hold on a mailbox via POST /api/ExecSetRetentionHold.
cipp_start_managed_folder_assistantProDestructiveStart the Managed Folder Assistant for a mailbox via POST /api/ExecStartManagedFolderAssistant to immediately process retention policies and tags instead of waiting for the next automatic cycle.

[CIPP] Create a new shared mailbox via POST /api/AddSharedMailbox. Shared mailboxes do not require a license and can be accessed by multiple users. The primary SMTP address is constructed by CIPP as username@domain. The tenant is carried by body key 'tenantID' — the ONLY tenant input this CIPP endpoint reads; the tool sends it automatically (from tenantID, or tenantFilter when tenantID is omitted). WARNING: with an unresolvable tenant value, CIPP creates NOTHING and still answers HTTP 200 with 'Successfully created shared mailbox' (upstream defect, reported) — so prefer the tenant GUID and confirm with cipp_list_mailboxes when in doubt.

ParamTypeRequiredDefaultDescription
addedAliasesstringnonullOptional additional alias addresses, NEWLINE-separated (e.g. 'helpdesk@contoso.com\nit@contoso.com'). Sent as body key 'addedAliases'. WARNING: upstream splits this value with `-split '\n'` and NEVER on commas, so a comma-separated list is handed to Set-Mailbox as a SINGLE malformed EmailAddresses entry; the alias step then fails INSIDE the HTTP 200 envelope ('Failed to add aliases to ...') while the mailbox itself is still created — read every Results entry rather than trusting the status. Verified against CIPP-API master @df3738d.
additionalFieldsJsonstringnonullForward-compatibility escape hatch: a JSON object whose properties are merged into the request body LAST, overriding any of the typed parameters above. Use only for new CIPP fields not yet exposed. Keys are passed verbatim — caller is responsible for exact spec casing.
displayNamestringyesDisplay name for the shared mailbox (e.g., 'IT Support'). Sent as body key 'displayName'.
domainstringyesDomain portion of the primary SMTP address (e.g., 'contoso.com'). Must be a verified domain in the target tenant. Sent as body key 'domain'.
tenantFilterstringyesTarget tenant (e.g., contoso.onmicrosoft.com, the tenant's default domain, or the tenant GUID). Use cipp_list_tenants to discover available tenants. Sent as the tenantFilter query argument for connector consistency AND used as the body tenantID when the tenantID parameter is omitted — CIPP's AddSharedMailbox endpoint reads the tenant ONLY from body key 'tenantID' (verified against CIPP-API source; there is no query-to-body resolution step). A vanity domain silently resolves to NO tenant, so prefer the customerId GUID from cipp_list_tenants.
tenantIDstringnonullTenant identifier placed in body key 'tenantID' — the ONLY tenant input CIPP's AddSharedMailbox endpoint reads. CIPP matches it against the tenant GUID (customerId — the safest value, from cipp_list_tenants), the tenant's default domain, or its initial .onmicrosoft.com domain; any other value (e.g. a vanity domain) resolves to NO tenant and CIPP still reports success while creating nothing. Defaults to tenantFilter when omitted.
usernamestringyesLocal part of the primary SMTP address (e.g., 'support' for support@contoso.com). Sent as body key 'username'.

[CIPP] Convert a mailbox between types via POST /api/ExecConvertMailbox. Shared mailboxes under 50GB do not require a license. Valid MailboxType values: 'UserMailbox', 'SharedMailbox', 'RoomMailbox', 'EquipmentMailbox'.

ParamTypeRequiredDefaultDescription
mailboxTypestringyesTarget mailbox type. Valid values: 'UserMailbox', 'SharedMailbox', 'RoomMailbox', 'EquipmentMailbox'. Sent as body key 'MailboxType'.
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Use cipp_list_tenants to discover available tenants. Sent as both query argument and body key 'tenantFilter'.
userIdstringyesUser ID or UPN of the mailbox to convert (e.g., user@contoso.com). Use cipp_list_mailboxes to find valid IDs. Sent as body key 'ID'.

[CIPP] Configure whether items sent on behalf of a mailbox by a delegate are also copied to the mailbox owner's Sent Items folder via POST /api/ExecCopyForSent. It is a single on/off switch — the one value sets both MessageCopyForSentAsEnabled and MessageCopyForSendOnBehalfEnabled. Body keys: 'tenantFilter' (required), 'ID' (UPPERCASE, the mailbox owner's user object ID or UPN), 'messageCopyState' (REQUIRED). WARNING — messageCopyState is NOT an 'Enabled'/'Disabled' enum: upstream runs it through [System.Convert]::ToBoolean, which accepts only a real JSON boolean or the strings 'True'/'False' (case-insensitive) and throws a FormatException on any other non-empty string. That conversion sits OUTSIDE the endpoint's try block, so 'Enabled' or 'Disabled' produces an unhandled Azure Function error, not the {"Results":...} envelope this tool otherwise returns. WARNING — OMITTING messageCopyState is not a no-op and not an error, it is an unrequested WRITE: [System.Convert]::ToBoolean(null) returns false WITHOUT throwing, so a body of {"ID":"user@contoso.com"} reaches Set-CIPPMessageCopy with MessageCopyForSentAsEnabled=false AND MessageCopyForSendOnBehalfEnabled=false — it silently turns delegate sent-item copies OFF and answers HTTP 200. Always send an explicit true/false. Verified against CIPP-API master @df3738d.

ParamTypeRequiredDefaultDescription
fieldsJsonstringyesConfiguration as JSON object merged LAST into the request body. Body keys (exact casing): 'ID' (UPPERCASE, mailbox owner's user object ID or UPN — upstream reads $Request.Query.ID ?? $Request.Body.ID, and this tool sends only the body); 'messageCopyState' is REQUIRED — pass a REAL JSON boolean (true = keep copies, false = stop), or the string 'True'/'False'. 'Enabled'/'Disabled' or any other non-empty string crashes the endpoint with an unhandled FormatException before its try block opens. OMITTING the key is the dangerous case, because it fails silently instead: upstream runs the absent value through [System.Convert]::ToBoolean, which returns false for a null WITHOUT throwing, and then writes MessageCopyForSentAsEnabled=false and MessageCopyForSendOnBehalfEnabled=false — delegate sent-item copies are switched OFF for that mailbox and the call answers HTTP 200 as though nothing happened. Keys are passed verbatim and are NOT rewritten by this tool — caller owns exact spec casing and value shape. Example: {"ID":"user@contoso.com","messageCopyState":true}
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Use cipp_list_tenants to discover available tenants. Sent as both the query argument and body key 'tenantFilter'; upstream reads the query first and falls back to the body, so either satisfies it.

[CIPP] Grant, change, or revoke a delegate's access to a mailbox's calendar folder via POST /api/ExecEditCalendarPermissions. RUNTIME CONTRACT: CIPP's live backend consumes the exact autocomplete objects its own React UI emits — it unwraps `$Request.Body.Permissions.value`, `$Request.Body.UserToGetPermissions.value`, and `$Request.Body.RemoveAccess.value`. Sending those fields as BARE STRINGS makes the backend read $null and fail with `Cannot convert value "System.String[]" to type MailboxFolderAccessRight[]`. This tool builds the backend-compatible {value,label} object shape (and a real JSON boolean for CanViewPrivateItems) from the typed parameters below, so callers pass plain strings/booleans. To GRANT or CHANGE access leave removeAccess=false and supply permissionLevel; to REVOKE access set removeAccess=true (permissionLevel/canViewPrivateItems are then ignored). Response is CIPP's raw {"Results":[...]} envelope (HTTP 200 even on per-operation failure). Use additionalFieldsJson only for forward-compatibility with new CIPP fields.

ParamTypeRequiredDefaultDescription
additionalFieldsJsonstringnonullForward-compatibility escape hatch: a JSON object whose properties are merged into the request body LAST, overriding anything the typed parameters set. Use only for new CIPP fields this tool does not yet expose. Keys passed verbatim — caller owns exact spec casing.
canViewPrivateItemsbooleannofalseEnable delegate sharing with private-item visibility ('Delegate,CanViewPrivateItems') so the shared calendar auto-appears in the delegate's Outlook and private items are visible. Only meaningful for the 'Editor' role on a grant. Sent as body key 'CanViewPrivateItems' as a JSON BOOLEAN (never a string — PowerShell treats the string "false" as $true, which would silently enable the flag). Default false.
delegateUserOrGroupstringyesThe delegate being granted or revoked — the UPN or object id of the user OR security group to give/remove calendar access (e.g., group@contoso.com). The tool wraps this into the backend's autocomplete shape automatically: on grant it becomes body key 'UserToGetPermissions' (array of {value,label}, mirroring the UI's multi-select); on revoke it becomes 'RemoveAccess' ({value,label}).
folderNamestringno"Calendar"Calendar folder name to target. Almost always 'Calendar' (the default). Sent as body key 'FolderName'.
mailboxUserIdstringyesThe mailbox OWNER whose calendar is being shared — UPN or user object id (e.g., owner@contoso.com). Sent as body key 'userid' (lowercase, per the CIPP entrypoint).
permissionLevelstringnonullOutlook calendar role to grant: 'Owner' | 'PublishingEditor' | 'Editor' | 'PublishingAuthor' | 'Author' | 'NonEditingAuthor' | 'Reviewer' | 'Contributor' | 'AvailabilityOnly' | 'LimitedDetails' | 'None'. REQUIRED when granting (removeAccess=false); ignored when removeAccess=true. Sent as body key 'Permissions' = {value,label}.
removeAccessbooleannofalseSet true to REVOKE the delegate's access instead of granting it. When true, permissionLevel and canViewPrivateItems are ignored and the delegate is sent as body key 'RemoveAccess'; the tool never emits an empty RemoveAccess (an empty value would flip a grant into a revoke in the backend). Default false = grant/change.
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Use cipp_list_tenants to discover available tenants. Sent as the tenantFilter query argument and body key.

[CIPP] Add or remove mailbox permissions (Full Access, Send As, Send on Behalf) on a mailbox via POST /api/ExecEditMailboxPermissions. NOTE: the body uses lowercase 'tenantfilter' and uppercase 'userID' — these casings are intentional and must not be normalized.

ParamTypeRequiredDefaultDescription
additionalFieldsJsonstringnonullPermission changes as JSON object using the exact CIPP bucket keys: AddFullAccess, AddFullAccessNoAutoMap, AddSendAs, AddSendOnBehalf, RemoveFullAccess, RemoveSendAs, RemoveSendOnBehalf (these seven are the complete set — there is no RemoveFullAccessNoAutoMap). SHAPE IS LOAD-BEARING: each bucket must be an array of {"value": "user@contoso.com"} OBJECTS — CIPP iterates the .value property, so plain UPN strings produce ZERO operations, an empty Results array, and HTTP 200 (a silent no-op). Correct example: {"AddFullAccess":[{"value":"tech@contoso.com"}]}. Merged into the request body LAST so it overrides typed fields.
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Use cipp_list_tenants to discover available tenants. Sent as both the tenantFilter query argument AND as body key 'tenantfilter' (lowercase, intentional).
userIdstringyesUser ID or UPN of the mailbox owner (e.g., user@contoso.com). Use cipp_list_mailboxes to find valid IDs. Sent as body key 'userID' (uppercase ID, intentional).

[CIPP] Enable the online archive mailbox for a user via POST /api/ExecEnableArchive. Archive mailboxes provide additional storage and auto-expanding archive capabilities. Provide either 'username' (UPN) OR 'id' (object ID) — supplying both is allowed but redundant.

ParamTypeRequiredDefaultDescription
additionalFieldsJsonstringnonullForward-compatibility escape hatch: a JSON object whose properties are merged into the request body LAST, overriding any of the typed parameters above. Use only for new CIPP fields that this tool does not yet expose. Keys are passed verbatim — caller is responsible for exact spec casing.
idstringnonullOptional Microsoft Graph object ID (GUID) of the user. Used when CIPP cannot resolve the user by UPN. Sent as body key 'id'.
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Use cipp_list_tenants to discover available tenants. Sent as both query argument and body key 'tenantFilter'.
usernamestringnonullUPN of the mailbox (e.g., user@contoso.com). Use cipp_list_mailboxes to find valid UPNs. Sent as body key 'username'.

[CIPP] Enable auto-expanding archive for a mailbox via POST /api/ExecEnableAutoExpandingArchive. Allows the archive mailbox to grow beyond the initial 100 GB limit by adding additional auxiliary archive mailboxes as needed. Body keys per spec: 'tenantFilter' (required), 'ID' (UPPERCASE, the user's object ID), 'username' (lowercase, the user's UPN). Provide either ID or username (or both).

ParamTypeRequiredDefaultDescription
fieldsJsonstringyesConfiguration as JSON object merged LAST into the request body. Spec body keys (exact casing): 'ID' (UPPERCASE, user object ID); 'username' (lowercase, user UPN). Provide either ID or username (or both). Keys are passed verbatim — caller is responsible for exact spec casing.
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Use cipp_list_tenants to discover available tenants. Sent as body key 'tenantFilter'.

[CIPP] Perform an admin action on a mailbox-attached mobile device via GET /api/ExecMailboxMobileDevices. Exactly one action per call, chosen with the 'action' parameter: 'Quarantine' (alias 'Block') adds the device to the mailbox's ActiveSync BLOCKED list so it stops syncing mail; 'Allow' adds it to the ActiveSync ALLOWED list; 'Delete' removes the device's ActiveSync partnership from Exchange entirely. The identifiers are NOT interchangeable — Quarantine/Allow act on 'userId' + 'deviceId' (the ActiveSync DeviceID), while Delete acts on 'guid' (the Exchange mobile-device Identity/GUID). Passing the wrong identifier silently writes a bogus entry and still reports success, so discover both values with cipp_get_mailbox_mobile_devices first. DESTRUCTIVE: 'Delete' calls Exchange Remove-MobileDevice, which removes the device's ActiveSync partnership — the device stops syncing immediately and CIPP offers no undo; the partnership has to be re-established from the device, and will typically re-create itself on the device's next connection unless that device is also blocked. This is partnership removal, NOT Exchange's Clear-MobileDevice remote-wipe cmdlet, though some device or MDM policies do drop locally cached mail when a partnership is removed. Note CIPP answers HTTP 200 even on failure, returning a 'Results' string beginning with 'Failed' — read the response body, not just the status.

ParamTypeRequiredDefaultDescription
actionstringyesWhich action to perform. One of: 'Quarantine' (or 'Block') — block the device from syncing by adding it to ActiveSyncBlockedDeviceIDs; 'Allow' — add it to ActiveSyncAllowedDeviceIDs; 'Delete' — remove the ActiveSync device partnership via Remove-MobileDevice (irreversible from CIPP). Case-insensitive. Note that CIPP only ever ADDS to the allowed/blocked list — 'Allow' does not remove a device from the blocked list, so it is not an undo for a previous 'Quarantine'.
deviceIdstringnonullThe device's ActiveSync DeviceID string. REQUIRED for 'Quarantine' and 'Allow' — this is the value added to the mailbox's allow/block list. IGNORED for 'Delete', which targets 'guid' instead; supplying it there has no effect. Sent as the all-lowercase 'deviceid' query parameter (per spec). Read it from cipp_get_mailbox_mobile_devices.
guidstringnonullThe Exchange mobile-device Identity/GUID. REQUIRED for 'Delete' — it is passed to Remove-MobileDevice as -Identity. Not used by 'Quarantine' or 'Allow'. This is a DIFFERENT value from the ActiveSync 'deviceId'; read it from cipp_get_mailbox_mobile_devices and do not substitute one for the other. Sent as the all-lowercase 'guid' query parameter (per spec).
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Sent as the required 'tenantFilter' query parameter (camelCase). Use cipp_list_tenants to discover available tenants. 'AllTenants' is refused locally — a device action targets exactly one tenant's mailbox.
userIdstringnonullMailbox UPN that owns the device (e.g., user@contoso.com). REQUIRED for 'Quarantine' and 'Allow' — it is the Set-CASMailbox identity whose ActiveSync allow/block list is edited. Optional for 'Delete' (it only labels CIPP's audit log entry there). Sent as the 'Userid' query parameter (capital U, lowercase 'id' — per spec).

[CIPP] Show or hide a mailbox from the Global Address List (GAL) via POST /api/ExecHideFromGAL. Hidden mailboxes are not visible in the address book but can still receive email.

ParamTypeRequiredDefaultDescription
additionalFieldsJsonstringnonullForward-compatibility escape hatch: a JSON object whose properties are merged into the request body LAST, overriding any of the typed parameters above. Use only for new CIPP fields that this tool does not yet expose. Keys are passed verbatim — caller is responsible for exact spec casing.
hidebooleannotrueTrue to hide from GAL, false to show in GAL. Defaults to true. Sent as body key 'HideFromGAL' as the string 'true' or 'false'.
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Use cipp_list_tenants to discover available tenants. Sent as both query argument and body key 'tenantFilter'.
userIdstringyesUser ID or UPN of the mailbox (e.g., user@contoso.com). Use cipp_list_mailboxes to find valid IDs. Sent as body key 'ID' (uppercase, intentional).

[CIPP] Manage a High Volume Email (HVE) user account via POST /api/ExecHVEUser. HVE accounts let line-of-business applications (CRM, ticketing, automation) send large volumes of internal email without a full Exchange mailbox licence. The endpoint is a switch on body key 'Action', DEFAULTING TO 'Create' when the key is absent, with FIVE behaviours — Create, Edit, AssignBillingPolicy, RemoveBillingPolicy and Remove (Remove deletes the mail user via Remove-MailUser -Confirm:$false). Each non-Create action requires 'Identity'. Casing differs BETWEEN actions and is not normalized: Create reads camelCase 'displayName' / 'primarySMTPAddress' / 'password', while Edit reads PascalCase 'DisplayName' / 'PrimarySmtpAddress' (or 'username' + 'domain') / 'ReplyTo'. Create also warns when Security Defaults are on and tries to exclude the new account from basic-auth-blocking Conditional Access policies. NOTE: a failed call returns HTTP 403 (not 500) with the message in Results. Verified against CIPP-API master @df3738d.

ParamTypeRequiredDefaultDescription
fieldsJsonstringyesHVE configuration as JSON object merged LAST into the request body, passed through VERBATIM (this tool never rewrites your keys or values). 'TenantFilter' is already seeded. Choose the behaviour with 'Action' — omitted means Create. CREATE: 'displayName' (camelCase), 'primarySMTPAddress' (camelCase, 'SMTP' in caps), 'password' (camelCase). EDIT ({"Action":"Edit"}): 'Identity' (required) plus any of 'DisplayName' (PascalCase), 'PrimarySmtpAddress' (PascalCase) or 'username'+'domain' to compose it, and 'ReplyTo'; with none of those it reports 'No changes specified'. ASSIGNBILLINGPOLICY: 'Identity' + 'BillingPolicyId' (both required; the id may be a bare string or a {"value":...} object). REMOVEBILLINGPOLICY: 'Identity'. REMOVE: 'Identity' — permanently deletes the HVE mail user. Example: {"displayName":"Billing Robot","primarySMTPAddress":"billing@contoso.com","password":"..."}
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Use cipp_list_tenants to discover available tenants. This tool seeds it into the body as PascalCase 'TenantFilter', which is exactly the key upstream reads ($Request.Body.TenantFilter) — nothing extra is needed in fieldsJson. It is also sent as the tenantFilter query argument for connector consistency; upstream ignores the query here.

[CIPP] Manage mailbox restore requests via POST /api/ExecMailboxRestore. DESTRUCTIVE on one arm: 'Remove' runs Remove-MailboxRestoreRequest and deletes the restore request. The endpoint is a switch on body key 'Action' with TWO DIFFERENT PARAMETER SETS, and the identity parameter belongs to only one of them. 'Remove' / 'Resume' / 'Suspend' act on an EXISTING restore request and use 'Identity' as its identifier. Every other Action value — including 'New' and an omitted Action — falls to the default arm, which CREATES a restore request from body keys 'RequestName' (the request's Name), 'SourceMailbox' and 'TargetMailbox' — always with AllowLegacyDNMismatch hardcoded to true by upstream, which is NOT a body key: sending AllowLegacyDNMismatch:false is silently ignored and the restore still runs with the mismatch allowed — and IGNORES 'Identity' completely. So to create a restore you must supply RequestName and SourceMailbox through additionalFieldsJson; the required identity parameter is inert on that path and exists for the three request-lifecycle actions. WARNING: this endpoint answers HTTP 200 even on failure — its catch block returns 200 with the error text in Results and colour='danger' — so always read the body. Verified against CIPP-API master @df3738d.

ParamTypeRequiredDefaultDescription
actionstringnonullWhich operation to run. 'Remove' DELETES the existing restore request named by identity (Remove-MailboxRestoreRequest); 'Resume' and 'Suspend' resume and pause it. ANY other value, including 'New' or omitting this parameter, takes the default arm and CREATES a restore request from RequestName + SourceMailbox + TargetMailbox. Sent as body key 'Action'. When you pass this parameter, an 'Action' supplied through additionalFieldsJson may not disagree with it — the call is refused before dispatch rather than running a different operation than the one it was reviewed as.
additionalFieldsJsonstringnonullForward-compatibility escape hatch: a JSON object whose properties are merged into the request body LAST, overriding the typed parameters above — EXCEPT the verb when you also passed it, and EXCEPT the three keys the typed parameters put a target in: 'TenantFilter', 'Identity' and, when you supplied the typed targetMailbox parameter, 'TargetMailbox'. Those three are re-validated on the MERGED body and may not be changed here — an 'Identity' override would remove, resume or suspend a DIFFERENT restore request than the reviewed one, a 'TargetMailbox' override would restore the source mailbox's contents into a DIFFERENT mailbox, and a 'TenantFilter' override would put a tenant on the body that contradicts the one this call was authorized for (upstream prefers the query argument this tool always sends, so it would not move the operation today, but the disagreement is refused rather than sent); a second, differently-cased spelling of any of them is refused too. Supply them through the typed 'tenantFilter', 'identity' and 'targetMailbox' parameters instead. If you supplied the typed 'action' parameter, an 'Action' here that disagrees with it is refused before dispatch ('Remove' deletes the restore request while the default arm creates one, so honoring the override would run a different operation than the reviewed one), and a second differently-cased spelling is refused too — it does not replace the seeded key, it ships alongside it, and CIPP resolves body members without regard to case. An 'Action' naming the same verb is accepted and forwarded as written. If you OMIT the typed 'action' parameter, an 'Action' supplied here is honored as written, because this endpoint's verb set is open — anything that is not Remove/Resume/Suspend creates a restore request. REQUIRED ON THE CREATE PATH for 'RequestName' (becomes New-MailboxRestoreRequest -Name) and 'SourceMailbox' (the soft-deleted mailbox — its distinguished name, alias or GUID; a plain string is fine, upstream reads '.value ?? the scalar'). Other create-path keys upstream reads: AssociatedMessagesCopyOption, ExcludeFolders, IncludeFolders, ConflictResolutionOption and TargetType (these five are read as {"value":...} objects — a bare string sends null), plus BatchName, CompletedRequestAgeLimit, SourceRootFolder, BadItemLimit, LargeItemLimit, ExcludeDumpster, SourceIsArchive, TargetIsArchive. Keys are passed verbatim — caller is responsible for exact spec casing.
identitystringyesIdentifier of an EXISTING mailbox restore request. Sent as body key 'Identity' and used ONLY by action 'Remove', 'Resume' or 'Suspend' (as Remove/Resume/Suspend-MailboxRestoreRequest -Identity). It is IGNORED on the create path — creating a restore reads RequestName/SourceMailbox/TargetMailbox instead — so on a create call pass any placeholder and supply those keys via additionalFieldsJson.
targetMailboxstringnonullDestination mailbox that receives the restored data (e.g., admin@contoso.com). Sent as body key 'TargetMailbox' and used ONLY on the create path (New-MailboxRestoreRequest -TargetMailbox). A plain string is correct — upstream reads '.value ?? the scalar'.
targetRootFolderstringnonullOptional sub-folder under the target mailbox where restored content lands (e.g., 'Restored'). Sent as body key 'TargetRootFolder'; create path only.
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Use cipp_list_tenants to discover available tenants. Sent as the tenantFilter query argument AND as body key 'TenantFilter' (uppercase, intentional — upstream reads $Request.Query.TenantFilter ?? $Request.Body.TenantFilter).

[CIPP] Trace email messages via POST /api/ListMessageTrace by sender, recipient, and date range. Returns delivery status, timestamps, and routing details. Useful for troubleshooting email delivery issues. dateFilter='relative' (uses 'days') OR 'startEnd' (uses 'startDate'/'endDate').

ParamTypeRequiredDefaultDescription
additionalFieldsJsonstringnonullForward-compatibility escape hatch: a JSON object whose properties are merged into the request body LAST, overriding any of the typed parameters above. Use only for new CIPP fields that this tool does not yet expose. Keys are passed verbatim — caller is responsible for exact spec casing.
dateFilterstringnonullDate filter mode. Valid values: 'relative' (use the 'days' parameter) or 'startEnd' (use 'startDate' and 'endDate'). Sent as body key 'dateFilter'.
daysnumbernonullNumber of days back to trace when dateFilter='relative' (e.g., 7). Sent as body key 'days'.
endDatestringnonullEnd date for the trace in ISO 8601 format (e.g., '2024-01-07T23:59:59Z'). Sent as body key 'endDate'.
fromIPstringnonullOriginating IP address filter. Sent as body key 'fromIP'.
messageIdstringnonullSpecific Exchange Internet Message ID to look up. Sent as body key 'MessageId'.
recipientstringnonullRecipient email address to filter by. CIPP wraps this into a single-element array under body key 'recipient'.
senderstringnonullSender email address to filter by. CIPP wraps this into a single-element array under body key 'sender'.
startDatestringnonullStart date for the trace in ISO 8601 format (e.g., '2024-01-01T00:00:00Z'). Sent as body key 'startDate'.
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Use cipp_list_tenants to discover available tenants. Sent as both query argument and body key 'tenantFilter'.
toIPstringnonullDestination IP address filter. Sent as body key 'toIP'.
traceDetailstringnonullTrace detail level passed verbatim by CIPP (e.g., 'Summary', 'Detailed'). Sent as body key 'traceDetail'.

[CIPP] Modify calendar folder permissions via POST /api/ExecModifyCalPerms — the cmdlet-style batch alternative to cipp_edit_calendar_permissions. The backend expects body key 'permissions' to be a JSON ARRAY of permission-entry objects (NOT a JSON-encoded string). Each entry's PermissionLevel/UserID are unwrapped via `.value ?? <scalar>`, so this tool sends {value,label} objects. It builds a single-entry array from the typed parameters below; supply permissionsJson to send a full multi-entry array instead. Response is CIPP's raw {"Results":[...]} envelope (HTTP 500 if any entry errored).

ParamTypeRequiredDefaultDescription
additionalFieldsJsonstringnonullForward-compatibility escape hatch: a JSON object merged into the TOP-LEVEL request body LAST (alongside tenantFilter/userID/permissions), overriding earlier keys. Keys passed verbatim.
canViewPrivateItemsbooleannofalseEnable delegate private-item visibility for this entry (Editor role only). Sent as the entry's 'CanViewPrivateItems' JSON boolean. Default false.
delegateUserOrGroupstringnonullThe delegate user/group UPN or id being granted or removed. Wrapped into the entry's 'UserID' array of {value,label}. Required unless permissionsJson is supplied.
folderNamestringno"Calendar"Calendar folder name for the entry. Default 'Calendar'. Sent as the entry's 'FolderName'.
mailboxUserIdstringyesThe mailbox OWNER whose calendar is modified — UPN or user object id. Sent as body key 'userID' (PascalCase 'ID', per the CIPP entrypoint). CIPP resolves a UPN to an object id via Graph.
modificationstringno"Add"'Add' to grant/update the permission or 'Remove' to revoke it. Sent verbatim as the entry's 'Modification'. Default 'Add'.
permissionLevelstringnonullOutlook calendar role for the entry (e.g., 'Editor', 'Reviewer', 'Owner', 'AvailabilityOnly'). Sent as the entry's 'PermissionLevel' = {value,label}. Required when modification='Add'.
permissionsJsonstringnonullAdvanced: a JSON ARRAY of full permission-entry objects to send as body key 'permissions', REPLACING the single-entry array built from the typed parameters. Each entry may contain: UserID (array of objects or a bare UPN), PermissionLevel ( object or a bare role string), Modification ('Add'|'Remove'), CanViewPrivateItems (bool), FolderName. Use to batch multiple delegates in one call.
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Use cipp_list_tenants to discover available tenants. Sent as body key 'tenantFilter'.

[CIPP] Modify a mailbox owner's Contacts-folder permissions via POST /api/ExecModifyContactPerms (cmdlet-style batch). Controls who can view or edit a user's personal Contacts folder. The backend expects body key 'permissions' to be a JSON ARRAY of permission-entry objects (NOT a JSON-encoded string). Each entry's PermissionLevel/UserID are unwrapped via `.value ?? <scalar>`, so this tool sends {value,label} objects. It builds a single-entry array from the typed parameters below; supply permissionsJson for a full multi-entry array. NOTE (differs from calendar perms): the Contacts helper has NO CanViewPrivateItems/delegate flag, and FolderName defaults to 'Contact'. Response is CIPP's raw {"Results":[...]} envelope (HTTP 500 if any entry errored).

ParamTypeRequiredDefaultDescription
additionalFieldsJsonstringnonullForward-compatibility escape hatch: a JSON object merged into the TOP-LEVEL request body LAST (alongside tenantFilter/userID/permissions), overriding earlier keys. Keys passed verbatim.
delegateUserOrGroupstringnonullThe delegate user/group UPN or id being granted or removed. Wrapped into the entry's 'UserID' array of {value,label}. Required unless permissionsJson is supplied.
folderNamestringno"Contact"Contacts folder name for the entry. Default 'Contact' (the Contacts helper's default — NOT 'Calendar'). Sent as the entry's 'FolderName'.
mailboxUserIdstringyesThe mailbox OWNER whose Contacts folder is modified — UPN or user object id. Sent as body key 'userID' (PascalCase 'ID', per the CIPP entrypoint). CIPP resolves a UPN to an object id via Graph.
modificationstringno"Add"'Add' to grant/update the permission or 'Remove' to revoke it. Sent verbatim as the entry's 'Modification'. Default 'Add'.
permissionLevelstringnonullOutlook permission role for the entry (e.g., 'Editor', 'Reviewer', 'Owner', 'AvailabilityOnly'). Sent as the entry's 'PermissionLevel' = {value,label}. Required when modification='Add'.
permissionsJsonstringnonullAdvanced: a JSON ARRAY of full permission-entry objects to send as body key 'permissions', REPLACING the single-entry array built from the typed parameters. Each entry may contain: UserID (array of objects or a bare UPN), PermissionLevel ( object or a bare role string), Modification ('Add'|'Remove'), FolderName, SendNotificationToUser (bool). Use to batch multiple delegates in one call.
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Use cipp_list_tenants to discover available tenants. Sent as body key 'tenantFilter'.

[CIPP] Modify mailbox-level permissions via POST /api/ExecModifyMBPerms — the cmdlet-style batch alternative to cipp_edit_mailbox_permissions. TWO PAYLOAD SHAPES ARE REACHABLE HERE: a BATCH, body key 'mailboxRequests' = array of {"userID":..., "permissions":[...]}; or a SINGLE mailbox, top-level 'userID' AND 'permissions' both present (both are required for that shape to match). CIPP also accepts a bare top-level JSON array, but this tool cannot send one — fieldsJson is deserialized as a JSON object. If no shape matches, CIPP answers HTTP 400 'No mailbox requests provided'. Each permissions entry carries: 'PermissionLevel' (REQUIRED) — one of FullAccess, SendAs, SendOnBehalf, ReadPermission, ExternalAccount, DeleteItem, ChangePermission, ChangeOwner (an UNRECOGNISED value is SILENTLY SKIPPED; a comma-separated string is split into several levels); 'Modification' — 'Remove' revokes, ANY other value (including 'Add') grants; 'UserID' (REQUIRED) — the delegate, as a bare UPN string, an array of them, or {"value":...} objects; and optional 'AutoMap' (defaults true). WARNING: an unrecognised PermissionLevel is forgiving, a MISSING one is not. Upstream dereferences both required fields by METHOD CALL — $PermissionLevels.Trim() and $Permission.UserID.ToString() — inside a loop that sits OUTSIDE any try block, so omitting either raises 'You cannot call a method on a null-valued expression' as an unhandled function error: HTTP 500 with no {"Results":...} envelope at all. The Add*/Remove* bucket keys (AddFullAccess, RemoveSendAs, ...) belong to cipp_edit_mailbox_permissions — this endpoint never reads them. The mailbox identifier key is 'userID'; 'userPrincipalName' is not read. Verified against CIPP-API master @df3738d.

ParamTypeRequiredDefaultDescription
fieldsJsonstringyesPermission configuration as JSON object merged into the request body after the seeded tenantFilter, passed through VERBATIM (this tool never rewrites your keys or values). Use EITHER {"mailboxRequests":[{"userID":"owner@contoso.com","permissions":[...]}, ...]} for a batch, OR top-level "userID" plus "permissions" for one mailbox. A permissions entry: {"PermissionLevel":"FullAccess","Modification":"Add","UserID":[{"value":"tech@contoso.com"}],"AutoMap":true} — PermissionLevel accepts FullAccess, SendAs, SendOnBehalf, ReadPermission, ExternalAccount, DeleteItem, ChangePermission or ChangeOwner (or several comma-separated), an unrecognised level is skipped without an error, Modification 'Remove' revokes while anything else grants, UserID may also be a bare UPN string or an array of them, and AutoMap defaults to true. 'PermissionLevel' and 'UserID' are REQUIRED on EVERY entry: an unrecognised level is skipped quietly, but a missing one is fatal, because upstream evaluates $PermissionLevels.Trim() and $Permission.UserID.ToString() before any error handling starts — a null in either field is a method-call-on-null that returns an unhandled HTTP 500 instead of the {"Results":...} envelope. Full example: {"userID":"owner@contoso.com","permissions":[{"PermissionLevel":"FullAccess","Modification":"Add","UserID":"tech@contoso.com"}]}
tenantFilterstringyesTarget tenant (e.g., contoso.onmicrosoft.com, or the tenant GUID). Use cipp_list_tenants to discover available tenants. REQUIRED: CIPP's ExecModifyMBPerms reads the tenant ONLY from body key 'tenantFilter' — it never reads the query string and never validates the value — so a request without it resolves no tenant, every Graph mailbox lookup fails into 'Could not find user <upn>', and the endpoint STILL RETURNS HTTP 200. This tool now seeds the key from this parameter, before fieldsJson is merged, so an explicit 'tenantFilter' inside fieldsJson still overrides it.

[CIPP] Remove a specific inbox rule from a mailbox via POST /api/ExecRemoveMailboxRule. Use cipp_get_mailbox_rules first to find rule IDs. Commonly used to remove suspicious forwarding rules during incident response. ruleId is MANDATORY — it is not an alternative to ruleName: upstream calls $RuleId.Split('')[0] to derive the mailbox object id BEFORE its try block opens, so a ruleName-only call dies on a method-invocation-on-null and returns an unhandled Azure Function error instead of the documented {"Results":...} envelope. This tool refuses that call locally. ruleName is passed through as an additional selector alongside ruleId. NOTE: the body uses uppercase 'TenantFilter' (intentional). Verified against CIPP-API master @df3738d.

ParamTypeRequiredDefaultDescription
additionalFieldsJsonstringnonullForward-compatibility escape hatch: a JSON object whose properties are merged into the request body LAST, overriding any of the typed parameters above. Use only for new CIPP fields that this tool does not yet expose. Keys are passed verbatim — caller is responsible for exact spec casing.
ruleIdstringnonullREQUIRED. The rule ID to remove, exactly as cipp_get_mailbox_rules returns it — upstream splits it on the first backslash to recover the mailbox object id, so a truncated or synthesized id targets the wrong mailbox. Sent as body key 'ruleId'. Omitting it is refused before dispatch (upstream would crash with an unhandled null-method error, not an error envelope).
ruleNamestringnonullOptional rule display name, forwarded as body key 'ruleName' in addition to ruleId. It is NOT a substitute for ruleId — upstream passes both to Remove-CIPPMailboxRule but derives the mailbox from ruleId alone.
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Use cipp_list_tenants to discover available tenants. Sent as the tenantFilter query argument AND as body key 'TenantFilter' (uppercase, intentional — upstream reads $Request.Query.TenantFilter ?? $Request.Body.TenantFilter).
userPrincipalNamestringyesUPN of the mailbox owner (e.g., user@contoso.com). Use cipp_list_mailboxes to find valid UPNs. Sent as body key 'userPrincipalName'.

[CIPP] Unblock a user who has been restricted from sending email via POST /api/ExecRemoveRestrictedUser. Use cipp_list_restricted_users first to see blocked sender addresses.

ParamTypeRequiredDefaultDescription
senderAddressstringyesSender SMTP address of the restricted user (e.g., user@contoso.com). Use cipp_list_restricted_users to find blocked addresses. Sent as body key 'SenderAddress'.
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Use cipp_list_tenants to discover available tenants. Sent as both query argument and body key 'tenantFilter'.

[CIPP] Schedule a mailbox-vacation workflow against POST /api/ExecScheduleMailboxVacation. It queues TWO scheduled tasks — an 'Add' at startDate granting each delegate the named permission(s) on each vacationing owner's mailbox (plus calendar folder permissions when includeCalendar is set), and a 'Remove' at endDate that revokes them. The grant is the CARTESIAN PRODUCT of mailboxOwners x delegates x permissionTypes. WIRE SHAPES ARE LOAD-BEARING and this tool builds them for you from the plain parameters below: startDate/endDate go out as UNIX EPOCH SECONDS (real JSON numbers — CIPP casts them with [int64] and schedules the task at that second, so an ISO string fails the whole request); mailboxOwners/delegates go out as ARRAYS of {value,label} objects, because CIPP reads each element's '.addedFields.userPrincipalName ?? .value' and a JSON-encoded string or a bare UPN array resolves to null PER ELEMENT — and since @($null) has Count 1, CIPP's own 'owners, delegates and permission types are required' guard does NOT fire, so the vacation is scheduled against a null user at HTTP 200; permissionTypes goes out as an ARRAY (CIPP never splits a comma-separated string, it would use 'FullAccess,SendAs' as one bogus level); and autoMap/includeCalendar/canViewPrivateItems go out as REAL JSON BOOLEANS, because CIPP casts them with [bool] and in PowerShell the STRING 'false' is true. Note autoMap defaults to TRUE upstream when the key is absent. Body keys (camelCase): 'tenantFilter' (required, BODY ONLY — the query argument is not read), 'startDate', 'endDate', 'mailboxOwners', 'delegates', 'permissionTypes', 'autoMap', 'includeCalendar', 'calendarPermission', 'canViewPrivateItems', 'postExecution', 'reference'. Destructive note: this grants long-lived FullAccess; confirm the Remove task exists before relying on auto-revert. Verified against CIPP-API master @df3738d.

ParamTypeRequiredDefaultDescription
additionalFieldsJsonstringnonullForward-compatibility escape hatch: a JSON object whose properties are merged into the request body LAST, overriding any of the typed parameters above. Use only for new CIPP fields that this tool does not yet expose. Keys are passed verbatim — caller is responsible for exact spec casing.
autoMapbooleannonullWhen true, the covered mailbox auto-mounts in the delegate's Outlook. Sent as body key 'autoMap' as a REAL JSON boolean. Upstream defaults this to TRUE when the key is absent, so pass false explicitly to suppress auto-mapping.
calendarPermissionstringnonullOutlook calendar role granted to the delegate when includeCalendar is true. Typical values: 'Reviewer' | 'Editor' | 'AvailabilityOnly' | 'LimitedDetails' | 'Owner' | 'PublishingEditor' | 'Author' | 'PublishingAuthor' | 'Contributor' | 'NonEditingAuthor'. Sent as body key 'calendarPermission'; a plain string is correct (upstream reads '.value ?? the scalar').
canViewPrivateItemsbooleannonullWhen true, the delegate can view calendar items marked Private (otherwise hidden). Sent as body key 'canViewPrivateItems' as a REAL JSON boolean. Applies only to the calendar permissions, so it is inert unless includeCalendar is true.
delegatesstringyesDelegate users who receive coverage access. Pass a comma-separated list of UPNs or a JSON array of UPN strings. Sent as body key 'delegates' as an ARRAY of {value,label} objects, same contract as mailboxOwners.
endDatestringyesWhen the vacation/coverage window ends and the delegate access is revoked. Pass an ISO 8601 timestamp (e.g., '2026-05-15T17:00:00Z') or Unix epoch seconds; sent as body key 'endDate' as a JSON NUMBER of epoch seconds.
includeCalendarbooleannonullWhen true, also grant the delegate calendar folder permissions in addition to the mailbox permission. Sent as body key 'includeCalendar' as a REAL JSON boolean (a string 'false' would read as true upstream). Calendar permissions are only built when this is true AND calendarPermission is set.
mailboxOwnersstringyesVacationing mailbox owners whose mailboxes are being covered. Pass a comma-separated list of UPNs ('a@contoso.com,b@contoso.com') or a JSON array of UPN strings. The tool sends body key 'mailboxOwners' as an ARRAY of {value,label} objects — the autocomplete shape CIPP unwraps; a plain string or bare-UPN array would resolve to a null user and still be scheduled at HTTP 200.
permissionTypesstringnonullREQUIRED IN PRACTICE. Mailbox permission level(s) granted to each delegate — e.g. 'FullAccess', or 'FullAccess,SendAs' for two. Sent as body key 'permissionTypes' as an ARRAY of strings, one element per level, because CIPP uses each element as a whole permission level and never splits on commas. Omitting it is refused before dispatch: CIPP would read a single null level, its count guard would not fire (an array holding one null still counts 1), and it would schedule a vacation that grants nothing.
postExecutionEmailbooleannonullAfter completion, send the CIPP-configured notification email. Channel of the postExecution notification block.
postExecutionPsabooleannonullAfter completion, post a ticket/note via the configured PSA integration. Channel of the postExecution notification block.
postExecutionWebhookbooleannonullAfter completion, fire the configured CIPP webhook(s). Channel of the postExecution notification block.
referencestringnonullFree-form audit reference string written into CIPP history (e.g., a ticket number). Sent as body key 'reference' (camelCase, string).
startDatestringyesWhen the vacation/coverage window begins. Pass an ISO 8601 timestamp (e.g., '2026-05-01T09:00:00Z') or Unix epoch seconds; the tool converts it and sends body key 'startDate' as a JSON NUMBER of epoch seconds, which is what CIPP's [int64] cast and task scheduler require. An unparseable value is refused before dispatch.
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Sent as body key 'tenantFilter' (camelCase, required) — upstream reads the BODY only; the identical query argument this connector also sends is inert here. Use cipp_list_tenants to discover available tenants.

[CIPP] Schedule a future out-of-office (auto-reply) window for one or more users via POST /api/ExecScheduleOOOVacation. CIPP queues TWO tasks — an 'Add' at startDate that turns auto-replies on with the supplied messages, and a 'Remove' at endDate that turns them off without re-sending messages (so the user's own later edits survive). WIRE SHAPES ARE LOAD-BEARING and this tool builds them for you: startDate/endDate go out as UNIX EPOCH SECONDS (real JSON numbers — CIPP does [DateTimeOffset]::FromUnixTimeSeconds([int64]$StartDate) and schedules at that second, so an ISO string fails the request), and 'Users' goes out as an ARRAY of {value,label} objects, because CIPP reads each element's '.addedFields.userPrincipalName ?? .value'. A JSON-encoded string — the shape this tool used to send — resolves to null PER ELEMENT, and because an array holding one null still has Count 1, CIPP's 'At least one user is required' guard does NOT fire: the OOO is scheduled against a null user and the API answers HTTP 200 'Successfully scheduled'. Body keys: 'tenantFilter' (camelCase, required, BODY ONLY — the query argument is not read), 'Users' (PascalCase, intentional — do not normalize), 'startDate', 'endDate', 'externalMessage', 'internalMessage', 'postExecution', 'reference'. Verified against CIPP-API master @df3738d.

ParamTypeRequiredDefaultDescription
additionalFieldsJsonstringnonullForward-compatibility escape hatch: a JSON object whose properties are merged into the request body LAST, overriding any of the typed parameters above. Use only for new CIPP fields that this tool does not yet expose. Keys are passed verbatim — caller is responsible for exact spec casing.
endDatestringyesWhen the out-of-office window ends and auto-replies are switched off. Pass an ISO 8601 timestamp (e.g., '2026-05-15T17:00:00Z') or Unix epoch seconds; sent as body key 'endDate' as a JSON NUMBER of epoch seconds.
externalMessagestringnonullAuto-reply text sent to external senders. Plain text or HTML. Sent as body key 'externalMessage' (camelCase, string). Used by the Add task only.
internalMessagestringnonullAuto-reply text sent to internal senders (same tenant). Plain text or HTML. Sent as body key 'internalMessage' (camelCase, string). Used by the Add task only.
postExecutionEmailbooleannonullAfter completion, send the CIPP-configured notification email. Channel of the postExecution notification block.
postExecutionPsabooleannonullAfter completion, post a ticket/note via the configured PSA integration. Channel of the postExecution notification block.
postExecutionWebhookbooleannonullAfter completion, fire the configured CIPP webhook(s). Channel of the postExecution notification block.
referencestringnonullFree-form audit reference string written into CIPP history (e.g., a ticket number). Sent as body key 'reference' (camelCase, string).
startDatestringyesWhen the out-of-office window begins. Pass an ISO 8601 timestamp (e.g., '2026-05-01T09:00:00Z') or Unix epoch seconds; the tool converts it and sends body key 'startDate' as a JSON NUMBER of epoch seconds, which is what CIPP's [int64] cast and task scheduler require. An unparseable value is refused before dispatch.
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Sent as body key 'tenantFilter' (camelCase, required) — upstream reads the BODY only; the identical query argument this connector also sends is inert here. Use cipp_list_tenants to discover available tenants.
usersstringyesUsers whose OOO is being scheduled. Pass a comma-separated list of UPNs ('a@contoso.com,b@contoso.com') or a JSON array of UPN strings. The tool sends body key 'Users' (PascalCase, intentional) as an ARRAY of {value,label} objects — the autocomplete shape CIPP unwraps; a plain or JSON-encoded string would resolve to a null user and still be scheduled at HTTP 200.

[CIPP] Configure calendar processing settings for a resource mailbox (room or equipment) via POST /api/ExecSetCalendarProcessing — auto-accept, booking window, conflict resolution, processing of external meeting messages, and so on. TWO WARNINGS, both silent at HTTP 200. (1) BOOLEANS MUST BE REAL JSON BOOLEANS: upstream coerces every flag with `-as [bool]`, and in PowerShell EVERY non-empty string is $true — so the string 'false' TURNS THE SETTING ON. Send true/false, or omit the key. (2) THIS IS A FULL OVERWRITE, NOT A PATCH: all ten boolean flags are placed in Set-CalendarProcessing's cmdParams on every call, so any flag you omit is written as $false, and omitting BOTH automaticallyAccept and automaticallyProcess writes AutomateProcessing='None'. Always send the mailbox's complete desired boolean state, not just the one field you want to change. Numeric keys are the exception — they are applied only when truthy. Body keys: 'tenantFilter' (required), 'UPN' (PascalCase, the resource mailbox UPN = Set-CalendarProcessing -Identity), booleans 'automaticallyAccept', 'automaticallyProcess', 'allowConflicts', 'allowRecurringMeetings', 'scheduleOnlyDuringWorkHours', 'addOrganizerToSubject', 'deleteComments', 'deleteSubject', 'removePrivateProperty', 'removeCanceledMeetings', 'removeOldMeetingMessages', 'processExternalMeetingMessages'; numerics 'maxConflicts', 'maximumDurationInMinutes', 'minimumDurationInMinutes', 'bookingWindowInDays'; string 'additionalResponse'. Verified against CIPP-API master @df3738d.

ParamTypeRequiredDefaultDescription
fieldsJsonstringyesCalendar processing settings as JSON object merged LAST into the request body, passed through VERBATIM (this tool never rewrites your keys or values). 'UPN' (PascalCase) is the resource mailbox. Booleans — 'automaticallyAccept', 'automaticallyProcess', 'allowConflicts', 'allowRecurringMeetings', 'scheduleOnlyDuringWorkHours', 'addOrganizerToSubject', 'deleteComments', 'deleteSubject', 'removePrivateProperty', 'removeCanceledMeetings', 'removeOldMeetingMessages', 'processExternalMeetingMessages' — MUST be real JSON booleans: upstream uses `-as [bool]`, so the STRING 'false' evaluates to true and turns the setting ON. Every omitted boolean is written as false (this endpoint always sends the whole flag set), and with neither automaticallyAccept nor automaticallyProcess truthy it writes AutomateProcessing='None'. Numerics — 'maxConflicts', 'maximumDurationInMinutes', 'minimumDurationInMinutes', 'bookingWindowInDays' — are applied only when truthy, so JSON 0 is dropped while the string '0' is sent as 0. 'additionalResponse' is a free string. Example: {"UPN":"room1@contoso.com","automaticallyAccept":true,"allowConflicts":false,"deleteComments":false,"bookingWindowInDays":180}
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Use cipp_list_tenants to discover available tenants. Sent as body key 'tenantFilter' — upstream reads the body only.

[CIPP] Configure email forwarding for a mailbox via POST /api/ExecEmailForward. forwardOption SELECTS THE OPERATION and is REQUIRED — it is not a hint: 'internalAddress' forwards to the ForwardInternal recipient, 'ExternalAddress' forwards to the ForwardExternal SMTP address, 'disabled' turns forwarding off. Supplying ForwardInternal or ForwardExternal does NOT choose the branch. WARNING (why this tool refuses bad values locally): upstream is a PowerShell switch over exactly those three literals with NO default arm, so an absent or unrecognised forwardOption runs no branch at all — neither the result nor the HTTP status is ever assigned and nothing changes. Body keys: 'tenantFilter' (body is what upstream reads), 'userID' (uppercase ID), 'ForwardInternal', 'ForwardExternal', 'KeepCopy' (real JSON boolean), 'forwardOption'. Verified against CIPP-API master @df3738d.

ParamTypeRequiredDefaultDescription
additionalFieldsJsonstringnonullForward-compatibility escape hatch: a JSON object whose properties are merged into the request body LAST, overriding any of the typed parameters above — EXCEPT the verb, the two keys that say whose mailbox this acts on, and the forwarding destinations the typed parameters supplied. THREE keys are always re-validated on the MERGED body and may NOT be changed here: 'forwardOption', because the typed value is the operation this call was reviewed as and 'disabled' turns forwarding OFF while 'ExternalAddress' sends the mailbox's mail OUT of the tenant; 'tenantFilter', because upstream reads the TENANT from the body and not from the query argument, so an override would configure forwarding in a different customer's tenant; and 'userID', because an override would redirect a different user's mail. TWO more are re-validated whenever the matching typed parameter was passed: 'ForwardInternal' and 'ForwardExternal' — the destination the mail goes TO is part of what this call was reviewed as, so an override that disagrees with a typed destination is refused before dispatch; when the typed destination was omitted, supplying the key here keeps its documented last-wins behavior. An override of any guarded key that disagrees with the typed parameter is refused before dispatch, and so is a second, differently-cased spelling of any of them; an exact restatement is accepted (and 'forwardOption' additionally accepts a same-operation respelling, since upstream's switch is case-insensitive). Use this for new CIPP fields that this tool does not yet expose. Every OTHER key keeps its documented last-wins behavior and is passed verbatim — caller is responsible for exact spec casing.
forwardExternalstringnonullExternal forwarding target — full SMTP address outside the tenant (e.g., archive@partner.com). Used ONLY when forwardOption='ExternalAddress'. Sent as body key 'ForwardExternal'. Pass a PLAIN SMTP STRING: unlike ForwardInternal, this key gets no '.value' unwrap upstream, so a {value,label} object would be forwarded to Exchange as an object where a string is expected.
forwardInternalstringnonullInternal forwarding target — UPN of an internal recipient in the same tenant (e.g., manager@contoso.com). Used ONLY when forwardOption='internalAddress'. Sent as body key 'ForwardInternal'; a plain UPN string is correct (upstream reads the value directly when it is a string, and unwraps '.value' only when it is not).
forwardOptionstringnonullREQUIRED — selects which operation runs. Accepted case-insensitively: 'internalAddress' (aliases 'internal', 'forwardInternal'); 'ExternalAddress' (aliases 'external', 'forwardExternal'); 'disabled' (aliases 'disable', 'off', 'none'). The resolved upstream literal is sent as body key 'forwardOption'. Any other value — including the ambiguous 'forward', which does not say internal or external — is refused before dispatch, because upstream's switch has no default arm and would silently change nothing.
keepCopybooleannonullWhen true, keep a copy of forwarded messages in the source mailbox in addition to forwarding (DeliverToMailboxAndForward). Sent as body key 'KeepCopy' as a REAL JSON boolean — upstream tests it with '-eq $true'. The 'disabled' branch does not pass it to Set-CIPPForwarding at all.
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Use cipp_list_tenants to discover available tenants. Sent as the tenantFilter query argument AND as body key 'tenantFilter' — upstream reads only the BODY key.
userIdstringyesUser ID or UPN of the mailbox owner (e.g., user@contoso.com). Use cipp_list_mailboxes to find valid IDs. Sent as body key 'userID' (uppercase ID).

[CIPP] Enable or disable litigation hold on a mailbox via POST /api/ExecSetLitigationHold. Litigation hold preserves all mailbox content including deleted items for legal discovery. Upstream computes the hold state as `-not $Request.Body.disable`, a TRUTHINESS test, not a boolean parse — so what decides the outcome is whether the value is FALSY, not whether the key is present: a boolean true or any NON-EMPTY string (the literal "false" and the string "0" included) DISABLES the hold, while a boolean false, a numeric 0 or an empty string are falsy and ENABLE it exactly like omitting the key — and CIPP answers HTTP 200 either way. That is why this tool OMITS 'disable' entirely for disable=false rather than sending a value; do not 'fix' that back to emitting "false". The same rule governs the escape hatch below, where it reads backwards: a 'disable': false placed there is merged LAST over a typed disable=true and silently RE-ENABLES the hold instead of releasing it. 'Identity' is the ONLY mailbox identifier that reaches Set-Mailbox — 'UPN' appears solely in the response and log text — so this tool defaults Identity from the upn parameter when identity is omitted. Body keys: 'tenantFilter', 'UPN', 'Identity', 'disable', 'days'. Verified against CIPP-API master @df3738d.

ParamTypeRequiredDefaultDescription
additionalFieldsJsonstringnonullForward-compatibility escape hatch: a JSON object whose properties are merged into the request body LAST, overriding any of the typed parameters above. Use only for new CIPP fields that this tool does not yet expose. Keys are passed verbatim — caller is responsible for exact spec casing.
daysstringnonullHold duration in days as a numeric string (e.g., '2555'). Sent as body key 'days'; upstream applies it with `-as [int]`. HONOURED ONLY WHEN THE HOLD IS BEING ENABLED (disable false/omitted) — it is ignored on the disable path. Omit for an indefinite hold.
disablebooleannonullWhen true, DISABLE litigation hold: body key 'disable' is sent as a real JSON boolean true. When false or omitted the key is NOT sent at all, which is the enable path — upstream truthiness-tests the value, so a boolean true or any NON-EMPTY string (including the string "false") would disable the hold, while a falsy value would silently enable it.
identitystringnonullExplicit mailbox Identity (UPN, alias, distinguished name, or GUID). Sent as body key 'Identity' — the ONLY value handed to Set-Mailbox -Identity. Defaults to the upn parameter when omitted; supply it explicitly only when the mailbox must be targeted by something other than its UPN.
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Use cipp_list_tenants to discover available tenants. Sent as the tenantFilter query argument AND as body key 'tenantFilter' — upstream reads only the BODY key.
upnstringyesUPN of the mailbox owner (e.g., user@contoso.com). Use cipp_list_mailboxes to find valid IDs. Sent as body key 'UPN', which upstream uses ONLY to compose the result and log text — it is never passed to Set-Mailbox. It is also the default for body key 'Identity' (see below), which is the value that actually targets the mailbox.

[CIPP] Set the maximum send/receive message size for a mailbox via POST /api/ExecSetMailboxEmailSize. Controls the largest single message (including attachments) that can be sent from or received by the mailbox. Body keys per spec: 'tenantFilter' (required), 'UPN' (PascalCase, the mailbox UPN), 'id' (lowercase, the mailbox object ID), 'maxSendSize' (camelCase string with units, e.g., '35MB'), 'maxReceiveSize' (camelCase string with units, e.g., '35MB').

ParamTypeRequiredDefaultDescription
fieldsJsonstringyesConfiguration as JSON object merged LAST into the request body. Spec body keys (exact casing): 'UPN' (PascalCase, mailbox UPN); 'id' (lowercase, mailbox object ID); 'maxSendSize' (camelCase string with size + units, e.g., '35MB'); 'maxReceiveSize' (camelCase string, same format). Keys are passed verbatim — caller is responsible for exact spec casing.
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Use cipp_list_tenants to discover available tenants. Sent as body key 'tenantFilter'.
ParamTypeRequiredDefaultDescription
additionalFieldsJsonstringnonullForward-compatibility escape hatch: a JSON object whose properties are merged into the request body LAST, overriding any of the typed parameters above. Use only for new CIPP fields that this tool does not yet expose. Keys are passed verbatim — caller is responsible for exact spec casing.
localestringnonullLocale identifier (e.g., 'en-US', 'de-DE', 'fr-FR'). Sent as body key 'locale'.
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Use cipp_list_tenants to discover available tenants. Sent as both query argument and body key 'tenantFilter'.
userstringyesUPN of the mailbox owner (e.g., user@contoso.com). Use cipp_list_mailboxes to find valid IDs. Sent as body key 'user'.

[CIPP] Set ONE storage quota threshold for a mailbox via POST /api/ExecSetMailboxQuota. THE THRESHOLD KEYS ARE PRESENCE FLAGS, NOT VALUES: upstream reads the size to write from body key 'quota' alone and uses 'IssueWarningQuota' / 'ProhibitSendQuota' / 'ProhibitSendReceiveQuota' only to decide WHICH Exchange property that one value lands on. Naming a threshold with no 'quota' therefore writes null. Worse, upstream reassigns its $quota variable to each Set-Mailbox call's return value, so a second or third threshold in the same request receives that response object instead of a size string. This tool therefore accepts exactly ONE threshold per call and seeds 'quota' from it when quota is omitted; a second threshold is refused before dispatch. Call the tool once per threshold, warning-first then prohibit-send then prohibit-send-receive. WARNING: naming NO threshold at all is a silent no-op — none of upstream's three presence checks fires, nothing is written, and it still answers HTTP 200 with an empty Results array. NOTE: the body key is lowercase 'tenantfilter' here (intentional, do not normalize) and the endpoint hardcodes HTTP 200 — read the Results array, never the status. Verified against CIPP-API master @df3738d.

ParamTypeRequiredDefaultDescription
additionalFieldsJsonstringnonullForward-compatibility escape hatch: a JSON object whose properties are merged into the request body LAST, overriding any of the typed parameters above. Use only for new CIPP fields that this tool does not yet expose. Keys are passed verbatim — caller is responsible for exact spec casing.
issueWarningQuotastringnonullSet the warning threshold (mail still flows). Sent as body key 'IssueWarningQuota', which upstream treats as a PRESENCE FLAG — the size written comes from 'quota'. Pass the size here and leave quota empty, or pass the same size in both. Only one of the three threshold parameters may be used per call.
prohibitSendQuotastringnonullSet the prohibit-send threshold. Sent as body key 'ProhibitSendQuota', which upstream treats as a PRESENCE FLAG — the size written comes from 'quota'. Pass the size here and leave quota empty, or pass the same size in both. Only one of the three threshold parameters may be used per call.
prohibitSendReceiveQuotastringnonullSet the prohibit-send-and-receive threshold. Sent as body key 'ProhibitSendReceiveQuota', which upstream treats as a PRESENCE FLAG — the size written comes from 'quota'. Pass the size here and leave quota empty, or pass the same size in both. Only one of the three threshold parameters may be used per call.
quotastringnonullThe size actually written, as a string with units (e.g., '50GB', '49.5GB'). Sent as body key 'quota' — the ONLY value upstream writes, whichever threshold was flagged. May be omitted when exactly one threshold parameter below carries the size, in which case this tool copies that size into 'quota'.
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Use cipp_list_tenants to discover available tenants. Sent as the tenantFilter query argument AND as body key 'tenantfilter' (lowercase, intentional — upstream reads $request.body.tenantfilter).
userstringyesUser ID or UPN of the mailbox (e.g., user@contoso.com). Use cipp_list_mailboxes to find valid IDs. Sent as body key 'user' and used as Set-Mailbox -Identity.

[CIPP] Enable or disable an existing server-side inbox rule on a mailbox via POST /api/ExecSetMailboxRule. This endpoint does not create a rule — it flips an existing one identified by ruleId or ruleName. STATE IS EXPRESSED BY PRESENCE, NOT BY VALUE: upstream reads both flags with `-as [bool]`, under which every non-empty string is $true, so the string 'false' means TRUE. Send exactly one of 'Enable' / 'Disable' as a real JSON boolean true and OMIT the other — sending Enable:true together with Disable:'false' sets both, and Enable is tested first, so the rule is enabled. With neither flag truthy upstream `throw`s outside any try block ('No state provided for mailbox rule'), which surfaces as an unhandled function error rather than a result envelope. Body keys: 'TenantFilter' (PascalCase — this tool seeds it for you), 'userPrincipalName' (camelCase), 'ruleId' / 'ruleName', 'Enable' / 'Disable' (PascalCase). Verified against CIPP-API master @df3738d.

ParamTypeRequiredDefaultDescription
fieldsJsonstringyesInbox rule configuration as JSON object merged LAST into the request body, passed through VERBATIM (this tool never rewrites your keys or values). Body keys (exact casing): 'userPrincipalName' (camelCase, mailbox owner UPN); 'ruleId' (camelCase) OR 'ruleName' (camelCase) to identify the rule; and EITHER 'Enable' OR 'Disable' (PascalCase) as a real JSON boolean true — omit the other one entirely. Do not send 'false' for either: upstream coerces with `-as [bool]`, so the string 'false' is true and would set the flag you meant to clear. 'TenantFilter' is already seeded; overriding it here replaces the tenant. Example: {"userPrincipalName":"user@contoso.com","ruleId":"AAA...","Disable":true}
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Use cipp_list_tenants to discover available tenants. This tool seeds it into the body as PascalCase 'TenantFilter', which is exactly the key upstream reads ($Request.Body.TenantFilter) — nothing extra is needed in fieldsJson. It is also sent as the tenantFilter query argument for connector consistency; upstream ignores the query here.

[CIPP] Configure out-of-office (automatic reply) settings for a mailbox via POST /api/ExecSetOoO with separate internal and external messages. AutoReplyState valid values: 'Enabled', 'Disabled', 'Scheduled'. When 'Scheduled', set StartTime and EndTime as ISO-8601 timestamps.

ParamTypeRequiredDefaultDescription
additionalFieldsJsonstringnonullForward-compatibility escape hatch: a JSON object whose properties are merged into the request body LAST, overriding any of the typed parameters above. Use only for new CIPP fields that this tool does not yet expose. Keys are passed verbatim — caller is responsible for exact spec casing.
autoReplyStatestringnonullAuto-reply state. Valid values: 'Enabled', 'Disabled', 'Scheduled'. When 'Scheduled', also set startTime and endTime. Sent as body key 'AutoReplyState'.
endTimestringnonullISO-8601 end timestamp for scheduled OOO (e.g., '2026-05-15T17:00:00Z'). Required when autoReplyState='Scheduled'. Sent as body key 'EndTime'.
externalMessagestringnonullAuto-reply message body for external senders. Plain text or HTML. Sent as body key 'ExternalMessage'.
internalMessagestringnonullAuto-reply message body for internal senders. Plain text or HTML. Sent as body key 'InternalMessage'.
startTimestringnonullISO-8601 start timestamp for scheduled OOO (e.g., '2026-05-01T09:00:00Z'). Required when autoReplyState='Scheduled'. Sent as body key 'StartTime'.
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Use cipp_list_tenants to discover available tenants. Sent as both query argument and body key 'tenantFilter'.
userIdstringyesUser ID or UPN of the mailbox owner (e.g., user@contoso.com). Use cipp_list_mailboxes to find valid IDs. Sent as body key 'userId'.

[CIPP] Set the maximum number of recipients per outbound email message for a mailbox via POST /api/ExecSetRecipientLimits. Helps contain mass-mailing damage from compromised accounts. 'Identity' IS THE ONLY MAILBOX IDENTIFIER — it is the sole value passed to Set-Mailbox; body key 'userid' is used purely to compose the success/error text, so a call that supplies userid without Identity targets a null mailbox while the message still names the user. Body keys: 'tenantFilter' (required, camelCase, body only), 'Identity' (PascalCase — UPN, alias, DN or GUID), 'recipientLimit' (camelCase, passed straight through to Set-Mailbox -RecipientLimits), 'userid' (lowercase, display only). Verified against CIPP-API master @df3738d.

ParamTypeRequiredDefaultDescription
fieldsJsonstringyesConfiguration as JSON object merged LAST into the request body, passed through VERBATIM (this tool never rewrites your keys or values). Body keys (exact casing): 'Identity' (PascalCase) — REQUIRED, the only value that targets the mailbox (UPN, alias, DN or GUID); 'recipientLimit' (camelCase) — the numeric limit, e.g. '500'; 'userid' (lowercase) — OPTIONAL and cosmetic, it only appears in the returned message and never identifies the mailbox. Example: {"Identity":"user@contoso.com","recipientLimit":"500"}
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Use cipp_list_tenants to discover available tenants. Sent as body key 'tenantFilter' — upstream reads the body only.

[CIPP] Enable or disable retention hold on a mailbox via POST /api/ExecSetRetentionHold. When enabled, retention policies temporarily stop deleting/moving items so a user (e.g., on leave) does not lose mail to retention while away. TWO CONTRACTS THAT BITE. (1) The hold state is computed as `-not $Request.Body.disable` — a TRUTHINESS test, not a boolean parse — so what matters is whether the value is FALSY, not whether the key is present. Only a boolean true or a NON-EMPTY string DISABLES the hold, and note that the non-empty string 'false' is one of them. A JSON boolean false, a numeric 0 and an empty string are all falsy, so they ENABLE the hold exactly like omitting the key: there is no way to release a hold by sending 'disable':false. To enable, OMIT the key; to disable, send boolean true. (2) 'Identity' IS THE ONLY MAILBOX IDENTIFIER — it is the sole value passed to Set-Mailbox; 'UPN' only decorates the returned message, so a UPN-only call targets a null mailbox while the message still names the user. Body keys: 'tenantFilter' (required, camelCase, body only), 'Identity' (PascalCase, required in practice), 'UPN' (PascalCase, display only), 'disable' (lowercase). Verified against CIPP-API master @df3738d.

ParamTypeRequiredDefaultDescription
fieldsJsonstringyesConfiguration as JSON object merged LAST into the request body, passed through VERBATIM (this tool never rewrites your keys or values). Body keys (exact casing): 'Identity' (PascalCase) — REQUIRED in practice, the only value that targets the mailbox (UPN, alias, DN or GUID); 'UPN' (PascalCase) — optional and cosmetic, it only appears in the returned message; 'disable' (LOWERCASE) — send boolean true to DISABLE the hold, OMIT it entirely to ENABLE. Upstream truthiness-tests the value, so a boolean true or any NON-EMPTY string disables — including the string 'false', which reads as a release request and is not one. A JSON boolean false, a numeric 0 and an empty string are falsy and behave exactly like omission, so 'disable':false does NOT release a hold: it leaves the hold on and CIPP still answers HTTP 200 reporting the state it actually set. Examples: disable → {"Identity":"user@contoso.com","disable":true}; enable → {"Identity":"user@contoso.com"}
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Use cipp_list_tenants to discover available tenants. Sent as body key 'tenantFilter' — upstream reads the body only.

[CIPP] Start the Managed Folder Assistant for a mailbox via POST /api/ExecStartManagedFolderAssistant to immediately process retention policies and tags instead of waiting for the next automatic cycle. Body keys per spec: 'tenantFilter' (required), 'Id' (mixed-case 'Id' — capital I lowercase d, the mailbox object ID), 'UserPrincipalName' (PascalCase, the mailbox UPN). The same 'Id' and 'tenantFilter' values can also appear as query arguments per the spec.

ParamTypeRequiredDefaultDescription
fieldsJsonstringyesConfiguration as JSON object merged LAST into the request body. Spec body keys (exact casing): 'Id' (mixed case 'Id' — capital I lowercase d, mailbox object ID); 'UserPrincipalName' (PascalCase, mailbox UPN). Provide either Id or UserPrincipalName (or both). Keys are passed verbatim — caller is responsible for exact spec casing.
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Use cipp_list_tenants to discover available tenants. Sent as both query argument and body key 'tenantFilter'.

Mailbox Retention

ToolPlanAccessSummary
cipp_delete_retention_policiesProDestructiveDelete retention policies for a tenant via DELETE /api/ExecManageRetentionPolicies.
cipp_delete_retention_tagsProDestructiveDelete retention tags for a tenant via DELETE /api/ExecManageRetentionTags.
cipp_set_mailbox_retention_policiesProDestructiveAssign an Exchange Online retention policy to one or more mailboxes via POST /api/ExecSetMailboxRetentionPolicies.

[CIPP] Delete retention policies for a tenant via DELETE /api/ExecManageRetentionPolicies. Spec marks the body required with required camelCase 'tenantFilter'. Optional body keys: 'CreatePolicies' (PascalCase array), 'ModifyPolicies' (PascalCase array), 'DeletePolicies' (PascalCase array). WARNING: Deleting retention policies may affect legal hold compliance.

ParamTypeRequiredDefaultDescription
fieldsJsonstringnonullOptional fields to merge into the request body as a JSON object. Spec body keys (preserve EXACTLY): 'CreatePolicies' (PascalCase array), 'ModifyPolicies' (PascalCase array), 'DeletePolicies' (PascalCase array — typically the policy identities to remove). Tool injects camelCase 'tenantFilter' from the typed parameter — do NOT add it here. Pass null for a minimal body.
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Use cipp_list_tenants to discover available tenants. Sent as the 'tenantFilter' (camelCase) query argument AND body field per the live spec.

[CIPP] Delete retention tags for a tenant via DELETE /api/ExecManageRetentionTags. Upstream reads EXACTLY four top-level body keys and nothing else: 'tenantFilter' (camelCase, required; also accepted as a query argument), 'DeleteTags' (PascalCase array of plain tag identity STRINGS), 'CreateTags' (PascalCase array of tag OBJECTS), 'ModifyTags' (PascalCase array of tag OBJECTS). WARNING: there is no top-level 'Comment' key. 'Comment' is a property of each individual tag object inside CreateTags/ModifyTags; a Comment placed at the root of the body is read by nothing and silently dropped — no error, no 400, and the response gives no sign the input was ignored. WARNING: at least one of DeleteTags / CreateTags / ModifyTags is REQUIRED. A body carrying only tenantFilter is not a minimal delete: it takes an undocumented default LIST branch that runs Get-RetentionPolicyTag and returns EVERY retention tag in the tenant at HTTP 200 having deleted nothing, so despite this tool's name and its Destructive flag that list is the tenant's tags, NOT the tags that were removed. WARNING: Removing retention tags affects how mailbox items are managed and may impact compliance.

ParamTypeRequiredDefaultDescription
fieldsJsonstringnonullOptional fields to merge into the request body as a JSON object. Keys are passed through verbatim (PascalCase, preserve EXACTLY); upstream reads only these three besides tenantFilter. 'DeleteTags': array of plain tag identity STRINGS, e.g. ["Old Tag"] — NOT objects. 'CreateTags': array of tag objects; each requires 'Name' and 'Type', where Type must be one of All, Inbox, SentItems, DeletedItems, Drafts, Outbox, JunkEmail, Journal, SyncIssues, ConversationHistory, Personal, RecoverableItems, NonIpmRoot, LegacyArchiveJournals, Clutter, Calendar, Notes, Tasks, Contacts, RssSubscriptions, ManagedCustomFolder. 'ModifyTags': array of tag objects; each requires 'Identity'. Optional PER-TAG properties on the objects in either array: 'Comment', 'RetentionAction' (one of DeleteAndAllowRecovery, PermanentlyDelete, MoveToArchive, MarkAsPastRetentionLimit), 'RetentionEnabled', 'AgeLimitForRetention', 'LocalizedComment', 'LocalizedRetentionPolicyTagName'. WARNING: 'Comment' belongs INSIDE a tag object — a top-level Comment is silently ignored. Example: {"DeleteTags":["Old Tag"],"ModifyTags":[{"Identity":"Personal 1 year","Comment":"reviewed 2026"}]}. Tool injects camelCase 'tenantFilter' from the typed parameter — do NOT add it here. At least one of 'DeleteTags' / 'CreateTags' / 'ModifyTags' is REQUIRED: there is no minimal delete body. With none of the three present, upstream falls to an undocumented default LIST branch that runs Get-RetentionPolicyTag and returns EVERY retention tag in the tenant at HTTP 200 having deleted nothing — do NOT read that tag list as a record of what was removed. A real delete answers with an array of per-operation result strings such as 'Successfully deleted retention tag: <identity>'.
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Use cipp_list_tenants to discover available tenants. Sent as the 'tenantFilter' (camelCase) query argument AND body field per the live spec.

[CIPP] Assign an Exchange Online retention policy to one or more mailboxes via POST /api/ExecSetMailboxRetentionPolicies. Body keys: 'Mailboxes' (PascalCase, a JSON ARRAY of mailbox identity strings — upstream iterates the array and passes each element straight to Set-Mailbox -Identity), 'PolicyName' (PascalCase string — the retention policy display name), 'tenantFilter' (camelCase; also accepted as a query argument). All three are required: upstream answers 400 'Mailboxes array is required' when Mailboxes is missing or empty, and 400 'PolicyName is required' when PolicyName is empty. WARNING: do NOT pass Mailboxes as a comma-separated string. Upstream never splits it, and a non-empty scalar string satisfies the array guard, so the entire string is attempted as ONE invalid mailbox identity — every mailbox you intended is left unchanged and the only symptom is a single failure entry. Returns {"Results":[...]} with one success or failure string per mailbox; read every entry, because a per-mailbox failure is reported inside Results rather than as an error status.

ParamTypeRequiredDefaultDescription
fieldsJsonstringyesRetention assignment as a JSON object. Keys are passed through verbatim — the tool never rewrites or reshapes what you put here. 'Mailboxes' (PascalCase): a JSON ARRAY of mailbox identity strings (e.g. UPNs), one element per mailbox; each element is used directly as the Set-Mailbox -Identity value. 'PolicyName' (PascalCase): the retention policy display name to assign. Both are required. Example: {"Mailboxes":["user1@contoso.com","user2@contoso.com"],"PolicyName":"Default MRM Policy"}. WARNING: a comma-separated string such as "user1@contoso.com,user2@contoso.com" is NOT split upstream — it passes the array guard as a single value and is attempted as one invalid identity, so BOTH mailboxes are silently left unchanged. The tool injects 'tenantFilter' from the typed parameter; keys supplied here are merged last and override it.
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Sent as the 'tenantFilter' body field (camelCase, required) AND accepted as a 'tenantFilter' query parameter per the CIPP spec. The tool injects this for you. Use cipp_list_tenants to discover available tenants.

Contacts & Resources

ToolPlanAccessSummary
cipp_add_contactProWriteCreate a new mail contact in a tenant directory via POST /api/AddContact.
cipp_add_contact_templateProWriteCreate a new CIPP contact template via POST /api/AddContactTemplates.
cipp_add_equipment_mailboxProWriteCreate a new equipment mailbox for a bookable resource (projector, vehicle, conference phone, etc.) via POST /api/AddEquipmentMailbox.
cipp_add_room_listProWriteCreate a new room list (group of rooms by building/floor/location) via POST /api/AddRoomList.
cipp_add_room_mailboxProWriteCreate a new room mailbox via POST /api/AddRoomMailbox.
cipp_deploy_contact_templatesProDestructiveBulk-deploy CIPP contact templates to create mail contacts across one or more tenants via POST /api/DeployContactTemplates.
cipp_edit_contactProWriteEdit an existing mail contact via POST /api/EditContact.
cipp_edit_contact_templateProWriteModify an existing CIPP contact template via POST /api/EditContactTemplates.
cipp_edit_equipment_mailboxProWriteEdit properties of an existing equipment mailbox via POST /api/EditEquipmentMailbox — display name, booking settings, calendar processing, location, and resource metadata.
cipp_edit_room_listProWriteModify an existing room list via POST /api/EditRoomList — rename it, add/remove member rooms, change owners, or update its delivery settings.
cipp_edit_room_mailboxProWriteEdit properties of an existing room mailbox via POST /api/EditRoomMailbox — display name, capacity, booking settings, calendar processing, location, and accessibility.
cipp_list_contact_templatesFreeRead-onlyList CIPP contact templates from the CIPP template store via GET /api/ListContactTemplates.
cipp_list_contactsFreeRead-onlyList all mail contacts in a tenant.
cipp_list_equipmentFreeRead-onlyList all equipment mailboxes in a tenant.
cipp_list_room_listsFreeRead-onlyList room lists (groups of rooms) in a tenant.
cipp_list_roomsFreeRead-onlyList all room mailboxes in a tenant.
cipp_remove_contactProDestructiveRemove a mail contact via POST /api/RemoveContact.
cipp_remove_contact_templateProDestructivePermanently delete a CIPP contact template via POST /api/RemoveContactTemplates.

[CIPP] Create a new mail contact in a tenant directory via POST /api/AddContact. Mail contacts are external people that appear in the GAL but cannot sign in. Verified against CIPP-API master @df3738d: CIPP reads the destination tenant from the BODY key 'tenantid' only — the query string is never consulted for this endpoint — and this tool seeds it from tenantFilter. The body keys CIPP actually reads are 'displayName' (becomes both DisplayName and Name), 'email' (becomes ExternalEmailAddress), 'firstName', 'lastName', 'Title', 'Company', 'StreetAddress', 'City', 'State' (becomes StateOrProvince), 'PostalCode', 'CountryOrRegion', 'phone', 'mobilePhone', 'website' (becomes WebPage), 'mailTip' and 'hidefromGAL'; anything else is ignored. Empty/whitespace values are SKIPPED only for the second-pass Set-Contact fields ('Title', 'Company', 'StreetAddress', 'City', 'State', 'PostalCode', 'CountryOrRegion', 'phone', 'mobilePhone', 'website') and 'mailTip'; 'displayName', 'email', 'firstName' and 'lastName' are handed to New-MailContact UNCONDITIONALLY, so a blank one is sent as-is. WARNING — hidefromGAL is a TRUTHINESS test upstream ([bool]$value), so ANY non-empty string, INCLUDING the string "false", evaluates true and HIDES the contact from the Global Address List. Use the typed hideFromGal parameter, which sends a real JSON boolean.

ParamTypeRequiredDefaultDescription
displayNamestringyesDisplay name for the contact. Sent as 'displayName'; CIPP uses it for both the New-MailContact DisplayName and Name.
emailstringyesExternal email address for the contact. Sent as 'email'; CIPP uses it as the New-MailContact ExternalEmailAddress (the mail-routing target).
fieldsJsonstringnonullOptional additional fields as a JSON object, merged into the body LAST so its keys override the typed parameters above. Keys CIPP reads: 'firstName', 'lastName', 'mobilePhone', 'phone', 'website', 'mailTip', 'Title', 'Company', 'StreetAddress', 'City', 'State', 'PostalCode', 'CountryOrRegion'. Keys are passed through VERBATIM — never rewritten. WARNING: do NOT pass 'hidefromGAL' here as a string — CIPP truthiness-tests it, so "false" HIDES the contact; use the typed hideFromGal parameter (real JSON boolean) instead. 'tenantid' here would override the tenant this tool seeded from tenantFilter.
hideFromGalbooleannonullHide the contact from the Global Address List. Sent as a REAL JSON boolean on 'hidefromGAL'. Use this parameter rather than passing hidefromGAL through fieldsJson: CIPP evaluates the value as [bool]$value, so the STRING "false" would HIDE the contact. Omit to leave the contact visible (CIPP only ever sets HiddenFromAddressListsEnabled when the value is truthy; it never un-hides).
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Sent in the body as 'tenantid' — the ONLY place CIPP reads the destination tenant for this endpoint — and also on the query string, which CIPP ignores here. Use cipp_list_tenants to discover available tenants.

[CIPP] Create a new CIPP contact template via POST /api/AddContactTemplates. Stores the contact attributes for later bulk deployment (via cipp_deploy_contact_templates) across tenants. Not tenant-scoped — this is the CIPP-internal template store. CIPP body keys are MOSTLY camelCase per spec: businessPhone, city, companyName, country, displayName, email, firstName, hidefromGAL (boolean — note specific casing 'hidefromGAL'), jobTitle, lastName, mailTip, mobilePhone, postalCode, state, streetAddress, website. Display name and email are typically required for the underlying contact.

ParamTypeRequiredDefaultDescription
fieldsJsonstringyesContact template fields as a JSON object — the entire request body. Spec body keys (case-sensitive — preserve EXACTLY): 'displayName' (camelCase string — typically required), 'email' (camelCase string — external email address; typically required), 'firstName' (camelCase string), 'lastName' (camelCase string), 'companyName' (camelCase string), 'jobTitle' (camelCase string), 'businessPhone' (camelCase string), 'mobilePhone' (camelCase string), 'mailTip' (camelCase string), 'website' (camelCase string), 'streetAddress' (camelCase string), 'city' (camelCase string), 'state' (camelCase string), 'postalCode' (camelCase string), 'country' (camelCase string), 'hidefromGAL' (boolean — NOTE the specific casing: lowercase 'hidefrom' + uppercase 'GAL'). Pass the full intended template body verbatim.

[CIPP] Create a new equipment mailbox for a bookable resource (projector, vehicle, conference phone, etc.) via POST /api/AddEquipmentMailbox. Equipment mailboxes are bookable through Outlook calendaring. Verified against CIPP-API master @df3738d: CIPP reads the destination tenant from the BODY only — spelled 'tenantID' in the upstream source, but the casing is NOT load-bearing (CIPP re-serializes the request so every body lookup is case-insensitive; AddRoomMailbox spells the same key 'tenantid' and either spelling works on either endpoint) — and reads exactly THREE mailbox properties — 'username' (becomes New-Mailbox Name), 'displayName' and 'userPrincipalName' (becomes PrimarySmtpAddress, which is what determines the mailbox address). A 'domain' key is NEVER READ by this endpoint. After creation CIPP blocks sign-in for the new mailbox; a failure there is reported inside Results but still returns HTTP 200. A create failure returns HTTP 403, not 500.

ParamTypeRequiredDefaultDescription
additionalFieldsJsonstringnonullForward-compatibility escape hatch: a JSON object whose properties are merged into the request body LAST, overriding any of the typed parameters above. Use only for new CIPP fields not yet exposed as typed parameters. Keys are passed through VERBATIM — never rewritten.
displayNamestringyesDisplay name for the equipment (e.g., 'Projector - Room 101'). Sent as 'displayName'.
domainstringnonullIGNORED BY CIPP — Invoke-AddEquipmentMailbox.ps1 never reads a 'domain' property, so whatever is passed here is serialized and discarded. It does NOT control the mailbox's SMTP domain; that comes from userPrincipalName. Optional and safe to omit. Sent, when supplied, as the LabelValue object 'domain' = {label,value}.
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Used by the connector to route the request via the query string, which THIS endpoint ignores — the body 'tenantID' below is the only tenant selector CIPP reads. Use cipp_list_tenants to discover available tenants.
tenantIdstringyesTenant id (GUID) for the destination tenant. Sent as the body field 'tenantID' — the ONLY tenant selector this endpoint reads (the upstream source spells it 'tenantID' where AddRoomMailbox spells it 'tenantid', but that difference is NOT load-bearing: CIPP reads the body case-insensitively).
userPrincipalNamestringyesUPN for the new equipment mailbox (e.g., 'projector1@contoso.com'). Sent as 'userPrincipalName'; CIPP uses it as the New-Mailbox PrimarySmtpAddress, so this is what actually sets the mailbox's address.
usernamestringyesMailbox alias / local-part before @ (e.g., 'projector1'). Sent as 'username'; CIPP uses it as the New-Mailbox Name.

[CIPP] Create a new room list (group of rooms by building/floor/location) via POST /api/AddRoomList. Room lists group rooms so users can browse them by category in Outlook's room finder. CIPP body uses camelCase + a LabelValue 'primDomain' object. tenantFilter is sent on BOTH the query string AND the body.

ParamTypeRequiredDefaultDescription
fieldsJsonstringyesRoom list configuration as a JSON object. CIPP body keys (mostly camelCase, exact spec casing): 'displayName' (string, REQUIRED — name of the room list shown in Outlook), 'username' (string — mailbox alias / local-part for the list itself), 'tenantid' (string — tenant id GUID; ALL-LOWERCASE per spec), 'primDomain' (LabelValue {label, value} where value is the accepted domain to use, e.g. {"label":"contoso.com","value":"contoso.com"}). Note: there is no spec field for the initial member list — add rooms afterwards via cipp_edit_room_list.
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Sent on the query string and added to the body as 'tenantFilter' (camelCase). Use cipp_list_tenants to discover available tenants.

[CIPP] Create a new room mailbox via POST /api/AddRoomMailbox. Room mailboxes are bookable through Outlook calendaring. Verified against CIPP-API master @df3738d. TENANT: CIPP reads the destination tenant from the BODY key 'tenantid' (all-lowercase) ONLY (`$Tenant = $Request.Body.tenantid`); there is no query-string read in the file, so the TenantFilter query argument is inert. This tool therefore seeds 'tenantid' from tenantFilter, and the optional tenantId parameter overrides it. CIPP reads exactly FOUR mailbox properties: 'username' (becomes New-Mailbox Name), 'displayName', 'userPrincipalName' (becomes PrimarySMTPAddress — this, NOT any domain field, determines the mailbox address) and 'ResourceCapacity'. A 'domain' key is accepted by the schema but NEVER READ by this endpoint. After creation CIPP blocks sign-in for the new mailbox; a failure there is reported inside Results but still returns HTTP 200.

ParamTypeRequiredDefaultDescription
additionalFieldsJsonstringnonullForward-compatibility escape hatch: a JSON object whose properties are merged into the request body LAST, overriding any of the typed parameters above (including the seeded 'tenantid'). Use only for new CIPP fields not yet exposed as typed parameters. Keys are passed through VERBATIM — never rewritten.
displayNamestringyesDisplay name for the room (e.g., 'Conference Room A - 2nd Floor'). Sent as 'DisplayName'.
domainstringnonullIGNORED BY CIPP — Invoke-AddRoomMailbox.ps1 never reads a 'domain' property, so whatever is passed here is serialized and discarded. It does NOT control the mailbox's SMTP domain; that comes from userPrincipalName. Retained only for forward compatibility. Sent, when supplied, as the LabelValue object 'domain': {"label":<domain>,"value":<domain>}.
resourceCapacitystringnonullResource capacity (number of seats). Sent as 'ResourceCapacity'. CIPP only whitespace-checks the value before handing it to New-Mailbox, so pass it as a string such as '10'.
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Sent in the body as 'tenantid' — the ONLY tenant selector this endpoint reads — and also on the query string, which CIPP ignores here. Use cipp_list_tenants to discover available tenants.
tenantIdstringnonullOPTIONAL override for the destination tenant, sent as the body key 'tenantid'. Leave unset in normal use: the tool already seeds 'tenantid' from tenantFilter, which is the only tenant selector this endpoint reads. Supply a tenant id (GUID) here only when it must differ from tenantFilter.
userPrincipalNamestringnonullUser principal name (UPN) for the room mailbox (e.g., 'roomA@contoso.com'). Sent as 'userPrincipalName'; CIPP uses it as the New-Mailbox PrimarySMTPAddress, so this is what actually sets the mailbox's address. Required by Exchange.
usernamestringnonullMailbox alias / local-part (the part before @). Sent as 'username'; CIPP uses it as the New-Mailbox Name.

[CIPP] Bulk-deploy CIPP contact templates to create mail contacts across one or more tenants via POST /api/DeployContactTemplates. CROSS-TENANT: the targets come from the body key 'selectedTenants', never from tenantFilter. Verified against CIPP-API master @df3738d — BOTH body keys are ARRAYS OF OBJECTS, and CIPP reads only the '.value' of each element. 'selectedTenants': [{"value":"contoso.onmicrosoft.com"}, ...], where the literal value 'AllTenants' expands to EVERY tenant CIPP knows. 'TemplateList': [{"value":<the full contact-template object>}, ...] — the element's value is the whole template (its email/displayName/firstName/lastName/jobTitle/companyName/streetAddress/city/state/postalCode/country/businessPhone/mobilePhone/website/hidefromGAL/mailTip fields are read individually), NOT a template id, and '.Count' in the upstream guard is PowerShell's array length, not a field you supply. WARNING — a selectedTenants list that yields no '.value' is NOT an error: CIPP skips the deploy loop entirely and returns HTTP 200 with empty Results, so a silent zero-tenant deploy looks like success. The typed selectedTenants parameter exists to make that unreachable. Per-tenant behaviour: an existing contact with the same email is SKIPPED (reported, not overwritten), and per-tenant failures come back as text lines inside Results under HTTP 200.

ParamTypeRequiredDefaultDescription
fieldsJsonstringnonullForward-compatibility escape hatch: a JSON object merged into the request body LAST, so its keys override 'selectedTenants' and 'TemplateList' built above. Use only for new CIPP fields not yet exposed as typed parameters. Keys are passed through VERBATIM — never rewritten.
selectedTenantsstringyesREQUIRED. Comma-separated target tenants for this cross-tenant deploy — each a defaultDomainName (e.g. 'contoso.onmicrosoft.com,fabrikam.onmicrosoft.com'), or the single literal 'AllTenants' to deploy to EVERY tenant CIPP manages. The tool wraps each into the {"value":"..."} object CIPP requires; a plain string or bare array here would make CIPP deploy to ZERO tenants and still return HTTP 200. Use cipp_list_tenants to discover available tenants.
templatesJsonstringnonullOptional JSON ARRAY of full contact-template objects to deploy — e.g. the objects returned by cipp_list_contact_templates. The tool wraps each element into the {"value":<object>} envelope CIPP requires and sends them as 'TemplateList'. Each template's own fields are what get deployed: email, displayName, firstName, lastName, jobTitle, companyName, streetAddress, city, state, postalCode, country, businessPhone, mobilePhone, website, hidefromGAL, mailTip. Pass a raw array (not an object). If omitted, supply 'TemplateList' yourself through fieldsJson in its full [{"value":}] shape — CIPP throws when no template survives.

[CIPP] Edit an existing mail contact via POST /api/EditContact. Verified against CIPP-API master @df3738d. TENANT: CIPP reads the destination tenant from the BODY key 'tenantID' ONLY (`$TenantID = $Request.Body.tenantID`); the file contains no query-string read, so the TenantFilter query argument is inert. This tool therefore seeds 'tenantID' from tenantFilter, and the optional tenantId parameter overrides it. Body keys CIPP reads: 'ContactID' (REQUIRED — the Set-Contact / Set-MailContact Identity), 'displayName', 'email' (written to BOTH WindowsEmailAddress, the address the contacts list displays, AND ExternalEmailAddress, the actual mail-routing target), 'firstName', 'LastName', 'Title', 'StreetAddress', 'PostalCode', 'City', 'State' (becomes StateOrProvince), 'CountryOrRegion', 'Company', 'website' (becomes WebPage), 'mobilePhone', 'phone', 'hidefromGAL' (real JSON boolean, null-checked upstream — false genuinely UN-hides), 'mailTip'. Empty/whitespace values are SKIPPED for every field EXCEPT 'mobilePhone' and 'phone', which are presence-tracked: passing an empty string for those two CLEARS the value. WARNING — an edit carrying only ContactID writes NOTHING and still returns HTTP 200 'Successfully edited contact'; this tool refuses such a call up front rather than reporting that false success.

ParamTypeRequiredDefaultDescription
additionalFieldsJsonstringnonullForward-compatibility escape hatch: a JSON object whose properties are merged into the request body LAST, overriding any of the typed parameters above (including the seeded 'tenantID'). Use only for new CIPP fields not yet exposed as typed parameters. Keys are passed through VERBATIM — never rewritten.
citystringnonullCity. Sent as 'City' (PascalCase) per CIPP spec.
companystringnonullCompany name. Sent as 'Company' (PascalCase) per CIPP spec.
contactIdstringyesContact identifier to edit. Sent as 'ContactID'; CIPP passes it straight to Set-Contact / Set-MailContact as Identity. Use cipp_list_contacts to find valid IDs.
countryOrRegionstringnonullCountry or region. Sent as 'CountryOrRegion'. Empty/whitespace is ignored.
displayNamestringnonullNew display name. Sent as 'displayName'. Empty/whitespace is ignored by CIPP.
emailstringnonullEmail address. Sent as 'email'. CIPP writes it to BOTH WindowsEmailAddress (what the contacts list displays) and ExternalEmailAddress (where mail actually routes) — the two always move together. Empty/whitespace is ignored.
firstNamestringnonullFirst name. Sent as 'firstName' (camelCase).
hideFromGalbooleannonullHide the contact from the Global Address List. Sent as a real JSON boolean on 'hidefromGAL'. Unlike AddContact, this endpoint null-checks first and then casts, so false genuinely UN-hides the contact.
lastNamestringnonullLast name. Sent as 'LastName' (PascalCase) per CIPP spec.
mailTipstringnonullMailTip text shown to senders in Outlook. Sent as 'mailTip'. Empty/whitespace is ignored.
mobilePhonestringnonullMobile phone number. Sent as 'mobilePhone'. PRESENCE-TRACKED by CIPP: pass an empty string to CLEAR the existing value (it is written as $null), omit the parameter to leave it untouched.
phonestringnonullPhone number. Sent as 'phone'. PRESENCE-TRACKED by CIPP: pass an empty string to CLEAR the existing value (it is written as $null), omit the parameter to leave it untouched.
postalCodestringnonullPostal/zip code. Sent as 'PostalCode' (PascalCase) per CIPP spec.
statestringnonullState or province. Sent as 'State' (PascalCase) per CIPP spec.
streetAddressstringnonullStreet address. Sent as 'StreetAddress' (PascalCase) per CIPP spec.
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Sent in the body as 'tenantID' — the ONLY tenant selector this endpoint reads — and also on the query string, which CIPP ignores here. Use cipp_list_tenants to discover available tenants.
tenantIdstringnonullOPTIONAL override for the destination tenant, sent as the body key 'tenantID'. Leave unset in normal use: the tool already seeds 'tenantID' from tenantFilter, which is the only tenant selector this endpoint reads. Supply a tenant id (GUID) here only when it must differ from tenantFilter.
titlestringnonullJob title. Sent as 'Title' (PascalCase) per CIPP spec.
websitestringnonullWebsite URL. Sent as 'website' (CIPP writes it to WebPage). Empty/whitespace is ignored.

[CIPP] Modify an existing CIPP contact template via POST /api/EditContactTemplates. The template is identified by 'ContactTemplateID' in the body (not the same key as live contact's 'ContactID'). Not tenant-scoped — this edits the CIPP-internal template store. CIPP body keys are MOSTLY camelCase per spec; preserve casing exactly. Use cipp_list_contact_templates to find the ContactTemplateID.

ParamTypeRequiredDefaultDescription
fieldsJsonstringyesUpdated contact template fields as a JSON object — the entire request body. Spec body keys (case-sensitive — preserve EXACTLY): 'ContactTemplateID' (PascalCase string — REQUIRED, the template identifier from cipp_list_contact_templates), 'displayName' (camelCase string), 'email' (camelCase string), 'firstName' (camelCase string), 'lastName' (camelCase string), 'companyName' (camelCase string), 'jobTitle' (camelCase string), 'businessPhone' (camelCase string), 'mobilePhone' (camelCase string), 'mailTip' (camelCase string), 'website' (camelCase string), 'streetAddress' (camelCase string), 'city' (camelCase string), 'state' (camelCase string), 'postalCode' (camelCase string), 'country' (camelCase string), 'hidefromGAL' (boolean — NOTE the specific casing: lowercase 'hidefrom' + uppercase 'GAL'). Only include fields you want to change; ContactTemplateID is required.

[CIPP] Edit properties of an existing equipment mailbox via POST /api/EditEquipmentMailbox — display name, booking settings, calendar processing, location, and resource metadata. Endpoint is NOT tenant-scoped at the URL/query level; the destination tenant is identified by the body 'tenantID' field.

ParamTypeRequiredDefaultDescription
fieldsJsonstringyesEquipment mailbox update as a JSON object. CIPP body keys (exact spec casing): 'equipmentId' (string, REQUIRED — Exchange equipment mailbox identifier; camelCase), 'tenantID' (string — tenant id GUID; camel-ish with all-caps ID), 'userPrincipalName' (string), 'DisplayName' (string — PascalCase). Booking booleans (real JSON booleans, camelCase): 'allowConflicts', 'allowRecurringMeetings', 'forwardRequestsToDelegates', 'processExternalMeetingMessages', 'scheduleOnlyDuringWorkHours', 'hiddenFromAddressListsEnabled'. Booking strings (camelCase): 'automateProcessing', 'workDays', 'workHoursEndTime', 'workHoursStartTime', 'workingHoursTimeZone'. Booking integers: 'bookingWindowInDays', 'maximumDurationInMinutes'. Location fields (camelCase strings): 'city', 'company', 'countryOrRegion', 'department', 'phone', 'postalCode', 'stateOrProvince', 'streetAddress'. Other: 'tags' (array<string>). Note this endpoint uses largely camelCase, in contrast to EditRoomMailbox which uses PascalCase for its booking fields.

[CIPP] Modify an existing room list via POST /api/EditRoomList — rename it, add/remove member rooms, change owners, or update its delivery settings. Verified against CIPP-API master @df3738d. The tenant IS read from the body here ('$TenantId = $RoomListObj.tenantFilter'), which this tool seeds. MEMBERS vs OWNERS ARE NOT THE SAME SHAPE — this is the trap: 'AddMember'/'RemoveMember' elements may be plain UPN/email strings (CIPP falls back with `if ($Member.value) { $Member.value } else { $Member }`), but 'AddOwner'/'RemoveOwner' have NO string fallback — CIPP reads only `$Owner.addedFields.id`, else `$Owner.value`. A plain string there resolves to $null, which is then appended to the owner list and written back with Set-DistributionGroup -ManagedBy, CORRUPTING the room list's ownership. Use the typed addOwners/removeOwners parameters, which build the objects for you. WARNING: this endpoint ALWAYS returns HTTP 200 — every per-operation failure appears only as a text line inside the Results array, so read Results rather than trusting the status code.

ParamTypeRequiredDefaultDescription
addOwnersstringnonullOptional comma-separated list of owners to ADD (UPN/email each, e.g. 'a@contoso.com,b@contoso.com'). The tool wraps each into the {"value":"<upn>"} object CIPP requires — a bare string in this array would otherwise write a NULL into ManagedBy. Supply owners through fieldsJson's 'AddOwner' instead only if you need the {addedFields:} directory-object-id form.
fieldsJsonstringyesRoom list update as a JSON object, merged into the body LAST so its keys override the typed parameters below. CIPP body keys: 'groupId' (string, REQUIRED — the Exchange room list identifier, used as the Set-DistributionGroup Identity). 'AddMember'/'RemoveMember' — arrays whose elements may be plain UPN/email STRINGS or {value:<upn>} objects (both work). 'AddOwner'/'RemoveOwner' — arrays whose elements MUST be objects: {value:<upn>} or {addedFields:{id:<directory object id>}}. A plain string in an owner array writes a NULL into the room list's ManagedBy — prefer the typed addOwners/removeOwners parameters. Descriptive fields (strings): 'displayName', 'description', 'mailNickname' (this one becomes the group's Name). Other: 'allowExternal' (boolean; CIPP presence-checks it and writes RequireSenderAuthenticationEnabled = NOT allowExternal). Keys are passed through VERBATIM — never rewritten.
removeOwnersstringnonullOptional comma-separated list of owners to REMOVE (UPN/email each). Wrapped into the {"value":"<upn>"} objects CIPP requires. NOTE: CIPP drops an owner only when the value MATCHES one of the group's current ManagedBy entries, which Get-DistributionGroup returns as strings (often a name or DN rather than a UPN); a value matching nothing is silently ignored and reported nowhere.
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Sent on the query string and added to the body as 'tenantFilter', which is where CIPP actually reads it for this endpoint. Use cipp_list_tenants to discover available tenants.

[CIPP] Edit properties of an existing room mailbox via POST /api/EditRoomMailbox — display name, capacity, booking settings, calendar processing, location, and accessibility. Endpoint is NOT tenant-scoped at the URL/query level; the destination tenant is identified by the body 'tenantID' field. CIPP body uses MOSTLY PascalCase booking settings + camelCase location fields.

ParamTypeRequiredDefaultDescription
fieldsJsonstringyesRoom mailbox update as a JSON object. CIPP body keys (exact spec casing): 'roomId' (string, REQUIRED — Exchange room mailbox identifier; camelCase), 'tenantID' (string — tenant id GUID; camel-ish with all-caps ID), 'userPrincipalName' (string), 'DisplayName' (string — PascalCase). Booking booleans (real JSON booleans, all PascalCase): 'AddOrganizerToSubject', 'AllowConflicts', 'AllowRecurringMeetings', 'DeleteSubject', 'EnforceCapacity', 'ForwardRequestsToDelegates', 'ProcessExternalMeetingMessages', 'RemoveCanceledMeetings', 'ScheduleOnlyDuringWorkHours'. Booking strings (PascalCase): 'AutomateProcessing', 'WorkDays', 'WorkHoursEndTime', 'WorkHoursStartTime', 'WorkingHoursTimeZone'. Booking integers: 'BookingWindowInDays', 'MaximumDurationInMinutes'. Location/AV fields (camelCase strings): 'audioDeviceName', 'building', 'city', 'countryOrRegion', 'displayDeviceName', 'floorLabel', 'phone', 'postalCode', 'state', 'street', 'videoDeviceName'. Location integers: 'capacity', 'floor'. Accessibility booleans (camelCase): 'hiddenFromAddressListsEnabled', 'isWheelChairAccessible'. Other: 'tags' (array<string>).

[CIPP] List CIPP contact templates from the CIPP template store via GET /api/ListContactTemplates. Returns template IDs and contact attribute payloads for reuse via cipp_deploy_contact_templates. Not tenant-scoped. The spec defines an optional 'id' query parameter; the underlying client wrapper does not currently surface it, so this tool always returns the full list.

[CIPP] List all mail contacts in a tenant. Returns display name, email address, and contact type for external contacts in the directory.

ParamTypeRequiredDefaultDescription
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Use cipp_list_tenants to discover available tenants.

[CIPP] List all equipment mailboxes in a tenant. Equipment mailboxes represent bookable resources like projectors, vehicles, or conference phones.

ParamTypeRequiredDefaultDescription
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Use cipp_list_tenants to discover available tenants.

[CIPP] List room lists (groups of rooms) in a tenant. Room lists organize meeting rooms by floor, building, or location.

ParamTypeRequiredDefaultDescription
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Use cipp_list_tenants to discover available tenants.

[CIPP] List all room mailboxes in a tenant. Returns room name, email address, capacity, and booking settings.

ParamTypeRequiredDefaultDescription
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Use cipp_list_tenants to discover available tenants.

[CIPP] Remove a mail contact via POST /api/RemoveContact. Permanently deletes the contact entry. Verified against CIPP-API master @df3738d: 'GUID' is the ONLY identifier — CIPP passes it straight to Remove-MailContact as Identity. 'Mail' is NOT a lookup key: it is interpolated into the success string ('Deleted <mail>') and nothing else, so a mail-only call would send Identity = $null and fail with HTTP 400. contactId is therefore required here. The tenant is read from the query string first, falling back to the body ('$Request.Query.tenantFilter ?? $Request.Body.tenantFilter'); this tool sends it in both places.

ParamTypeRequiredDefaultDescription
additionalFieldsJsonstringnonullForward-compatibility escape hatch: a JSON object whose properties are merged into the request body LAST, overriding any of the typed parameters above. Use only for new CIPP fields not yet exposed as typed parameters. Keys are passed through VERBATIM — never rewritten.
contactIdstringyesContact identifier (GUID) to remove — REQUIRED. Sent as 'GUID'; CIPP uses it as the Remove-MailContact Identity, and it is the only value that can identify the contact. Use cipp_list_contacts to find valid IDs.
mailstringnonullMail address of the contact being removed. Sent as 'Mail'. COSMETIC ONLY — CIPP uses it solely to compose the success message 'Deleted <mail>' and NEVER to look up the contact; it cannot substitute for contactId. Safe to omit.
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Sent both on the query string as tenantFilter and in the body as 'tenantFilter'; CIPP reads the query value first and falls back to the body. Use cipp_list_tenants to discover available tenants.

[CIPP] Permanently delete a CIPP contact template via POST /api/RemoveContactTemplates. Spec body: ; query also accepts ID. Destructive: removes the template only — already-deployed tenant contacts remain intact. Not tenant-scoped. Use cipp_list_contact_templates to find the template ID.

ParamTypeRequiredDefaultDescription
additionalFieldsJsonstringnonullForward-compatibility escape hatch: a JSON object whose properties are merged into the request body LAST, overriding any of the typed parameters above. Use only for new CIPP fields that this tool does not yet expose. Keys are passed verbatim — caller is responsible for exact spec casing.
idstringyesTemplate identifier to remove. Sent in the body as 'ID' (PascalCase, all-caps) per CIPP spec. Use cipp_list_contact_templates to find valid IDs.

Transport & Spam

ToolPlanAccessSummary
cipp_add_connection_filterProDestructiveConfigure a connection filter policy via POST /api/AddConnectionFilter.
cipp_add_connection_filter_templateProWriteSave a connection filter policy as a reusable CIPP template via POST /api/AddConnectionFilterTemplate.
cipp_add_edit_transport_ruleProDestructiveAdd or edit a full Exchange transport rule via POST /api/AddEditTransportRule (the unified high-fidelity authoring endpoint, distinct from the thin cipp_add_transport_rule and…
cipp_add_ex_connector_templateProWriteSave an Exchange connector configuration as a reusable CIPP template via POST /api/AddExConnectorTemplate.
cipp_add_exchange_connectorProDestructiveCreate a new Exchange connector for mail routing via POST /api/AddExConnector.
cipp_add_quarantine_policyProDestructiveCreate a new quarantine policy via POST /api/AddQuarantinePolicy.
cipp_add_spam_filterProDestructiveCreate a new spam filter (hosted content filter) policy AND its matching rule via POST /api/AddSpamFilter.
cipp_add_spam_filter_templateProWriteSave a spam filter policy as a reusable CIPP template via POST /api/AddSpamFilterTemplate.
cipp_add_tenant_allow_blockProDestructiveAdd entries to the Tenant Allow/Block List via POST /api/AddTenantAllowBlockList.
cipp_add_transport_ruleProDestructiveCreate OR update an Exchange transport rule (mail flow rule) via POST /api/AddTransportRule — it is an UPSERT: CIPP runs Get-TransportRule per tenant and, when a rule whose Identity equals the parsed…
cipp_add_transport_rule_templateProWriteSave a transport (mail flow) rule as a reusable CIPP template via POST /api/AddTransportTemplate.
cipp_edit_anti_phishing_filterProDestructiveEnable or disable an anti-phishing RULE via POST /api/EditAntiPhishingFilter.
cipp_edit_exchange_connectorProDestructiveEnable or disable an existing Exchange connector via POST /api/EditExConnector.
cipp_edit_malware_filterProDestructiveEnable or disable a malware filter RULE via POST /api/EditMalwareFilter.
cipp_edit_quarantine_policyProDestructiveEdit an existing quarantine policy via POST /api/EditQuarantinePolicy.
cipp_edit_safe_attachments_filterProDestructiveEnable or disable a Safe Attachments (ATP) RULE via POST /api/EditSafeAttachmentsFilter.
cipp_edit_spam_filterProDestructiveEnable or disable a spam filter (hosted content filter) RULE via POST /api/EditSpamFilter.
cipp_edit_transport_ruleProDestructiveEnable or disable an existing Exchange transport rule via POST /api/EditTransportRule.
cipp_list_connection_filter_templatesFreeRead-onlyList saved connection filter policy templates.
cipp_list_connection_filtersFreeRead-onlyList connection filter policies for a tenant.
cipp_list_ex_connector_templatesFreeRead-onlyList saved Exchange connector templates from the CIPP template store via GET /api/ListExConnectorTemplates.
cipp_list_exchange_connectorsFreeRead-onlyList all Exchange connectors for a tenant including inbound and outbound connectors, their type, status, and routing configuration.
cipp_list_quarantineFreeRead-onlyList quarantined email messages for a tenant.
cipp_list_quarantine_policyFreeRead-onlyList quarantine policies for a tenant via POST /api/ListQuarantinePolicy.
cipp_list_spam_filter_templatesFreeRead-onlyList saved spam filter policy templates.
cipp_list_spam_filtersFreeRead-onlyList spam filter policies for a tenant including policy name, spam action thresholds, allowed/blocked senders, and content filtering settings.
cipp_list_tenant_allow_blockFreeRead-onlyList one tenant's Tenant Allow/Block List entries (blocked and allowed senders, URLs and file hashes) via GET /api/ListTenantAllowBlockList.
cipp_list_transport_rulesFreeRead-onlyList all Exchange transport rules (mail flow rules) for a tenant.
cipp_list_transport_rules_templatesFreeRead-onlyList saved transport (mail flow) rule templates from the CIPP template store via GET /api/ListTransportRulesTemplates.
cipp_manage_quarantineProDestructiveManage a quarantined message via POST /api/ExecQuarantineManagement.
cipp_remove_connection_filter_templateProDestructivePermanently delete a connection filter policy template from the CIPP template store via POST /api/RemoveConnectionfilterTemplate (note: 'Connectionfilter' lowercase 'f' in the URL — preserve the…
cipp_remove_ex_connector_templateProDestructivePermanently delete an Exchange connector template from the CIPP template store via POST /api/RemoveExConnectorTemplate.
cipp_remove_exchange_connectorProDestructiveRemove an Exchange connector via POST /api/RemoveExConnector.
cipp_remove_quarantine_policyProDestructiveRemove a quarantine policy via POST /api/RemoveQuarantinePolicy.
cipp_remove_spam_filterProDestructiveRemove a spam filter rule and its policy via POST /api/RemoveSpamfilter (note: 'Spamfilter' lowercase 'f' in the URL — preserve the underlying CIPP path).
cipp_remove_spam_filter_templateProDestructivePermanently delete a spam filter policy template from the CIPP template store via POST /api/RemoveSpamfilterTemplate (note: 'Spamfilter' lowercase 'f' in the URL — preserve the underlying CIPP path).
cipp_remove_tenant_allow_blockProDestructiveRemove entries from the Tenant Allow/Block List via POST /api/RemoveTenantAllowBlockList.
cipp_remove_transport_ruleProDestructiveRemove an Exchange transport rule via POST /api/RemoveTransportRule.
cipp_remove_transport_rule_templateProDestructivePermanently delete a transport rule template from the CIPP template store via POST /api/RemoveTransportRuleTemplate.

[CIPP] Configure a connection filter policy via POST /api/AddConnectionFilter. Despite the 'Add' name this always runs Set-HostedConnectionFilterPolicy — it MODIFIES an existing policy (Exchange Online ships exactly one, named 'Default') rather than creating a new one. PowerShellCommand is NOT a PowerShell command — it is a JSON-SERIALIZED PARAMETER OBJECT string that CIPP pipes through ConvertFrom-Json; a command string fails to parse. CIPP then RENAMES that object's 'name' property to 'identity' and drops name, GUID and comments, so the policy you target is whatever you put in 'name' (normally 'Default'). Target tenants come from the body's selectedTenants array only — there is no tenantFilter on this endpoint and no query string is sent. Unlike the transport-rule and connector endpoints this one applies NO permitted-tenant narrowing of its own. WARNING: 'TemplateList' does not appear anywhere in this endpoint and is silently ignored.

ParamTypeRequiredDefaultDescription
fieldsJsonstringyesConnection filter configuration as JSON object merged into the request body. Spec body keys (case-sensitive — preserve EXACTLY): PowerShellCommand (PascalCase string — REQUIRED; a JSON-SERIALIZED OBJECT of Set-HostedConnectionFilterPolicy parameters, NOT a command line. Example: "{"name":"Default","IPAllowList":["1.2.3.4"],"EnableSafeList":false}". Its 'name' property is rewritten by CIPP into the cmdlet's Identity and then removed, so 'name' selects the policy to modify — normally 'Default'), selectedTenants (camelCase — the tool seeds this from tenantFilter; if you supply it here it MUST be the array-of-objects shape [{"value":"contoso.onmicrosoft.com"}] because CIPP evaluates ($Request.Body.selectedTenants).value, and a plain or comma-separated string yields zero tenants and a silent HTTP 200 no-op). Keys CIPP does NOT read here: TemplateList (ignored), tenantFilter (ignored). Anything you put here is merged LAST and overrides the tool's typed parameters.
tenantFilterstringyesTarget tenant's default domain name (e.g., contoso.onmicrosoft.com) — REQUIRED. Sent in the body as selectedTenants: [{"value":"<tenant>"}], which is the only place this endpoint looks for its tenant set; with none it returns HTTP 200 with an empty Results array and changes nothing. Accepts a comma- or semicolon-separated list to apply to several tenants at once. Use cipp_list_tenants to discover available tenants.

[CIPP] Save a connection filter policy as a reusable CIPP template via POST /api/AddConnectionFilterTemplate. This is a CIPP-internal template store, not a tenant action — no tenantFilter is required. PowerShellCommand is NOT a PowerShell command: CIPP pipes it through ConvertFrom-Json and stores the resulting object, so it must be a JSON-SERIALIZED POLICY OBJECT string. A command line fails to parse and the call returns HTTP 500 'Failed to create Connection Filter Template'. The stored template's name is the 'name' property INSIDE that JSON — the top-level 'name' key appears only in the success message.

ParamTypeRequiredDefaultDescription
additionalFieldsJsonstringnonullForward-compatibility escape hatch: a JSON object whose properties are merged into the request body LAST, overriding any of the typed parameters above. Use only for new CIPP fields that this tool does not yet expose. Keys are passed verbatim — caller is responsible for exact spec casing.
namestringyesTemplate name for CIPP's confirmation message. Sent in the body as 'name' (camelCase per spec). NOTE: this is NOT the name the template is stored under — CIPP takes that from the 'name' property inside the powerShellCommand JSON, so set it there too.
powerShellCommandstringyesThe connection filter policy as a JSON-SERIALIZED OBJECT string (NOT a command line) — CIPP runs it through ConvertFrom-Json. Example: "{"name":"Branch office allowlist","EnableSafeList":false,"IPAllowList":["1.2.3.4"],"IPBlockList":[]}". CIPP stores the parsed object WHOLE: every property you put in this JSON is written to the template, with no allow-list. The Name / EnableSafeList / IPAllowList / IPBlockList shortlist applies only to the fallback branch CIPP takes when PowerShellCommand is ABSENT, which this tool never takes, and it is NOT the set of fields that survive to the tenant either: deploying a connection filter forwards every property of this object except GUID, comments and name (name is re-sent as 'identity') straight to Set-HostedConnectionFilterPolicy. So keep any extra policy properties (AdminDisplayName, DirectoryBasedEdgeBlockMode, ...) you want applied — they are both stored and deployed. 'comments' is stored on the template but dropped before the cmdlet, so never carry policy meaning in it. Sent in the body as 'PowerShellCommand' (PascalCase per spec).

[CIPP] Add or edit a full Exchange transport rule via POST /api/AddEditTransportRule (the unified high-fidelity authoring endpoint, distinct from the thin cipp_add_transport_rule and cipp_edit_transport_rule). The body schema mirrors the Exchange Online New-TransportRule / Set-TransportRule cmdlets — almost every condition/exception/action parameter is a separate body key. Casing is PascalCase for Exchange parameters, camelCase for CIPP control flags. Set ruleId to update an existing rule; omit to create. Most string fields accept comma-separated lists or single values; *MemberOf fields accept group identifiers; *MatchesPatterns fields accept regex patterns.

ParamTypeRequiredDefaultDescription
fieldsJsonstringyesTransport rule definition as JSON object merged into the request body. Spec body keys (case-sensitive — preserve EXACTLY). Identity & meta: ruleId (camelCase string — set to update an existing rule, omit to create), Name (PascalCase string), Comments (PascalCase string), Priority (PascalCase number), State (PascalCase string), Mode (PascalCase object), Enabled (PascalCase boolean), StopRuleProcessing (PascalCase boolean), ActivationDate (PascalCase string date-time), ExpiryDate (PascalCase string date-time), applyToAllMessages (camelCase boolean), conditionType (camelCase array<string>), exceptionType (camelCase array<string>), actionType (camelCase array<string>), Count (PascalCase string), value (camelCase string). Sender/recipient conditions: From (PascalCase string), FromMemberOf (PascalCase string), FromScope (PascalCase string), FromAddressContainsWords (PascalCase string), FromAddressMatchesPatterns (PascalCase string), SentTo (PascalCase string), SentToMemberOf (PascalCase string), SentToScope (PascalCase string), AnyOfToHeader (PascalCase string), AnyOfToHeaderMemberOf (PascalCase string), AnyOfToCcHeader (PascalCase string), AnyOfToCcHeaderMemberOf (PascalCase string), AnyOfCcHeader (PascalCase string), AnyOfCcHeaderMemberOf (PascalCase string), AnyOfRecipientAddressContainsWords (PascalCase string), AnyOfRecipientAddressMatchesPatterns (PascalCase string), RecipientAddressContainsWords (PascalCase string), RecipientAddressMatchesPatterns (PascalCase string), RecipientDomainIs (PascalCase string), SenderDomainIs (PascalCase string), SenderIpRanges (PascalCase string), SenderAddressLocation (PascalCase object). Subject/body/header conditions: SubjectContainsWords (PascalCase string), SubjectMatchesPatterns (PascalCase string), SubjectOrBodyContainsWords (PascalCase string), SubjectOrBodyMatchesPatterns (PascalCase string), HeaderContainsWords (PascalCase string), HeaderContainsWordsMessageHeader (PascalCase string), HeaderMatchesPatterns (PascalCase string), HeaderMatchesPatternsMessageHeader (PascalCase string). Attachment conditions: AttachmentContainsWords (PascalCase string), AttachmentExtensionMatchesWords (PascalCase string), AttachmentHasExecutableContent (PascalCase string), AttachmentIsPasswordProtected (PascalCase string), AttachmentIsUnsupported (PascalCase string), AttachmentMatchesPatterns (PascalCase string), AttachmentNameMatchesPatterns (PascalCase string), AttachmentProcessingLimitExceeded (PascalCase string), AttachmentPropertyContainsWords (PascalCase string), AttachmentSizeOver (PascalCase string). Other conditions: MessageSizeOver (PascalCase string), MessageTypeMatches (PascalCase string), SCLOver (PascalCase string), WithImportance (PascalCase string). Exceptions (every condition has an ExceptIf-prefixed twin — preserve casing and PascalCase): ExceptIfFrom, ExceptIfFromMemberOf, ExceptIfFromScope, ExceptIfFromAddressContainsWords, ExceptIfFromAddressMatchesPatterns, ExceptIfSentTo, ExceptIfSentToMemberOf, ExceptIfSentToScope, ExceptIfAnyOfToHeader, ExceptIfAnyOfToHeaderMemberOf, ExceptIfAnyOfToCcHeader, ExceptIfAnyOfToCcHeaderMemberOf, ExceptIfAnyOfCcHeader, ExceptIfAnyOfCcHeaderMemberOf, ExceptIfAnyOfRecipientAddressContainsWords, ExceptIfAnyOfRecipientAddressMatchesPatterns, ExceptIfRecipientAddressContainsWords, ExceptIfRecipientAddressMatchesPatterns, ExceptIfRecipientDomainIs, ExceptIfSenderDomainIs, ExceptIfSenderIpRanges, ExceptIfSubjectContainsWords, ExceptIfSubjectMatchesPatterns, ExceptIfSubjectOrBodyContainsWords, ExceptIfSubjectOrBodyMatchesPatterns, ExceptIfHeaderContainsWords, ExceptIfHeaderContainsWordsMessageHeader, ExceptIfHeaderMatchesPatterns, ExceptIfHeaderMatchesPatternsMessageHeader, ExceptIfAttachmentContainsWords, ExceptIfAttachmentExtensionMatchesWords, ExceptIfAttachmentHasExecutableContent, ExceptIfAttachmentIsPasswordProtected, ExceptIfAttachmentIsUnsupported, ExceptIfAttachmentMatchesPatterns, ExceptIfAttachmentNameMatchesPatterns, ExceptIfAttachmentProcessingLimitExceeded, ExceptIfAttachmentPropertyContainsWords, ExceptIfAttachmentSizeOver, ExceptIfMessageSizeOver, ExceptIfMessageTypeMatches, ExceptIfSCLOver, ExceptIfWithImportance. Actions: ApplyClassification (PascalCase string), ApplyHtmlDisclaimerFallbackAction (PascalCase object), ApplyHtmlDisclaimerLocation (PascalCase object), ApplyHtmlDisclaimerText (PascalCase string), ApplyOME (PascalCase string), BlindCopyTo (PascalCase string), CopyTo (PascalCase string), DeleteMessage (PascalCase string), GenerateIncidentReport (PascalCase string), GenerateNotification (PascalCase string), IncidentReportContent (PascalCase array of LabelValue {label, value} objects — preserve {label,value} shape verbatim per item), ModerateMessageByManager (PascalCase string), ModerateMessageByUser (PascalCase string), PrependSubject (PascalCase string), Quarantine (PascalCase string), RedirectMessageTo (PascalCase string), RejectMessageEnhancedStatusCode (PascalCase string), RejectMessageReasonText (PascalCase string), RemoveHeader (PascalCase string), RouteMessageOutboundConnector (PascalCase string), SetAuditSeverity (PascalCase object), SetHeaderName (PascalCase string), SetHeaderValue (PascalCase string), SetSCL (PascalCase string). Tool injects tenantFilter — do NOT add it here.
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Sent in the body as tenantFilter (camelCase, required). Use cipp_list_tenants to discover available tenants.

[CIPP] Save an Exchange connector configuration as a reusable CIPP template via POST /api/AddExConnectorTemplate. This is a CIPP-internal template store, not a tenant action — no tenantFilter is required. The spec body is intentionally minimal: {cippconnectortype:string, name:string}. Use cipp_list_ex_connector_templates to view stored templates.

ParamTypeRequiredDefaultDescription
additionalFieldsJsonstringnonullForward-compatibility escape hatch: a JSON object whose properties are merged into the request body LAST, overriding any of the typed parameters above. Use only for new CIPP fields that this tool does not yet expose. Keys are passed verbatim — caller is responsible for exact spec casing.
cippConnectorTypestringyesConnector type identifier. Sent in the body as 'cippconnectortype' (all-lowercase per spec). Common values: 'Inbound' / 'Outbound' (matches the Exchange connector kind being captured).
namestringyesTemplate name shown in CIPP. Sent in the body as 'name' (camelCase per spec).

[CIPP] Create a new Exchange connector for mail routing via POST /api/AddExConnector. PowerShellCommand is MANDATORY and is NOT a PowerShell command — it is a JSON-SERIALIZED PARAMETER OBJECT string that CIPP pipes through ConvertFrom-Json; CIPP additionally reads a cippConnectorType property OUT of that parsed object to build the cmdlet name New-<cippConnectorType>connector, so a connector cannot be created without it. GUID, cippConnectorType and SenderRewritingEnabled are stripped before the parameters reach Exchange, and a 'comment' property inside the JSON defaults to the literal 'no comment', and is run through CIPP's %variable% text replacement per tenant only when it actually contains a '%' (so the defaulted comment never is). Target tenants come from the body's selectedTenants array only — the tenantFilter query argument is never read. WARNING: 'TemplateList' does not appear anywhere in this endpoint; the templateLabel/templateValue parameters below are inert and cannot substitute for PowerShellCommand.

ParamTypeRequiredDefaultDescription
additionalFieldsJsonstringnonullForward-compatibility escape hatch: a JSON object whose properties are merged into the request body LAST, overriding any of the typed parameters above. Keys are passed verbatim — caller is responsible for exact spec casing. WARNING: a selectedTenants key supplied here must itself use the [{"value":"tenant"}] array-of-objects shape, or the call becomes a silent no-op.
commentstringnonullINERT at the top level — CIPP reads 'comment' only off the object parsed from powerShellCommand, and defaults it to 'no comment' when absent there. Put the comment inside the powerShellCommand JSON instead; this key is sent for CIPP UI payload parity and is never read.
powerShellCommandstringnonullREQUIRED in practice: the connector definition as a JSON-SERIALIZED OBJECT string (not a command line). Must include cippConnectorType ('Inbound' or 'Outbound'), which CIPP reads to build the cmdlet name New-<type>connector, plus the New-InboundConnector/New-OutboundConnector parameters. Example: "{"cippConnectorType":"Inbound","Name":"Inbound from Partner","SenderDomains":"*.partner.com"}". Put any audit comment inside this JSON as a 'comment' property. Omitting this parameter leaves the cmdlet name as 'New-connector', which does not exist.
selectedTenantsstringnonullOptional: comma- or semicolon-separated tenant default domain names to deploy the connector to, replacing the single tenantFilter tenant. The tool converts your list into CIPP's required array-of-objects shape ([{"value":"a.onmicrosoft.com"},{"value":"b.onmicrosoft.com"}]) — a bare string would dereference to nothing and produce a silent HTTP 200 no-op.
templateLabelstringnonullINERT — CIPP's AddExConnector endpoint contains no reference to TemplateList and never looks a template up. Retained only for payload parity with the CIPP UI; it cannot replace powerShellCommand. Deploy from a saved template by reading it with cipp_list_ex_connector_templates and passing its stored body as powerShellCommand.
templateValuestringnonullINERT — see templateLabel. CIPP never reads TemplateList on this endpoint.
tenantFilterstringyesTarget tenant's default domain name (e.g., contoso.onmicrosoft.com). Sent in the body as selectedTenants: [{"value":"<tenant>"}] — the ONLY place this endpoint looks for its tenant set (the tenantFilter query argument is sent for connector consistency and ignored). When the CIPP API client is scope-restricted, CIPP narrows the list against the caller's permitted tenants by defaultDomainName — an unrestricted client is not narrowed at all — so supply the tenant's default domain name rather than a tenant id/GUID. Overridden by the selectedTenants parameter when that is supplied. Use cipp_list_tenants to discover available tenants.

[CIPP] Create a new quarantine policy via POST /api/AddQuarantinePolicy. CIPP maps the body onto an EndUserQuarantinePermissions set and calls Set-CIPPQuarantinePolicy with action 'New' per tenant. Target tenants come from the body's selectedTenants array only — there is no tenantFilter on this endpoint and no query string is sent. The permission fields must be REAL JSON BOOLEANS (true/false, unquoted). A quoted "true"/"false" is NOT silently coerced — it is a hard PowerShell parameter-binding failure: the four end-user permissions are splatted onto Convert-QuarantinePermissionsValue's [int] parameters and QuarantineNotification / IncludeMessagesFromBlockedSenderAddress onto Set-CIPPQuarantinePolicy's [bool] parameters, and both reject strings. The endpoint still answers HTTP 200, but Results then carries one 'Failed to create Quarantine policy <name> for tenant <t> - Cannot convert value ...' line per selected tenant and NO policy is created anywhere. Omitting one of the four end-user permissions is safe: an absent value binds to 0, i.e. the permission is DENIED. ReleaseActionPreference is read as `.value ?? ` itself, so either a plain string or a {label,value} object works; only 'Release' and 'RequestRelease' are meaningful (they set PermissionToRelease / PermissionToRequestRelease, and any other value leaves both false). WARNING: the literal 'AllTenants' anywhere in selectedTenants makes CIPP discard your selection and create the policy in EVERY tenant it manages. WARNING: 'TemplateList' does not appear anywhere in this endpoint and is silently ignored.

ParamTypeRequiredDefaultDescription
fieldsJsonstringyesQuarantine policy configuration as JSON object merged into the request body. Spec body keys (case-sensitive — preserve EXACTLY), split by what CIPP actually requires: Name (PascalCase string — REQUIRED; the policy name, bound to a Mandatory + ValidateNotNullOrEmpty parameter, so an absent value fails the create for every selected tenant), QuarantineNotification (PascalCase — REQUIRED real JSON boolean; it binds the Mandatory [bool] ESNEnabled, so an absent value binds $null and fails every tenant), IncludeMessagesFromBlockedSenderAddress (PascalCase — REQUIRED IN PRACTICE real JSON boolean; the entrypoint forwards this key unconditionally, so its [bool] parameter's default is unreachable and an absent body value binds $null and fails the same way), AllowSender / BlockSender / Delete / Preview (PascalCase — OPTIONAL real JSON booleans granting end-users that quarantine permission; an absent value binds to 0, i.e. the permission is DENIED), ReleaseActionPreference (OPTIONAL — string or {label,value} object; 'Release' grants PermissionToRelease, 'RequestRelease' grants PermissionToRequestRelease, anything else grants neither), selectedTenants (camelCase — the tool seeds this from tenantFilter; if you supply it here it MUST be the array-of-objects shape [{"value":"contoso.onmicrosoft.com"}] because CIPP evaluates ($Request.body.selectedTenants).value, and a plain or comma-separated string yields zero tenants and a silent HTTP 200 no-op). Quote NONE of the booleans: a quoted "true"/"false" is not coerced upstream, it is a hard parameter-binding failure reported per tenant inside an HTTP 200 — see the tool description. Keys CIPP does NOT read here: TemplateList (ignored), tenantFilter (ignored). Anything you put here is merged LAST and overrides the tool's typed parameters.
tenantFilterstringyesTarget tenant's default domain name (e.g., contoso.onmicrosoft.com) — REQUIRED. Sent in the body as selectedTenants: [{"value":"<tenant>"}], which is the only place this endpoint looks for its tenant set; with none it returns HTTP 200 with an empty Results array and creates nothing. Accepts a comma- or semicolon-separated list for several tenants. WARNING: the literal 'AllTenants' fans the policy out to EVERY tenant CIPP manages. Use cipp_list_tenants to discover available tenants.

[CIPP] Create a new spam filter (hosted content filter) policy AND its matching rule via POST /api/AddSpamFilter. Per tenant CIPP runs New-HostedContentFilterPolicy with your parameters, then New-HostedContentFilterRule bound to that policy with recipientdomainis set to every accepted domain, Enabled true, and Priority taken from the top-level body Priority key. PowerShellCommand is NOT a PowerShell command — it is a JSON-SERIALIZED PARAMETER OBJECT string that CIPP pipes through ConvertFrom-Json (GUID and comments are stripped); a command string fails to parse. The policy NAME is the 'name' property INSIDE that JSON, not a top-level key. Target tenants come from the body's selectedTenants array only — there is no tenantFilter on this endpoint and no query string is sent. WARNING: 'TemplateList' does not appear anywhere in this endpoint and is silently ignored.

ParamTypeRequiredDefaultDescription
fieldsJsonstringyesSpam filter configuration as JSON object merged into the request body. Spec body keys (case-sensitive — preserve EXACTLY): PowerShellCommand (PascalCase string — REQUIRED; a JSON-SERIALIZED OBJECT of New-HostedContentFilterPolicy parameters, NOT a command line. Example: "{"name":"Strict Spam","SpamAction":"Quarantine","HighConfidenceSpamAction":"Quarantine"}". Its 'name' property names both the policy and the rule CIPP creates), Priority (PascalCase — read from the TOP LEVEL of the body and passed to New-HostedContentFilterRule as the rule's Priority), selectedTenants (camelCase — the tool seeds this from tenantFilter; if you supply it here it MUST be the array-of-objects shape [{"value":"contoso.onmicrosoft.com"}] because CIPP evaluates ($Request.body.selectedTenants).value, and a plain or comma-separated string yields zero tenants and a silent HTTP 200 no-op). Keys CIPP does NOT read here: TemplateList (ignored), top-level name (ignored — the name lives inside PowerShellCommand), tenantFilter (ignored). Anything you put here is merged LAST and overrides the tool's typed parameters.
tenantFilterstringyesTarget tenant's default domain name (e.g., contoso.onmicrosoft.com) — REQUIRED. Sent in the body as selectedTenants: [{"value":"<tenant>"}], which is the only place this endpoint looks for its tenant set; with none it returns HTTP 200 with an empty Results array and creates nothing. Accepts a comma- or semicolon-separated list to deploy to several tenants at once. When the CIPP API client is scope-restricted, CIPP narrows the list against the caller's permitted tenants by defaultDomainName — an unrestricted client is not narrowed at all — so supply the tenant's default domain name rather than a tenant id/GUID. Use cipp_list_tenants to discover available tenants.

[CIPP] Save a spam filter policy as a reusable CIPP template via POST /api/AddSpamFilterTemplate. This is a CIPP-internal template store, not a tenant action — no tenantFilter is required. PowerShellCommand is NOT a PowerShell command: CIPP pipes it through ConvertFrom-Json and stores the resulting object, so it must be a JSON-SERIALIZED POLICY OBJECT string. A command line fails to parse and the call returns HTTP 500 'Failed to create Spam Filter Template'. The stored template's name is the 'name' property INSIDE that JSON — the top-level 'name' key appears only in the success message. Pair with cipp_list_spam_filter_templates to view stored templates.

ParamTypeRequiredDefaultDescription
additionalFieldsJsonstringnonullForward-compatibility escape hatch: a JSON object whose properties are merged into the request body LAST, overriding any of the typed parameters above. Use only for new CIPP fields that this tool does not yet expose. Keys are passed verbatim — caller is responsible for exact spec casing.
namestringyesTemplate name for CIPP's confirmation message. Sent in the body as 'name' (camelCase per spec). NOTE: this is NOT the name the template is stored under — CIPP takes that from the 'name' property inside the powerShellCommand JSON, so set it there too.
powerShellCommandstringyesThe spam filter policy as a JSON-SERIALIZED OBJECT string (NOT a command line) — CIPP runs it through ConvertFrom-Json. Example: "{"name":"Strict Spam","SpamAction":"Quarantine","HighConfidenceSpamAction":"Quarantine","BulkThreshold":6}". Its 'name' property becomes the stored template's name. Sent in the body as 'PowerShellCommand' (PascalCase per spec). Accepts any New-HostedContentFilterPolicy parameter (AllowedSenderDomains, BlockedSenders, QuarantineRetentionPeriod, EndUserSpamNotification*, and so on).

[CIPP] Add entries to the Tenant Allow/Block List via POST /api/AddTenantAllowBlockList. listType and listMethod are PLAIN STRINGS, not LabelValue objects: CIPP flattens listType with a [string] cast (so an object would arrive as a useless type name) and uses listMethod as the cmdlet PARAMETER NAME it sets to true (New-TenantAllowBlockListItems -Allow / -Block), so an object there corrupts the cmdParams outright. The tenant goes into a body key named tenantID (NOT tenantFilter — preserve casing verbatim); CIPP accepts either a plain string or a object there, and the literal 'AllTenants' fans the entry out to EVERY tenant. Note NoExpiration wins over RemoveAfter when both are true, and RemoveAfter means exactly 45 days.

ParamTypeRequiredDefaultDescription
additionalFieldsJsonstringnonullForward-compatibility escape hatch: a JSON object whose properties are merged into the request body LAST, overriding any of the typed parameters above. Use only for new CIPP fields that this tool does not yet expose. TWO keys are re-validated on the MERGED body: 'listMethod' and 'tenantID', neither of which may be overridden here — for either one, an override that disagrees with the typed parameter that seeded it, or a second differently-cased spelling of the key, is refused before dispatch. 'listMethod' because CIPP uses this value as the NAME of the cmdlet parameter it sets to true (New-TenantAllowBlockListItems -Allow / -Block), so an override merged here turns a BLOCK into an ALLOW — allowlisting the sender, URL or file hash you asked to block. 'tenantID' because it is the ONLY place this endpoint reads the tenant (the tenantFilter query argument is ignored upstream), so an override would write to a tenant this call was not reviewed for, and the literal 'AllTenants' would fan the entry out to EVERY tenant CIPP manages — supply the tenant through the typed 'tenantFilter' parameter. Every other key keeps its last-wins behavior. Keys are passed verbatim — caller is responsible for exact spec casing.
entriesstringyesEntries to add (sender addresses, URLs, or file hashes). A comma- or semicolon-separated string is correct here — CIPP splits it on [,;] and trims each value.
listMethodstringyesAction to apply — REQUIRED. Sent as the PLAIN STRING 'Allow' or 'Block'; CIPP uses this value as the NAME of the cmdlet parameter it sets to true, so any other value (or an object, or omitting it) breaks the request. Values outside Allow/Block are refused before the call.
listTypestringyesType of entry being added. Sent as a PLAIN STRING (CIPP casts it with [string], so a {label,value} object would flatten to a type name and match nothing). Exchange's New-TenantAllowBlockListItems -ListType accepts: 'Sender', 'Url', 'FileHash', 'IP'.
noExpirationbooleannonullIf true, the entry never expires. Sent as a real JSON boolean in the body key NoExpiration. Takes precedence over removeAfter when both are true.
notesstringnonullFree-form notes recorded against the entry in Microsoft 365. Sent in the body as notes (lowercase); CIPP casts it with [string].
removeAfterbooleannonullIf true, CIPP sets the entry's RemoveAfter to 45 days (the value is fixed upstream, not configurable). Ignored when noExpiration is true.
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Used as the tenantFilter query-string parameter AND emitted in the body as tenantID, which is where CIPP actually reads it. WARNING: the literal 'AllTenants' makes CIPP apply the entry to EVERY tenant it manages. Use cipp_list_tenants to discover available tenants.

[CIPP] Create OR update an Exchange transport rule (mail flow rule) via POST /api/AddTransportRule — it is an UPSERT: CIPP runs Get-TransportRule per tenant and, when a rule whose Identity equals the parsed payload's 'name' already exists, runs Set-TransportRule against it instead of New-TransportRule. PowerShellCommand is NOT a PowerShell command — it is a JSON-SERIALIZED PARAMETER OBJECT string that CIPP pipes through ConvertFrom-Json and hands to the cmdlet as -cmdParams; a command string fails to parse. The rule NAME is the 'name' property INSIDE that JSON (it is also the create-vs-update key) — the top-level body 'name' this tool sends is never read by CIPP. Target tenants come from the body's selectedTenants array only; the tenantFilter query argument is ignored by this endpoint. GUID, HasSenderOverride, ExceptIfHasSenderOverride, MessageContainsDataClassifications and ExceptIfMessageContainsDataClassifications are stripped from your payload, as is UseLegacyRegex on the update branch. WARNING: 'TemplateList' does not appear anywhere in this endpoint — passing it is silently ignored. Use cipp_add_edit_transport_rule for typed condition/action authoring.

ParamTypeRequiredDefaultDescription
fieldsJsonstringyesRule configuration as JSON object merged into the request body. Spec body keys (case-sensitive — preserve EXACTLY): PowerShellCommand (PascalCase string — REQUIRED; a JSON-SERIALIZED OBJECT of New-TransportRule/Set-TransportRule parameters, NOT a command line. Example: "{"name":"Block External","FromScope":"NotInOrganization","PrependSubject":"[EXT]"}". CIPP pipes it through ConvertFrom-Json, drops null-valued properties, and passes the rest as -cmdParams; its 'name' property is the rule name AND the upsert key), selectedTenants (camelCase — array of objects, [{"value":"contoso.onmicrosoft.com"}]; the tool seeds this from tenantFilter, and any value you supply here MUST use that array-of-objects shape because CIPP evaluates ($Request.body.selectedTenants).value — a plain or comma-separated string yields zero tenants and a silent HTTP 200 no-op). Keys CIPP does NOT read on this endpoint: TemplateList (ignored), PSObject (ignored), top-level name (ignored), tenantFilter (ignored). Anything you put here is merged LAST and overrides the tool's typed parameters.
namestringyesRule name. WARNING: CIPP does NOT read this top-level body key — the rule name that is actually created, and that the create-vs-update lookup matches on, is the 'name' property inside the PowerShellCommand JSON. Set 'name' there; this parameter is retained for CIPP UI payload parity only.
tenantFilterstringyesTarget tenant's default domain name (e.g., contoso.onmicrosoft.com). Sent in the body as selectedTenants: [{"value":"<tenant>"}] — the ONLY place this endpoint looks for its tenant set (it never reads the tenantFilter query argument, which is still sent for connector consistency). When the CIPP API client is scope-restricted, CIPP narrows this list against the caller's permitted tenants by defaultDomainName — an unrestricted client is not narrowed at all — so supply the tenant's default domain name rather than a tenant id/GUID. Use cipp_list_tenants to discover available tenants.

[CIPP] Save a transport (mail flow) rule as a reusable CIPP template via POST /api/AddTransportTemplate. This is a CIPP-internal template store, not a tenant action — no tenantFilter is required. PowerShellCommand is NOT a PowerShell command: CIPP pipes it through ConvertFrom-Json and stores the resulting object, so it must be a JSON-SERIALIZED RULE OBJECT string. A command line fails to parse and the call returns HTTP 403 Forbidden with Results 'Failed to create Transport Rule Template: <error>'. Unlike its spam-filter and connection-filter siblings, whose catch arms return 500, this endpoint's catch arm sets Forbidden. READ THE BODY, not the status, to tell the two 403s apart: a 403 whose body is that Results envelope is a malformed PowerShellCommand payload and NOT a roles problem, while a 403 whose body is instead a bare access-denied message IS a permissions problem — CIPP answers every access denial (missing role, out-of-range IP, endpoint blocked by a custom role) with the same Forbidden status, and this endpoint's required role is Exchange.TransportRule.ReadWrite. The stored template's name is the 'name' property INSIDE that JSON — the top-level 'name' key appears only in the success message. Pair with cipp_list_transport_rules_templates to view stored templates.

ParamTypeRequiredDefaultDescription
additionalFieldsJsonstringnonullForward-compatibility escape hatch: a JSON object whose properties are merged into the request body LAST, overriding any of the typed parameters above. Use only for new CIPP fields that this tool does not yet expose. Keys are passed verbatim — caller is responsible for exact spec casing.
namestringyesTemplate name for CIPP's confirmation message. Sent in the body as 'name' (camelCase per spec). NOTE: this is NOT the name the template is stored under — CIPP takes that from the 'name' property inside the powerShellCommand JSON, so set it there too.
powerShellCommandstringyesThe transport rule as a JSON-SERIALIZED OBJECT string (NOT a command line) — CIPP runs it through ConvertFrom-Json. Example: "{"name":"Block External","FromScope":"NotInOrganization","PrependSubject":"[EXT]"}". Any New-TransportRule parameter is accepted (conditions, ExceptIf* exceptions and actions). Sent in the body as 'PowerShellCommand' (PascalCase per spec).

[CIPP] Enable or disable an anti-phishing RULE via POST /api/EditAntiPhishingFilter. The body is narrow: {RuleName (PascalCase — passed as Identity), State (PascalCase), tenantFilter (camelCase, required)}. CIPP runs Enable-AntiPhishRule or Disable-AntiPhishRule; there is no rename and no policy-setting edit on this endpoint. CRITICAL: State must be exactly 'Enable' or 'Disable' (PowerShell's switch is case-insensitive, so 'enable'/'ENABLE' also work). 'Enabled' and 'Disabled' are NOT accepted — they hit the switch's default arm, which throws 'Invalid state', so the call fails loudly with HTTP 500 and nothing changes. To author full anti-phishing policy bodies use the Microsoft Graph / Exchange Online cmdlets directly.

ParamTypeRequiredDefaultDescription
fieldsJsonstringyesFilter settings as JSON object merged into the request body. Spec body keys (case-sensitive — preserve EXACTLY): RuleName (PascalCase string — the anti-phishing rule name, used as Identity), State (PascalCase string — MUST be exactly 'Enable' or 'Disable'. 'Enabled'/'Disabled' are rejected by CIPP's switch default arm with 'Invalid state' and HTTP 500). Example: {"RuleName":"Office365 AntiPhish Default","State":"Enable"}. Tool injects tenantFilter — do NOT add it here.
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Sent as the tenantFilter query-string parameter AND injected into the body as tenantFilter (camelCase, required). Use cipp_list_tenants to discover available tenants.

[CIPP] Enable or disable an existing Exchange connector via POST /api/EditExConnector. This endpoint does NOT edit connector configuration: it reads only tenantFilter, State, GUID and Type, then runs Set-<Type>Connector with @. It cannot change routing, TLS settings, smart hosts or address space — there is no connectorId or smartHosts key here — and CIPP offers no edit endpoint for those; recreate the connector with cipp_add_exchange_connector instead. DANGEROUS-DEFAULT NOTE: CIPP computes Enabled as `State -eq 'Enable'`, so ANY other value — and an ABSENT State — resolves to false and DISABLES the connector while still returning HTTP 200 with "Set Connector <guid> to <your value>". This tool therefore requires an explicit state, maps it to CIPP's literal, and refuses anything ambiguous. Use cipp_list_exchange_connectors to discover the GUID and type.

ParamTypeRequiredDefaultDescription
fieldsJsonstringnonullForward-compatibility escape hatch: a JSON object whose properties are merged into the request body LAST, overriding any of the typed parameters above. Two keys are re-validated on the MERGED body: 'State' and 'Type', neither of which may be overridden here — an override that disagrees with the typed 'state'/'type' parameter, or a second, differently-cased spelling of either key, is refused before dispatch. That closes what used to be a live trap: upstream computes `$State = ($ConnectorState -eq 'Enable')` with no else branch, so a 'State' merged here reading 'Enabled' WOULD have disabled the connector while reporting success. Keys are passed verbatim — caller is responsible for exact spec casing.
guidstringyesGUID (Identity) of the connector to enable/disable. Sent in the body as GUID (all-caps — preserve casing). Use cipp_list_exchange_connectors to find valid values.
statestringyesTarget state — REQUIRED. Accepted: 'Enable'/'Enabled'/'on'/'true' to ENABLE, or 'Disable'/'Disabled'/'off'/'false' to DISABLE; any other value is refused before the call rather than silently disabling the connector. The tool sends CIPP's own literal ('Enable' or 'Disable') in the body as State.
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Sent as the tenantFilter query-string parameter AND in the body (CIPP accepts either). Use cipp_list_tenants to discover available tenants.
typestringyesConnector direction — 'Inbound' or 'Outbound'. Sent in the body as Type (PascalCase). CIPP interpolates it into the cmdlet name Set-<Type>Connector, so any other value asks Exchange for a cmdlet that does not exist; values outside Inbound/Outbound are refused before the call.

[CIPP] Enable or disable a malware filter RULE via POST /api/EditMalwareFilter. The body is narrow: {RuleName (PascalCase — passed as Identity), State (PascalCase), tenantFilter (camelCase, required)}. CIPP runs Enable-MalwareFilterRule or Disable-MalwareFilterRule; there is no rename and no policy-setting edit on this endpoint. CRITICAL: State must be exactly 'Enable' or 'Disable' (case-insensitive). 'Enabled' and 'Disabled' are NOT accepted — they hit the switch's default arm, which throws 'Invalid state', so the call fails loudly with HTTP 500 and nothing changes. To author full malware policy bodies (file-type lists, ZAP, etc.) use the Microsoft Graph / Exchange Online cmdlets directly.

ParamTypeRequiredDefaultDescription
fieldsJsonstringyesFilter settings as JSON object merged into the request body. Spec body keys (case-sensitive — preserve EXACTLY): RuleName (PascalCase string — the malware filter rule name, used as Identity), State (PascalCase string — MUST be exactly 'Enable' or 'Disable'. 'Enabled'/'Disabled' are rejected by CIPP's switch default arm with 'Invalid state' and HTTP 500). Example: {"RuleName":"Default Malware Rule","State":"Enable"}. Tool injects tenantFilter — do NOT add it here.
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Sent as the tenantFilter query-string parameter AND injected into the body as tenantFilter (camelCase, required). Use cipp_list_tenants to discover available tenants.

[CIPP] Edit an existing quarantine policy via POST /api/EditQuarantinePolicy. CRITICAL: this endpoint's body uses TenantFilter (PascalCase) — NOT camelCase tenantFilter. Most boolean-like fields are STRING ('true'/'false') not bool — preserve string casing as the spec lists them (string type, not boolean). Query also accepts tenantFilter (camelCase) and Type.

ParamTypeRequiredDefaultDescription
fieldsJsonstringyesQuarantine policy settings as JSON object merged into the request body. Spec body keys (case-sensitive — preserve EXACTLY, note these are mostly TYPE STRING per the spec, not boolean): Identity (PascalCase string — the quarantine policy GUID/Identity to edit; required), Name (PascalCase string), Action (PascalCase string), AllowSender (PascalCase string — 'true' or 'false'), BlockSender (PascalCase string — 'true' or 'false'), Delete (PascalCase string — 'true' or 'false'), Preview (PascalCase string — 'true' or 'false'), QuarantineNotification (PascalCase string), IncludeMessagesFromBlockedSenderAddress (PascalCase string), OrganizationBrandingEnabled (PascalCase string), EndUserSpamNotificationFrequency (PascalCase string — frequency per Exchange Online spec, e.g., '04:00:00'), EndUserSpamNotificationCustomFromAddress (PascalCase string), ReleaseActionPreference (PascalCase string), TenantFilter (PascalCase string — the tenant key on this endpoint is PascalCase, NOT camelCase). Tool injects camelCase tenantFilter; pass TenantFilter explicitly here.
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Sent as the tenantFilter query-string parameter. The body's tenant field is TenantFilter (PascalCase) — pass it explicitly via fieldsJson if camelCase tenantFilter (which the tool injects) is silently dropped on this endpoint. Use cipp_list_tenants to discover available tenants.

[CIPP] Enable or disable a Safe Attachments (ATP) RULE via POST /api/EditSafeAttachmentsFilter. The body is narrow: {RuleName (PascalCase — passed as Identity), State (PascalCase), tenantFilter (camelCase, required)}. CIPP runs Enable-SafeAttachmentRule or Disable-SafeAttachmentRule; there is no rename and no policy-setting edit on this endpoint. CRITICAL: State must be exactly 'Enable' or 'Disable' (case-insensitive). 'Enabled' and 'Disabled' are NOT accepted — they hit the switch's default arm, which throws 'Invalid state', so the call fails loudly with HTTP 500 and nothing changes. To author full Safe Attachments policy bodies (detonation mode, redirect URL, action) use the Microsoft Graph / Exchange Online cmdlets directly.

ParamTypeRequiredDefaultDescription
fieldsJsonstringyesFilter settings as JSON object merged into the request body. Spec body keys (case-sensitive — preserve EXACTLY): RuleName (PascalCase string — the Safe Attachments rule name, used as Identity), State (PascalCase string — MUST be exactly 'Enable' or 'Disable'. 'Enabled'/'Disabled' are rejected by CIPP's switch default arm with 'Invalid state' and HTTP 500). Example: {"RuleName":"Standard Preset Security Policy","State":"Enable"}. Tool injects tenantFilter — do NOT add it here.
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Sent as the tenantFilter query-string parameter AND injected into the body as tenantFilter (camelCase, required). Use cipp_list_tenants to discover available tenants.

[CIPP] Enable or disable a spam filter (hosted content filter) RULE via POST /api/EditSpamFilter. This endpoint changes NOTHING but the rule's on/off state: it builds @ and runs Enable-HostedContentFilterRule or Disable-HostedContentFilterRule. There is no rename and no policy-setting edit here — 'name' is purely the SELECTOR for the rule to toggle. tenantFilter is read from the QUERY STRING only (this tool sends it there); name is read from the query or the body; state is read from the BODY ONLY — upstream writes `$State = $State ?? $Request.Body.state`, whose left operand is an uninitialised local, so the query half never fires. This tool sends both name and state in the body. Use cipp_list_spam_filters to discover policy/rule names. DANGEROUS-DEFAULT NOTE: CIPP enables only when state equals its literal 'enable' and treats every other value — including 'Enabled' — as DISABLE, returning HTTP 200 with "Set Spamfilter rule <name> to <your value>". Disabling a spam filter is a security-relevant change that the response text disguises, so this tool maps your intent to the exact literal and refuses anything ambiguous.

ParamTypeRequiredDefaultDescription
additionalFieldsJsonstringnonullForward-compatibility escape hatch: a JSON object whose properties are merged into the request body LAST, overriding any of the typed parameters above. One key is re-validated on the MERGED body: 'state', which may NOT be overridden here — an override that disagrees with the typed 'state' parameter, or a second, differently-cased spelling of the key, is refused before dispatch. That closes what used to be a live trap: upstream picks Disable-HostedContentFilterRule for every value that is not exactly 'enable', so a 'state' merged here reading 'Enabled' WOULD have turned the spam filter OFF behind a 200 that echoed your own word. Keys are passed verbatim — caller is responsible for exact spec casing.
namestringyesName of the spam filter RULE to enable/disable — REQUIRED, and a SELECTOR rather than a new value. CIPP passes it as Identity to Enable-/Disable-HostedContentFilterRule; no rename or display-name update happens on this endpoint. Sent in the body as name. Use cipp_list_spam_filters to discover names.
statestringyesTarget state of the rule — REQUIRED. Accepted: 'Enable'/'Enabled'/'on'/'true' to ENABLE, or 'Disable'/'Disabled'/'off'/'false' to DISABLE; any other value is REFUSED before the call rather than silently disabling the filter. The tool sends CIPP's own literal ('enable' or 'disable') in the body as state. It is required because CIPP reads an ABSENT state as $null, which is not its enable literal, and disables the filter while still returning HTTP 200.
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Sent as the tenantFilter query-string parameter — this endpoint reads the tenant from the QUERY ONLY and ignores any body tenantFilter. Use cipp_list_tenants to discover available tenants.

[CIPP] Enable or disable an existing Exchange transport rule via POST /api/EditTransportRule. This endpoint does NOT edit rule content — it reads only tenantFilter, guid and state, then runs Enable-TransportRule or Disable-TransportRule against the guid. Use cipp_add_edit_transport_rule to change a rule's conditions or actions. The rule is identified by its guid (NOT ruleId — preserve casing); use cipp_list_transport_rules to discover guids. DANGEROUS-DEFAULT NOTE: CIPP enables only when state equals its literal 'enable' and treats EVERY other value — including the plausible-looking 'Enabled' — as DISABLE, then returns HTTP 200 with "Set transport rule <guid> to <your value>", so the response cannot reveal the inversion. This tool therefore maps your intent to the exact literal and refuses anything ambiguous; omitting state entirely sends no state key, which CIPP also reads as disable.

ParamTypeRequiredDefaultDescription
additionalFieldsJsonstringnonullForward-compatibility escape hatch: a JSON object whose properties are merged into the request body LAST, overriding any of the typed parameters above. One key is re-validated on the MERGED body: 'state', which may NOT be overridden here — an override that disagrees with the typed 'state' parameter, or a second, differently-cased spelling of the key, is refused before dispatch. That closes what used to be a live trap: upstream runs `if ($State -eq 'enable') else ` with no else guard, so a 'state' merged here reading 'Enabled' WOULD have disabled the rule behind a 200 that echoed your own word. Keys are passed verbatim — caller is responsible for exact spec casing.
guidstringyesGUID of the transport rule to enable/disable. Sent in the body as guid. Use cipp_list_transport_rules to find valid GUIDs.
statestringyesTarget state of the rule — REQUIRED. Accepted: 'Enable'/'Enabled'/'on'/'true' to ENABLE, or 'Disable'/'Disabled'/'off'/'false' to DISABLE; any other value is REFUSED before the call rather than silently disabling the rule. The tool sends CIPP's own literal ('enable' or 'disable') in the body as state. It is required because CIPP reads an ABSENT state as $null, which is not its enable literal, and disables the rule while still returning HTTP 200.
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Sent as the tenantFilter query-string parameter AND in the body (CIPP accepts either). Use cipp_list_tenants to discover available tenants.

[CIPP] List saved connection filter policy templates. Returns template names and configured IP allow/block lists for reuse.

[CIPP] List connection filter policies for a tenant. Connection filters control which IP addresses are allowed or blocked from sending email to the tenant.

ParamTypeRequiredDefaultDescription
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Use cipp_list_tenants to discover available tenants.

[CIPP] List saved Exchange connector templates from the CIPP template store via GET /api/ListExConnectorTemplates. Returns template names, IDs, and connector configuration for reuse across tenants. Not tenant-scoped — these are CIPP-stored reusable templates. The spec defines an optional 'id' query parameter; the underlying client wrapper does not currently surface it, so this tool always returns the full list.

ParamTypeRequiredDefaultDescription
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Use cipp_list_tenants to discover available tenants.

[CIPP] List quarantined email messages for a tenant. Returns message subject, sender, recipient, quarantine reason, and received date.

ParamTypeRequiredDefaultDescription
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Use cipp_list_tenants to discover available tenants.

[CIPP] List quarantine policies for a tenant via POST /api/ListQuarantinePolicy. CRITICAL: this endpoint's body uses TenantFilter (PascalCase) — NOT camelCase tenantFilter. Spec body is minimal: {TenantFilter (PascalCase string)}. Query also accepts tenantFilter (camelCase) and Type. Returns policy names, end-user access settings, and notification configuration.

ParamTypeRequiredDefaultDescription
fieldsJsonstringnonullOptional filter configuration as JSON object merged into the request body. Spec body keys (case-sensitive — preserve EXACTLY): TenantFilter (PascalCase string — note PascalCase here, NOT camelCase; pass explicitly when camelCase tenantFilter from the tool is silently dropped). Pass null or empty object if no body customization is needed.
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Sent as the tenantFilter query-string parameter. The body's tenant field is TenantFilter (PascalCase) — pass it explicitly via fieldsJson if camelCase tenantFilter (which the tool injects) is silently dropped on this endpoint. Use cipp_list_tenants to discover available tenants.

[CIPP] List saved spam filter policy templates. Returns template names and configured settings for reuse across tenants.

ParamTypeRequiredDefaultDescription
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Use cipp_list_tenants to discover available tenants.

[CIPP] List one tenant's Tenant Allow/Block List entries (blocked and allowed senders, URLs and file hashes) via GET /api/ListTenantAllowBlockList. The tenant is required: CIPP binds it into a mandatory parameter and answers a 403 (not a permission problem) when it is missing. Pass 'AllTenants' for an estate-wide sweep — CIPP serves that from its cached reporting database rather than live Exchange, so entries added or removed since CIPP's last sync may not appear. Returns raw CIPP JSON.

ParamTypeRequiredDefaultDescription
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Use cipp_list_tenants to discover available tenants. Pass 'AllTenants' for every tenant at once, served from CIPP's cached report data.

[CIPP] List all Exchange transport rules (mail flow rules) for a tenant. Returns rule name, priority, state, conditions, and actions.

ParamTypeRequiredDefaultDescription
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Use cipp_list_tenants to discover available tenants.

[CIPP] List saved transport (mail flow) rule templates from the CIPP template store via GET /api/ListTransportRulesTemplates. Returns template names, IDs, and the PowerShell command they will run when deployed. Not tenant-scoped — these are CIPP-stored reusable templates. The spec defines an optional 'id' query parameter; the underlying client wrapper does not currently surface it, so this tool always returns the full list.

[CIPP] Manage a quarantined message via POST /api/ExecQuarantineManagement. tenantFilter, Identity and Type are read from the BODY (this endpoint reads no query string). Identity may be a single id string (the identity parameter) or an array of ids (the identitiesJson parameter) — supply exactly one of the two. Type selects the cmdlet: 'Delete' runs Delete-QuarantineMessage; every other value runs Release-QuarantineMessage, where 'Release' adds -ReleaseToAll and anything else ('Deny', 'Request', 'Approve') is passed as -ActionType, with 'Deny' additionally consuming the body's RecipientAddress as -User. AllowSender is a strict BOOLEAN FLAG, not a sender address: CIPP evaluates `$Request.Body.AllowSender -eq $true`, so it must be a real JSON true. The address to allowlist is a SEPARATE body key, SenderAddress, paired with PolicyName; when either is omitted and Identity is a single string, CIPP looks both up from the quarantined message itself. Use cipp_list_quarantine to discover Identity values.

ParamTypeRequiredDefaultDescription
additionalFieldsJsonstringnonullForward-compatibility escape hatch: a JSON object whose properties are merged into the request body LAST, overriding the typed parameters above — EXCEPT three keys, which are re-validated on the MERGED body and may not be changed here. (1) 'Type': an override that disagrees with the typed 'type' parameter is refused before dispatch, because it would run a different operation than the one this call was reviewed as — 'Delete' destroys the quarantined message and every other value releases it to the recipient — while a 'Type' naming the same verb is accepted and forwarded as written. (2) 'tenantFilter': the body is the only place this endpoint reads the tenant, so an override would act on a tenant this call was not reviewed for. (3) 'Identity': it names the message or messages acted on, so an override would release or destroy a different message than the reviewed one — pass several ids through the typed identitiesJson parameter instead. For all three, a second differently-cased spelling of the key is refused too: it does not replace the seeded key, it ships alongside it, and CIPP resolves body members without regard to case. Use this for new CIPP fields that this tool does not yet expose (e.g. RecipientAddress for a 'Deny'). Keys are passed verbatim — caller is responsible for exact spec casing.
allowSenderbooleannonullSet true to also add the sender to the tenant's hosted content filter allowed-senders list after the release. Sent as a REAL JSON BOOLEAN in the body key AllowSender, because CIPP tests `AllowSender -eq $true`, where every string except the literal "true" (case-insensitive) evaluates false — so a sender address put here silently skips the allowlisting. This flag does NOT carry the address; use senderAddress for that.
identitiesJsonstringnonullAct on SEVERAL quarantined messages in one call: a JSON ARRAY of identity strings (e.g. ["id1","id2"]), sent in the body as Identity IN PLACE OF the single-id string. This is the typed form of the array shape CIPP supports — it tests whether Identity is a string and passes anything else to Release-QuarantineMessage/Delete-QuarantineMessage as -Identities. Exactly one of identity and identitiesJson must be supplied, so pass an EMPTY identity when using this. NOTE: CIPP looks SenderAddress and PolicyName up from the message itself only when Identity is a single string, so supply both explicitly when allowSender is true on a multi-message call.
identitystringyesQuarantine message Identity — a SINGLE id, sent in the body as Identity (PascalCase). Use cipp_list_quarantine to find valid identities. To act on SEVERAL messages in one call, leave this empty and pass identitiesJson instead (CIPP switches to -Identities whenever Identity is not a string). Exactly one of identity and identitiesJson must be supplied; both, or neither, is refused before dispatch.
policyNamestringnonullOptional: the hosted content filter policy to add the sender to, sent in the body as PolicyName (PascalCase). Only consulted when allowSender is true. When omitted and identity is a single id, CIPP reads the policy name off the quarantined message.
senderAddressstringnonullOptional: the sender address to allowlist, sent in the body as SenderAddress (PascalCase). Only consulted when allowSender is true. When omitted and identity is a single id, CIPP reads the address off the quarantined message via Get-QuarantineMessage.
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Sent in the body as tenantFilter, which is the only place this endpoint reads it (the query argument is sent for connector consistency and ignored). Use cipp_list_tenants to discover available tenants.
typestringyesAction verb, sent in the body as Type (PascalCase). 'Delete' runs Delete-QuarantineMessage. 'Release' runs Release-QuarantineMessage -ReleaseToAll. 'Deny', 'Request' and 'Approve' run Release-QuarantineMessage with -ActionType set to that value; for 'Deny' CIPP also reads the body's RecipientAddress (comma/semicolon separated) as -User.

[CIPP] Permanently delete a connection filter policy template from the CIPP template store via POST /api/RemoveConnectionfilterTemplate (note: 'Connectionfilter' lowercase 'f' in the URL — preserve the underlying CIPP path). Spec body: . Destructive: removes the template only — tenant policies created from it remain intact. Use cipp_list_connection_filter_templates to find the template ID. Not tenant-scoped.

ParamTypeRequiredDefaultDescription
additionalFieldsJsonstringnonullForward-compatibility escape hatch: a JSON object whose properties are merged into the request body LAST, overriding any of the typed parameters above. Use only for new CIPP fields that this tool does not yet expose. Keys are passed verbatim — caller is responsible for exact spec casing.
idstringyesTemplate identifier to remove. Sent in the body as 'ID' (PascalCase, all-caps) per CIPP spec. Use cipp_list_connection_filter_templates to find valid IDs.

[CIPP] Permanently delete an Exchange connector template from the CIPP template store via POST /api/RemoveExConnectorTemplate. Spec body: ; query also accepts ID. Destructive: removes the template only — tenant connectors created from it remain intact. Use cipp_list_ex_connector_templates to find the template ID. Not tenant-scoped.

ParamTypeRequiredDefaultDescription
additionalFieldsJsonstringnonullForward-compatibility escape hatch: a JSON object whose properties are merged into the request body LAST, overriding any of the typed parameters above. Use only for new CIPP fields that this tool does not yet expose. Keys are passed verbatim — caller is responsible for exact spec casing.
idstringyesTemplate identifier to remove. Sent in the body as 'ID' (PascalCase, all-caps) per CIPP spec. Use cipp_list_ex_connector_templates to find valid IDs.

[CIPP] Remove an Exchange connector via POST /api/RemoveExConnector. CIPP reads tenantFilter, GUID and Type, then runs Remove-<Type>Connector with @. There is NO connectorId key — Type is mandatory because the cmdlet name is string-interpolated from it, and omitting it produces the non-existent cmdlet 'Remove-Connector'. Use cipp_list_exchange_connectors first to find the GUID and the connector's direction. WARNING: deleting a connector takes effect immediately and can break mail routing for the tenant.

ParamTypeRequiredDefaultDescription
fieldsJsonstringnonullForward-compatibility escape hatch: a JSON object whose properties are merged into the request body LAST, overriding any of the typed parameters above. One key is re-validated on the MERGED body: 'Type', which may NOT be overridden here — an override that disagrees with the typed 'type' parameter, or a second, differently-cased spelling of it, is refused before dispatch, because Type is half the cmdlet name and so decides WHICH connector is deleted. Keys are passed verbatim — caller is responsible for exact spec casing.
guidstringyesGUID (Identity) of the connector to remove. Sent in the body as GUID (all-caps — preserve casing). Use cipp_list_exchange_connectors to find valid values.
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Sent as the tenantFilter query-string parameter AND in the body (CIPP accepts either). Use cipp_list_tenants to discover available tenants.
typestringyesConnector direction — 'Inbound' or 'Outbound'. REQUIRED: CIPP interpolates it into the cmdlet name Remove-<Type>Connector, so omitting it or passing anything else asks Exchange for a cmdlet that does not exist. Values outside Inbound/Outbound are refused before the call. Sent in the body as Type (PascalCase).

[CIPP] Remove a quarantine policy via POST /api/RemoveQuarantinePolicy. CRITICAL: this endpoint's body uses TenantFilter (PascalCase) — NOT camelCase tenantFilter. Spec body: {Identity (PascalCase string — the quarantine policy Identity to remove), Name (PascalCase string), TenantFilter (PascalCase string)}. Query also accepts Identity, Name, and tenantFilter (camelCase). Use cipp_list_quarantine_policy first to find Identity.

ParamTypeRequiredDefaultDescription
fieldsJsonstringyesPolicy removal configuration as JSON object merged into the request body. Spec body keys (case-sensitive — preserve EXACTLY): Identity (PascalCase string — the quarantine policy Identity to remove), Name (PascalCase string — the quarantine policy display name), TenantFilter (PascalCase string — note PascalCase here, NOT camelCase). Tool injects camelCase tenantFilter; pass TenantFilter explicitly here.
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Sent as the tenantFilter query-string parameter. The body's tenant field is TenantFilter (PascalCase) — pass it explicitly via fieldsJson if camelCase tenantFilter (which the tool injects) is silently dropped on this endpoint. Use cipp_list_tenants to discover available tenants.

[CIPP] Remove a spam filter rule and its policy via POST /api/RemoveSpamfilter (note: 'Spamfilter' lowercase 'f' in the URL — preserve the underlying CIPP path). CIPP runs Remove-HostedContentFilterRule then Remove-HostedContentFilterPolicy, both with Identity set to the name. The tenant rides the QUERY STRING as 'tenantFilter' — upstream reads it there only (an upstream typo makes both sides of its ?? operator read the query) and accepts no body tenant key — while the policy name rides the body. Use cipp_list_spam_filters to find the policy name. WARNING: removing a spam filter policy may expose the tenant to increased spam/phishing, and this cannot be undone.

ParamTypeRequiredDefaultDescription
additionalFieldsJsonstringnonullForward-compatibility escape hatch: a JSON object whose properties are merged into the request body LAST, overriding the typed parameter above. Keys are passed verbatim — caller is responsible for exact spec casing. NOTE: adding 'tenantFilter' here does NOT help — CIPP never reads a body tenantFilter on this endpoint.
namestringyesName of the spam filter rule/policy to remove. Sent in the body as name (camelCase); CIPP reads `$Request.Query.name ?? $Request.Body.name`, so the body carries it correctly. Use cipp_list_spam_filters to discover names.
tenantFilterstringyesTarget tenant domain (e.g. contoso.onmicrosoft.com). Required. Sent as the query key 'tenantFilter' — the only place CIPP reads it on this endpoint. Use cipp_list_tenants to discover available tenants.

[CIPP] Permanently delete a spam filter policy template from the CIPP template store via POST /api/RemoveSpamfilterTemplate (note: 'Spamfilter' lowercase 'f' in the URL — preserve the underlying CIPP path). Spec body: . Destructive: removes the template only — tenant policies created from it remain intact. Use cipp_list_spam_filter_templates to find the template ID. Not tenant-scoped.

ParamTypeRequiredDefaultDescription
additionalFieldsJsonstringnonullForward-compatibility escape hatch: a JSON object whose properties are merged into the request body LAST, overriding any of the typed parameters above. Use only for new CIPP fields that this tool does not yet expose. Keys are passed verbatim — caller is responsible for exact spec casing.
idstringyesTemplate identifier to remove. Sent in the body as 'ID' (PascalCase, all-caps) per CIPP spec. Use cipp_list_spam_filter_templates to find valid IDs.

[CIPP] Remove entries from the Tenant Allow/Block List via POST /api/RemoveTenantAllowBlockList. CIPP reads exactly three BODY keys — tenantFilter (camelCase), Entries and ListType (both PascalCase) — and runs Remove-TenantAllowBlockListItems with @. There is NO 'id' key: removal targets the entry VALUES themselves (the sender addresses, URLs or file hashes), so an id-shaped body removes nothing. Unlike the add endpoint, CIPP does NOT split a comma-separated string here — this tool splits your list into a real JSON array so each value is matched individually. Use cipp_list_tenant_allow_block first to find the entry values.

ParamTypeRequiredDefaultDescription
entriesstringyesThe entry VALUES to remove — sender addresses, URLs or file hashes exactly as they appear in the list; NOT an id. Comma- or semicolon-separated for several; the tool sends them as a JSON array in the body key Entries (PascalCase), because CIPP passes this through as @($Entries) with no splitting of its own and a single comma-joined string would match nothing.
fieldsJsonstringnonullForward-compatibility escape hatch: a JSON object whose properties are merged into the request body LAST, overriding any of the typed parameters above. Keys are passed verbatim — caller is responsible for exact spec casing.
listTypestringyesKind of entry being removed, sent in the body as ListType (PascalCase — note the capital L, unlike the lowercase listType the ADD endpoint reads). Exchange's Remove-TenantAllowBlockListItems -ListType accepts: 'Sender', 'Url', 'FileHash', 'IP'.
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Sent in the body as tenantFilter, which is where CIPP reads it (the query argument is sent for connector consistency). Use cipp_list_tenants to discover available tenants.

[CIPP] Remove an Exchange transport rule via POST /api/RemoveTransportRule. The rule is identified by its guid (NOT ruleId — preserve casing). Use cipp_list_transport_rules first to find the guid. WARNING: Deleting a transport rule takes effect immediately and may affect mail flow.

ParamTypeRequiredDefaultDescription
additionalFieldsJsonstringnonullForward-compatibility escape hatch: a JSON object whose properties are merged into the request body LAST, overriding any of the typed parameters above. Use only for new CIPP fields that this tool does not yet expose. Keys are passed verbatim — caller is responsible for exact spec casing.
guidstringyesGUID of the transport rule to remove. Use cipp_list_transport_rules to find valid GUIDs.
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Sent as the tenantFilter query-string parameter AND in the body. Use cipp_list_tenants to discover available tenants.

[CIPP] Permanently delete a transport rule template from the CIPP template store via POST /api/RemoveTransportRuleTemplate. Spec body: ; query also accepts ID. Destructive: removes the template only — tenant rules created from it remain intact. Use cipp_list_transport_rules_templates to find the template ID. Not tenant-scoped.

ParamTypeRequiredDefaultDescription
additionalFieldsJsonstringnonullForward-compatibility escape hatch: a JSON object whose properties are merged into the request body LAST, overriding any of the typed parameters above. Use only for new CIPP fields that this tool does not yet expose. Keys are passed verbatim — caller is responsible for exact spec casing.
idstringyesTemplate identifier to remove. Sent in the body as 'ID' (PascalCase, all-caps) per CIPP spec. Use cipp_list_transport_rules_templates to find valid IDs.

Devices

ToolPlanAccessSummary
cipp_get_device_detailsFreeRead-onlyGet detailed information for a specific Intune device including hardware, OS version, compliance, and encryption status.
cipp_list_app_protectionFreeRead-onlyList Intune app protection policies (MAM) for a tenant.
cipp_list_app_statusFreeRead-onlyList Intune application install status across devices via GET /api/ListAppStatus (upstream POSTs Graph beta deviceManagement/reports/getDeviceInstallStatusReport).
cipp_list_appsFreeRead-onlyList Intune-managed applications for a tenant.
cipp_list_assignment_filter_templatesFreeRead-onlyList saved Intune assignment-filter TEMPLATES (the reusable definitions, not tenant-deployed filters) via GET /api/ListAssignmentFilterTemplates.
cipp_list_assignment_filtersFreeRead-onlyList Intune assignment filters (Graph beta deviceManagement/assignmentFilters) via GET /api/ListAssignmentFilters.
cipp_list_autopilot_configsFreeRead-onlyList Windows Autopilot deployment profiles and configuration settings for a tenant.
cipp_list_autopilot_devicesFreeRead-onlyList all Windows Autopilot registered devices for a tenant.
cipp_list_compliance_policiesFreeRead-onlyList Intune device compliance policies for a tenant.
cipp_list_defender_stateFreeRead-onlyList Microsoft Defender for Endpoint device status for a tenant.
cipp_list_defender_tvmFreeRead-onlyList Microsoft Defender Threat & Vulnerability Management data for a tenant.
cipp_list_detected_app_devicesFreeRead-onlyList Intune-managed devices that have a specific detected application installed (Graph beta deviceManagement/detectedApps//managedDevices) via GET /api/ListDetectedAppDevices.
cipp_list_detected_appsFreeRead-onlyList applications detected on Intune-managed devices for a tenant.
cipp_list_devicesFreeRead-onlyList all Intune-managed devices for a tenant.
cipp_list_intune_intentsFreeRead-onlyList Intune security-baseline and endpoint-protection intents — the legacy template-based policies — via GET /api/ListIntuneIntents, which reads Graph beta deviceManagement/Intents with…
cipp_list_intune_policiesFreeRead-onlyList Intune device configuration policies for a tenant.
cipp_list_intune_reusable_setting_templatesFreeRead-onlyList saved Intune reusable-setting TEMPLATES (the reusable definitions, not tenant-deployed reusable settings) via GET /api/ListIntuneReusableSettingTemplates.
cipp_list_intune_reusable_settingsFreeRead-onlyList Intune reusable policy settings (Graph beta deviceManagement/reusablePolicySettings) via GET /api/ListIntuneReusableSettings.
cipp_list_intune_scriptsFreeRead-onlyList Intune PowerShell and remediation scripts deployed to a tenant.
cipp_list_intune_templatesFreeRead-onlyList saved Intune policy TEMPLATES (the reusable definitions, not tenant-deployed policies) via GET /api/ListIntuneTemplates.

[CIPP] Get detailed information for a specific Intune device including hardware, OS version, compliance, and encryption status. Use cipp_list_devices first to find the device ID.

ParamTypeRequiredDefaultDescription
deviceIdstringyesThe Intune device ID. Use cipp_list_devices to find valid IDs.
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Use cipp_list_tenants to discover available tenants.

[CIPP] List Intune app protection policies (MAM) for a tenant. Returns policy names, platform, settings, and targeted apps.

ParamTypeRequiredDefaultDescription
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Use cipp_list_tenants to discover available tenants.

[CIPP] List Intune application install status across devices via GET /api/ListAppStatus (upstream POSTs Graph beta deviceManagement/reports/getDeviceInstallStatusReport). Both inputs are required and both ride the query string: tenantFilter (query key 'tenantFilter') and appFilter (query key 'AppFilter'). appFilter is a single-application EQUALITY on the Intune ApplicationId — upstream interpolates it as "(ApplicationId eq '<appFilter>')", so it does NOT narrow by app type and an empty value matches no rows. The report is a fixed single page of 999 rows (skip 0, top 999) with no pagination. Use cipp_list_apps to enumerate the catalog and obtain an ApplicationId, and cipp_list_detected_apps for devices' detected (not necessarily managed) applications.

ParamTypeRequiredDefaultDescription
appFilterstringyesThe Intune ApplicationId whose per-device install status you want. Required — this is an equality filter, not an app-type narrowing, so an empty value matches zero rows. Sent as the query key 'AppFilter'. Use cipp_list_apps to find one.
tenantFilterstringyesThe tenant to query, as its default domain name (e.g. contoso.onmicrosoft.com) or tenant id. Required. Sent as the query key 'tenantFilter'.

[CIPP] List Intune-managed applications for a tenant. Returns app names, types, assignment status, and install counts.

ParamTypeRequiredDefaultDescription
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Use cipp_list_tenants to discover available tenants.

[CIPP] List saved Intune assignment-filter TEMPLATES (the reusable definitions, not tenant-deployed filters) via GET /api/ListAssignmentFilterTemplates. Templates are MSP-scoped — there is NO tenantFilter on this endpoint. Spec query supports an optional ID parameter to fetch a single template; the underlying client method does not currently forward any query arg, so this tool returns the full list. Use cipp_list_assignment_filters for the filters DEPLOYED in a specific tenant; that tool takes a required tenantFilter.

[CIPP] List Intune assignment filters (Graph beta deviceManagement/assignmentFilters) via GET /api/ListAssignmentFilters. tenantFilter is required and is sent as the query key 'tenantFilter'. Optional filterId (query key 'filterId') returns a single filter instead of the list; optional useReportDb (query key 'UseReportDB') serves cached rows from CIPP's reporting database instead of live Graph. NOTE two distinct branches: tenantFilter='AllTenants' with no filterId is a genuine CROSS-TENANT sweep of CIPP's reporting database. useReportDb=true with a NAMED tenant and no filterId is still SINGLE-TENANT — Get-CIPPAssignmentFilterReport fans out across tenants ONLY inside its 'AllTenants' block; for any other value it serves that one tenant's CACHED rows, each carrying an extra CacheTimestamp member, instead of live Graph, and if that tenant has never been cached the endpoint answers HTTP 500 with 'No assignment filter data found for <tenant>. Run a cache sync first.' Upstream does NOT validate a missing tenant, and the live-Graph catch answers HTTP 500 with an EMPTY ARRAY body — so an empty result is not by itself proof that a tenant has no assignment filters.

ParamTypeRequiredDefaultDescription
filterIdstringnonullOptional Intune assignment-filter id. Sent as the query key 'filterId'. When supplied the endpoint returns that one filter instead of the list.
tenantFilterstringyesThe tenant to query, as its default domain name (e.g. contoso.onmicrosoft.com) or tenant id. Sent as the query key 'tenantFilter'. Pass 'AllTenants' only when you intend a cross-tenant reporting-database sweep.
useReportDbbooleannonullOptional. Sent as the query key 'UseReportDB'. When true and no filterId is supplied, the endpoint serves that tenant's CACHED rows from CIPP's reporting database instead of live Graph — HTTP 500 'No assignment filter data found for <tenant>. Run a cache sync first.' if it has never been cached. This branch is SINGLE-TENANT unless tenantFilter is 'AllTenants'.
ParamTypeRequiredDefaultDescription
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Use cipp_list_tenants to discover available tenants.

[CIPP] List all Windows Autopilot registered devices for a tenant. Returns serial number, model, group tag, and enrollment profile.

ParamTypeRequiredDefaultDescription
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Use cipp_list_tenants to discover available tenants.

[CIPP] List Intune device compliance policies for a tenant. Returns policy names, settings, and assignment targets.

ParamTypeRequiredDefaultDescription
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Use cipp_list_tenants to discover available tenants.

[CIPP] List Microsoft Defender for Endpoint device status for a tenant. Returns protection state, engine version, and last scan time.

ParamTypeRequiredDefaultDescription
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Use cipp_list_tenants to discover available tenants.

[CIPP] List Microsoft Defender Threat & Vulnerability Management data for a tenant. Returns vulnerability scores, recommendations, and exposed devices.

ParamTypeRequiredDefaultDescription
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Use cipp_list_tenants to discover available tenants.

[CIPP] List Intune-managed devices that have a specific detected application installed (Graph beta deviceManagement/detectedApps//managedDevices) via GET /api/ListDetectedAppDevices. Both inputs are required and both ride the query string: tenantFilter (query key 'tenantFilter') and appId (query key 'AppID'). Use cipp_list_detected_apps to obtain a detected-application id. NOTE for interpreting a result: upstream reports its own required-parameter failure by CATCHING the error, setting the status to OK, and returning the message string as the response body — so a malformed request arrives as an HTTP 200 that reads like data. If the body is a bare message rather than a device collection, treat it as a failure, not an empty result.

ParamTypeRequiredDefaultDescription
appIdstringyesThe Intune detected-application id whose installed-on devices you want. Required — upstream refuses without it. Sent as the query key 'AppID'. Use cipp_list_detected_apps to find one.
tenantFilterstringyesThe tenant to query, as its default domain name (e.g. contoso.onmicrosoft.com) or tenant id. Required. Sent as the query key 'tenantFilter'.

[CIPP] List applications detected on Intune-managed devices for a tenant. Returns app names, versions, and device counts.

ParamTypeRequiredDefaultDescription
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Use cipp_list_tenants to discover available tenants.

[CIPP] List all Intune-managed devices for a tenant. Returns device ID, name, OS, compliance state, and last check-in time.

ParamTypeRequiredDefaultDescription
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Use cipp_list_tenants to discover available tenants.

[CIPP] List Intune security-baseline and endpoint-protection intents — the legacy template-based policies — via GET /api/ListIntuneIntents, which reads Graph beta deviceManagement/Intents with $expand=settings,categories (so each intent carries its expanded settings and categories; these are NOT assignments). tenantFilter is the endpoint's only input and is required; it is sent as the query key 'tenantFilter'. Upstream does not validate a missing tenant — its catch block answers HTTP 403 with a normalized error string as the body. There is no AllTenants reporting-database branch on this endpoint.

ParamTypeRequiredDefaultDescription
tenantFilterstringyesThe tenant to query, as its default domain name (e.g. contoso.onmicrosoft.com) or tenant id. Sent as the query key 'tenantFilter'. This endpoint has no cross-tenant mode.

[CIPP] List Intune device configuration policies for a tenant. Returns policy types, settings, and assignments.

ParamTypeRequiredDefaultDescription
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Use cipp_list_tenants to discover available tenants.

[CIPP] List saved Intune reusable-setting TEMPLATES (the reusable definitions, not tenant-deployed reusable settings) via GET /api/ListIntuneReusableSettingTemplates. Templates are MSP-scoped — there is NO tenantFilter on this endpoint. Spec query supports an optional ID parameter to fetch a single template; the underlying client method does not currently forward any query arg, so this tool returns the full list. Use cipp_list_intune_reusable_settings for the reusable settings DEPLOYED in a specific tenant; that tool takes a required tenantFilter.

[CIPP] List Intune reusable policy settings (Graph beta deviceManagement/reusablePolicySettings) via GET /api/ListIntuneReusableSettings. tenantFilter is required and is sent as the query key 'tenantFilter' — upstream hard-validates it, answering HTTP 400 {"Results":"tenantFilter is required"} before doing any work. Optional settingId (query key 'ID') returns one setting instead of the list; optional useReportDb (query key 'UseReportDB') serves cached rows from CIPP's reporting database. NOTE two distinct branches: tenantFilter='AllTenants' with no settingId is a genuine CROSS-TENANT sweep of CIPP's reporting database. useReportDb=true with a NAMED tenant and no settingId is still SINGLE-TENANT — Get-CIPPIntuneReusableSettingsReport fans out across tenants ONLY inside its 'AllTenants' block; for any other value it serves that one tenant's CACHED rows instead of live Graph, and if that tenant has never been cached the endpoint answers HTTP 500 with 'No reusable settings data found for <tenant>. Run a cache sync first.' The LIVE-Graph branch returns a fixed $select — id, settingInstance, displayName, description, settingDefinitionId, version, referencingConfigurationPolicyCount, createdDateTime, lastModifiedDateTime — plus a RawJSON member per row; cached rows instead carry whatever the sync stored, plus RawJSON and a CacheTimestamp member. referencingConfigurationPolicyCount is a COUNT of referencing policies, never the referencing policies themselves.

ParamTypeRequiredDefaultDescription
settingIdstringnonullOptional reusable-setting id. Sent as the query key 'ID'. When supplied the endpoint returns that one setting instead of the list.
tenantFilterstringyesThe tenant to query, as its default domain name (e.g. contoso.onmicrosoft.com) or tenant id. Sent as the query key 'tenantFilter'; upstream 400s without it. Pass 'AllTenants' only when you intend a cross-tenant reporting-database sweep.
useReportDbbooleannonullOptional. Sent as the query key 'UseReportDB'. When true and no settingId is supplied, the endpoint serves that tenant's CACHED rows from CIPP's reporting database instead of live Graph — HTTP 500 'No reusable settings data found for <tenant>. Run a cache sync first.' if it has never been cached. This branch is SINGLE-TENANT unless tenantFilter is 'AllTenants'.
ParamTypeRequiredDefaultDescription
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Use cipp_list_tenants to discover available tenants.

[CIPP] List saved Intune policy TEMPLATES (the reusable definitions, not tenant-deployed policies) via GET /api/ListIntuneTemplates. Templates are MSP-scoped — there is NO tenantFilter on this endpoint. Spec query supports optional id (lowercase, single template), mode (string), and View (PascalCase) parameters; the underlying client method does not currently forward any query args, so this tool returns the full list. Use cipp_list_intune_policies to inspect device-configuration policies deployed in a specific tenant.

Device Management

ToolPlanAccessSummary
cipp_add_assignment_filterProWriteCreate a new Intune assignment filter via POST /api/AddAssignmentFilter.
cipp_add_assignment_filter_templateProWriteCreate a saved Intune assignment-filter TEMPLATE (the reusable definition, not a tenant-deployed filter) via POST /api/AddAssignmentFilterTemplate.
cipp_add_autopilot_configProWriteCreate a Windows Autopilot deployment profile in one or more tenants via POST /api/AddAutopilotConfig.
cipp_add_autopilot_deviceProWriteRegister one or more devices in Windows Autopilot via POST /api/AddAPDevice (Partner Center DeviceBatches).
cipp_add_defender_deploymentProDestructiveDeploy a Microsoft Defender for Endpoint baseline to one or more tenants via POST /api/AddDefenderDeployment.
cipp_add_enrollmentProWriteCreate an Enrollment Status Page (ESP) / device enrollment configuration in one or more tenants via POST /api/AddEnrollment.
cipp_add_intune_reusable_settingProWriteDEPLOY a SAVED reusable-setting TEMPLATE into a tenant via POST /api/AddIntuneReusableSetting.
cipp_add_intune_reusable_setting_templateProWriteCreate a saved Intune reusable-setting TEMPLATE via POST /api/AddIntuneReusableSettingTemplate.
cipp_add_intune_templateProWriteCreate a saved Intune policy TEMPLATE via POST /api/AddIntuneTemplate.
cipp_add_policyProDestructiveCreate OR OVERWRITE an Intune device configuration / compliance policy via POST /api/AddPolicy.
cipp_assign_autopilot_deviceProDestructiveAssociate an Autopilot device with a user via POST /api/ExecAssignAPDevice (Graph UpdateDeviceProperties).
cipp_assign_policyProDestructiveAssign an Intune policy to groups, users, or all devices via POST /api/ExecAssignPolicy.
cipp_delete_assignment_filterProDestructiveDelete an Intune assignment filter via DELETE /api/ExecAssignmentFilter.
cipp_device_actionProDestructiveExecute a remote action on an Intune device via POST /api/ExecDeviceAction.
cipp_device_passcode_actionProDestructiveExecute a passcode-related action on an Intune device via POST /api/ExecDevicePasscodeAction.
cipp_edit_assignment_filterProDestructiveEdit an existing Intune assignment filter via POST /api/EditAssignmentFilter.
cipp_edit_intune_policyProDestructiveRename and/or re-describe an existing Intune policy via POST /api/EditIntunePolicy.
cipp_edit_intune_scriptProDestructiveEdit an existing Intune PowerShell or remediation script via POST /api/EditIntuneScript.
cipp_edit_policyProDestructiveRename and/or re-describe an ADMX group-policy configuration via POST /api/EditPolicy.
cipp_exec_bitlocker_searchProWriteLook up BitLocker recovery keys via POST /api/ExecBitlockerSearch.
cipp_get_local_admin_passwordProWriteRetrieve the LAPS (Local Administrator Password Solution) password for a specific Intune device via POST /api/ExecGetLocalAdminPassword.
cipp_get_recovery_keyProWriteRetrieve the BitLocker recovery key for a specific Intune device via POST /api/ExecGetRecoveryKey.
cipp_remove_assignment_filter_templateProDestructiveDelete a saved Intune assignment-filter TEMPLATE via POST /api/RemoveAssignmentFilterTemplate.
cipp_remove_autopilot_configProDestructiveRemove a Windows Autopilot deployment profile via POST /api/RemoveAutopilotConfig.
cipp_remove_autopilot_deviceProDestructiveRemove a device from Windows Autopilot via POST /api/RemoveAPDevice.
cipp_remove_intune_reusable_settingProDestructiveRemove a reusable setting from a tenant's Intune via POST /api/RemoveIntuneReusableSetting.
cipp_remove_intune_reusable_setting_templateProDestructiveDelete a saved Intune reusable-setting TEMPLATE via POST /api/RemoveIntuneReusableSettingTemplate.
cipp_remove_intune_scriptProDestructiveRemove an Intune PowerShell or remediation script via POST /api/RemoveIntuneScript.
cipp_remove_intune_templateProDestructiveDelete a saved Intune policy TEMPLATE via POST /api/RemoveIntuneTemplate.
cipp_remove_policyProDestructiveRemove an Intune device configuration or compliance policy via POST /api/RemovePolicy.
cipp_rename_autopilot_deviceProWriteRename an Autopilot device via POST /api/ExecRenameAPDevice.
cipp_set_autopilot_group_tagProDestructiveSet the group tag on an Autopilot device via POST /api/ExecSetAPDeviceGroupTag.
cipp_sync_autopilotProWriteTrigger a sync of all Windows Autopilot devices for a tenant via POST /api/ExecSyncAPDevices.
cipp_sync_depProWriteSynchronize Apple Device Enrollment Program (DEP) devices for a tenant via POST /api/ExecSyncDEP.

[CIPP] Create a new Intune assignment filter via POST /api/AddAssignmentFilter. Filters refine policy and app assignments based on device properties. Tool injects tenantFilter (camelCase) into the body — caller supplies the rest in fieldsJson.

ParamTypeRequiredDefaultDescription
fieldsJsonstringyesAssignment filter configuration as JSON object merged into the request body. Spec body keys (case-sensitive — preserve EXACTLY): displayName (camelCase string), description (camelCase string), rule (camelCase string — KQL-style device-property expression), platform (camelCase string — e.g., 'Windows10AndLater', 'iOS', 'macOS', 'android'), assignmentFilterManagementType (camelCase string enum: 'devices' or 'apps'). Tool injects tenantFilter — do NOT add it here.
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Sent as the body key 'tenantFilter' (camelCase, required per spec). Use cipp_list_tenants to discover available tenants.

[CIPP] Create a saved Intune assignment-filter TEMPLATE (the reusable definition, not a tenant-deployed filter) via POST /api/AddAssignmentFilterTemplate. Verified against CIPP-API master @df3738d: displayName, rule and platform are HARD REQUIREMENTS — each has its own guard that throws ('You must enter a displayname', 'You must enter a filter rule', 'You must select a platform'). There is NO casing trap here: the guard tests $Request.Body.displayName, the normalizer additionally accepts Displayname and displayname, and PowerShell binds property names case-insensitively anyway — the previous 'must be lowercase n' instruction was false. WARNING: this endpoint returns a hardcoded HTTP 200 OK on BOTH paths — the three guards throw into a catch that only rewrites the Results string, so a rejection arrives as 200 with Results = 'Assignment Filter Template Creation failed: <message>'. Always read Results; a 200 alone does not mean the template was created. This endpoint does NOT take tenantFilter; templates are MSP-scoped.

ParamTypeRequiredDefaultDescription
fieldsJsonstringyesTemplate definition as a JSON object — the entire request body. Body keys CIPP reads: displayName (string — REQUIRED; displayName, Displayname and displayname all bind), rule (string — REQUIRED, the filter rule expression), platform (string — REQUIRED, the Intune platform name, e.g. 'windows10AndLater', 'iOS', 'android'), assignmentFilterManagementType (string — 'devices' or 'apps'; DEFAULTS to 'devices' when omitted), description (string — description and Description both bind), GUID (string — the template's identifier/RowKey; CIPP generates a new GUID when omitted, so supply it only to overwrite a specific template). This endpoint does NOT take tenantFilter.

[CIPP] Create a Windows Autopilot deployment profile in one or more tenants via POST /api/AddAutopilotConfig. This endpoint is MULTI-TENANT: there is no tenantFilter on its body — the targets come from selectedTenants. Verified against CIPP-API master @df3738d: upstream evaluates $Request.Body.selectedTenants.value and loops over the result, so selectedTenants must be an ARRAY OF OBJECTS ([{"value":"contoso.onmicrosoft.com"}]) — a comma-separated STRING dereferences to null, the loop never runs, no profile is created for any tenant, and the call still returns HTTP 200 OK with Results = null (this endpoint does not wrap the loop output in an array). This tool builds that array for you from the typed selectedTenants parameter. Note the asymmetry on this same body: GroupIds IS string-tolerant (upstream accepts bare id strings OR objects there), so the two keys do NOT behave alike. DisplayName is validated by Test-CIPPAutopilotProfileName and an invalid name (blank, or anything outside letters/numbers/spaces and : " ? . @ $ & _ [ ] | \ — a hyphen is the common rejection) returns HTTP 400. That is the only UP-FRONT guard, but not the only loud failure: the per-tenant loop has no try/catch, so a deployment failure on any tenant throws out to the router as HTTP 500 with the raw message and aborts the remaining tenants, losing the results of the tenants that already succeeded.

ParamTypeRequiredDefaultDescription
fieldsJsonstringyesAutopilot profile configuration as JSON object merged into the request body (PowerShell binds property names case-insensitively; the casing shown is CIPP's own convention). Body keys CIPP reads: DisplayName (string — REQUIRED and name-validated; an invalid name is a 400), Description (string), DeviceNameTemplate (string — Autopilot computer-name template), DeploymentMode (boolean — true selects the 'shared'/self-deploying mode, false user-driven), Assignto (string/boolean — whether to assign the profile), Autokeyboard (boolean), CollectHash (boolean), HideChangeAccount (boolean), HidePrivacy (boolean), HideTerms (boolean), NotLocalAdmin (boolean — true makes the user a standard user), allowWhiteGlove (boolean — forced off when DeploymentMode selects shared mode), languages (object — locale settings), GroupIds (array — accepts EITHER bare group-id strings OR objects, unlike selectedTenants). There is NO tenantFilter on this endpoint. WARNING: keys here are merged LAST and override the tool's construction — supplying your own 'selectedTenants' as a plain comma-separated string re-arms the silent no-op described in the tool summary (200 OK, nothing created).
selectedTenantsstringyesREQUIRED: comma-separated tenant domains to create the profile in (e.g. 'contoso.onmicrosoft.com,fabrikam.onmicrosoft.com'). The tool converts this into the body array selectedTenants = [{"value":"contoso.onmicrosoft.com"},...], the only shape upstream can read. Use cipp_list_tenants to discover available tenants.

[CIPP] Register one or more devices in Windows Autopilot via POST /api/AddAPDevice (Partner Center DeviceBatches). Verified against CIPP-API master @df3738d: autopilotData is a COLLECTION OF DEVICE OBJECTS, not a CSV and not a JSON string — upstream enumerates it and reads .hardwareHash, .SerialNumber, .productKey, .oemManufacturerName and .modelName off each element, so a bare string uploads as devices:["<your whole text>"] on the create path and one all-null device on the append path. This tool therefore takes autopilotData as a JSON ARRAY of device objects (a single device object is accepted and wrapped for you) and sends it as an array. Groupname is the Partner Center DEVICE BATCH id, NOT an Azure AD group — this endpoint makes no Azure AD call at all; when omitted CIPP generates a GUID batch id, and it is an UPSERT key: an id that already exists in the tenant's Partner Center DeviceBatches makes CIPP APPEND the devices to that batch (via a different per-device payload), while an unknown id creates a new batch. Upstream reads Body.TenantFilter.VALUE — the tenant-picker object , NOT a plain string — and reads NO query string at all; a bare-string TenantFilter makes .value $null, Get-Tenants then returns EVERY tenant, and the whole array of customerIds is interpolated space-separated into the Partner Center URI, so the request cannot resolve to your tenant. This tool constructs the {"value": "<domain>"} wrapper for you from the tenantFilter parameter.

ParamTypeRequiredDefaultDescription
additionalFieldsJsonstringnonullForward-compatibility escape hatch: a JSON object whose properties are merged into the request body LAST, overriding any of the typed parameters above. Use only for new CIPP fields that this tool does not yet expose. Keys are passed verbatim — caller is responsible for exact spec casing. WARNING: an 'autopilotData' key merged here bypasses the typed parameter's array handling, so a string value re-arms the broken upload it exists to prevent (devices:["<your text>"] on the create path, an all-null device on the append path) — if you must set it here, set it to an ARRAY of device objects.
autopilotDatastringyesREQUIRED: a JSON ARRAY of Autopilot device objects, each carrying hardwareHash, serialNumber, productKey, oemManufacturerName and modelName — e.g. [{"hardwareHash":"<base64 hash>","serialNumber":"ABC123","productKey":"","oemManufacturerName":"Contoso","modelName":"Latitude 5540"}]. A single device object is accepted and wrapped into a one-element array for you. NOT a CSV and NOT a JSON string: upstream enumerates this value and reads those five properties off each element, so a bare string yields devices:["<your text>"] on the create path and an all-null device on the append path. Convert a Get-WindowsAutoPilotInfo CSV export into this array before calling. Sent in the body as autopilotData (an array).
groupNamestringnonullOptional: the Partner Center DEVICE BATCH id, sent in the body as Groupname. This is NOT an Azure AD group — this endpoint never touches Azure AD. When omitted CIPP generates a GUID batch id. It is an UPSERT key: if the id already exists in the tenant's Partner Center DeviceBatches the devices are APPENDED to that batch; otherwise a new batch is created.
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). The tool wraps this as the body object TenantFilter = {"value": "<domain>"}, which is the ONLY form upstream reads (it dereferences .value). The tenantFilter query-string parameter is also sent for connector consistency but upstream ignores it entirely. Use cipp_list_tenants to discover available tenants.

[CIPP] Deploy a Microsoft Defender for Endpoint baseline to one or more tenants via POST /api/AddDefenderDeployment. This endpoint is MULTI-TENANT: there is no tenantFilter on its body — the targets come from selectedTenants. Verified against CIPP-API master @df3738d: upstream evaluates ($Request.Body.selectedTenants).value and loops over the result, so selectedTenants must be an ARRAY OF OBJECTS ([{"value":"contoso.onmicrosoft.com"}]) — a comma-separated STRING dereferences to null, the per-tenant loop never runs, and the call returns HTTP 200 OK with an empty Results array having deployed nothing. This tool builds that array for you from the typed selectedTenants parameter. Includes ASR rule deployment, EDR/compliance/exclusion blocks, platform connectors, and the Defender setup-wizard section toggles.

ParamTypeRequiredDefaultDescription
fieldsJsonstringyesDeployment configuration as JSON object merged into the request body. Body keys CIPP reads: selectedTenants (see below), Compliance (object), Policy (object), Exclusion (object), ASR (object), EDR (object). Those six keys are the WHOLE top-level contract. Every other wizard field is NESTED inside one of them and is ignored at the top level: Mode lives on ASR ONLY (ASR.Mode); AssignTo is read on FOUR of the objects — Policy.AssignTo, ASR.AssignTo, EDR.AssignTo and Exclusion.AssignTo — and Policy's guard is the odd one out: ASR, EDR and Exclusion skip the assign call when their AssignTo is absent, while Policy tests only 'not None', so a Policy object with NO AssignTo still fires an assign POST with an empty target type. Connectwindows, ConnectIos, ConnectMac, ConnectAndroid, ConnectIosCompliance, ConnectAndroidCompliance, BlockunsupportedOS and AppSync live on Compliance; excludedExtensions, excludedPaths and excludedProcesses live on Exclusion. showASR, showDefenderDefaults, showDefenderSetup and showExclusionPolicy are UI-only and are read nowhere in CIPP-API. There is NO tenantFilter on this endpoint. WARNING: keys here are merged LAST and override the tool's construction — supplying your own 'selectedTenants' as a plain comma-separated string re-arms the silent no-op described in the tool summary (200 OK, nothing deployed); if you must set it here, use the [{"value":"domain"}] object array.
selectedTenantsstringyesREQUIRED: comma-separated tenant domains to deploy to (e.g. 'contoso.onmicrosoft.com,fabrikam.onmicrosoft.com'). The tool converts this into the body array selectedTenants = [{"value":"contoso.onmicrosoft.com"},...], the only shape upstream can read. WARNING: the literal 'AllTenants' is a magic value — upstream expands it to every tenant (Get-Tenants -IncludeErrors) and deploys the baseline to all of them. Use cipp_list_tenants to discover available tenants.

[CIPP] Create an Enrollment Status Page (ESP) / device enrollment configuration in one or more tenants via POST /api/AddEnrollment. This endpoint is MULTI-TENANT: there is no tenantFilter on its body — the targets come from selectedTenants. Verified against CIPP-API master @df3738d: upstream evaluates $Request.Body.selectedTenants.value and loops over the result, so selectedTenants must be an ARRAY OF OBJECTS ([{"value":"contoso.onmicrosoft.com"}]) — a comma-separated STRING dereferences to null and the loop never runs. This tool builds that array for you from the typed selectedTenants parameter. WARNING: this function has NO validation and NO try/catch. A failure on any tenant is a terminating throw that escapes to the CIPP router, which answers HTTP 500 with the raw exception text and ABORTS the remaining tenants — so a partial multi-tenant run loses the results of the tenants that already succeeded. A 200 means every tenant the loop reached succeeded; it does NOT prove the loop ran, because a malformed selectedTenants yields zero iterations and a 200 with Results = null. Always check Results is non-empty.

ParamTypeRequiredDefaultDescription
fieldsJsonstringyesEnrollment configuration as JSON object merged into the request body (PowerShell binds property names case-insensitively; the casing shown is CIPP's own convention). Body keys: AllowFail (boolean — allow the user to skip ESP errors), AllowReset (boolean — allow reset on failure), EnableLog (boolean — collect ESP logs), ErrorMessage (string — custom message shown on ESP failure), InstallWindowsUpdates (boolean — install updates during ESP), OBEEOnly (boolean — restrict ESP to first-boot OOBE; note CIPP's 'OBEE' spelling), ShowProgress (boolean — show installation progress), TimeOutInMinutes (string), blockDevice (boolean — block the device on ESP failure). There is NO tenantFilter on this endpoint. WARNING: keys here are merged LAST and override the tool's construction — supplying your own 'selectedTenants' as a plain comma-separated string re-arms the silent no-op described in the tool summary (200 OK, nothing created).
selectedTenantsstringyesREQUIRED: comma-separated tenant domains to create the enrollment configuration in (e.g. 'contoso.onmicrosoft.com,fabrikam.onmicrosoft.com'). The tool converts this into the body array selectedTenants = [{"value":"contoso.onmicrosoft.com"},...], the only shape upstream can read. Use cipp_list_tenants to discover available tenants.

[CIPP] DEPLOY a SAVED reusable-setting TEMPLATE into a tenant via POST /api/AddIntuneReusableSetting. Despite the name this does not create a setting from a payload you supply. Verified against CIPP-API master @df3738d: the ONLY inputs are a tenant and a TemplateId — CIPP loads the stored template's RawJSON out of its 'templates' table (PartitionKey 'IntuneReusableSettingTemplate') and PUTs or POSTs that to Graph. displayName, description, rawJSON and ID in the request are NOT read; the display name is taken from the stored template. Failure modes: HTTP 400 'tenantFilter is required', HTTP 400 'TemplateId is required', HTTP 404 'Template <id> not found'. If the tenant already has a matching reusable setting CIPP updates it in place (or reports it already compliant). To CREATE the template first, use cipp_add_intune_reusable_setting_template.

ParamTypeRequiredDefaultDescription
fieldsJsonstringyesTemplate selection as a JSON object merged into the request body. Body keys CIPP reads: TemplateId (string — REQUIRED; the stored reusable-setting template's RowKey. Upstream also accepts TemplateList as an OBJECT whose .value is the id, or TemplateList as a bare string, and falls back to a TemplateId query argument). NOTHING ELSE on this endpoint is read — displayName, description, rawJSON and ID are ignored, and the deployed setting's display name comes from the stored template. Use cipp_list_intune_reusable_setting_templates to find a TemplateId. Tool injects tenantFilter — do NOT add it here.
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com) to deploy the template into. Sent as the tenantFilter query-string parameter AND injected into the body as tenantFilter — upstream reads the query first and falls back to the body, so either works. Required: without it CIPP returns HTTP 400 'tenantFilter is required'. Use cipp_list_tenants to discover available tenants.

[CIPP] Create a saved Intune reusable-setting TEMPLATE via POST /api/AddIntuneReusableSettingTemplate. This writes the MSP-wide 'templates' table; deploying a template into a tenant is a separate step (cipp_add_intune_reusable_setting). Verified against CIPP-API master @df3738d: tenantFilter is NOT required and is NOT read — this endpoint is MSP-scoped (AnyTenant) and references no tenant at all in body or query. The tool still passes the tenantFilter parameter for connector consistency; upstream ignores it. displayName and RawJSON ARE hard requirements and throw without them ('You must enter a displayName' / 'You must provide RawJSON for the reusable setting'), and invalid JSON in RawJSON throws too.

ParamTypeRequiredDefaultDescription
fieldsJsonstringyesTemplate definition as a JSON object merged into the request body (PowerShell binds property names case-insensitively; upstream additionally has explicit fallback chains). Body keys CIPP reads: displayName (string — REQUIRED; displayName, DisplayName and displayname all bind), rawJSON (string — REQUIRED, the serialized reusable-setting graph payload; rawJSON, RawJSON and the alias json all bind, and the content must parse as JSON), description (string — description and Description both bind), GUID (string — the template's identifier/RowKey; CIPP generates a new GUID when omitted). There is NO 'package' key — CIPP never reads one. There is no tenantFilter on this endpoint; the one the tool sends is ignored.
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Passed on the query string and seeded into the body for connector consistency, but this endpoint is MSP-scoped and reads NO tenant — the value has no effect on the template that is written. Use cipp_list_tenants to discover available tenants.

[CIPP] Create a saved Intune policy TEMPLATE via POST /api/AddIntuneTemplate. Verified against CIPP-API master @df3738d: the endpoint has TWO MUTUALLY EXCLUSIVE BRANCHES chosen by whether RawJSON is present. (1) RawJSON SUPPLIED — CIPP stores the payload as-is and reads ONLY displayName (required; 'You must enter a displayName'), description, RawJSON (must parse as JSON: 'the JSON is invalid') and TemplateType. tenantFilter, URLName, ID and ODataType are IGNORED on this branch, so the previous blanket claim that tenantFilter is required is wrong. (2) RawJSON OMITTED — CIPP builds the template from a LIVE tenant and then tenantFilter is genuinely required, together with URLName, ID and ODataType; a tenant outside your allowed list throws 'Access to this tenant is not allowed'. There is NO 'policySource' key on either branch — CIPP never reads one.

ParamTypeRequiredDefaultDescription
fieldsJsonstringyesTemplate definition as a JSON object merged into the request body (PowerShell binds property names case-insensitively; the casing shown is CIPP's convention). Branch 1 (you supply the payload) reads: RawJSON (string — the serialized policy graph payload; its presence selects this branch and it must parse as JSON), displayName (string — REQUIRED here), description (string), TemplateType (string). Branch 2 (build from a live tenant — select it by OMITTING RawJSON) reads: URLName (string — the Graph URL segment, e.g. 'deviceConfigurations'), ID (string — the source policy GUID being templatized), ODataType (string — the Graph @odata.type of the source resource), plus the injected tenantFilter; each of URLName/ID/ODataType may also be supplied on the query string. There is NO 'policySource' key. Tool injects tenantFilter — do NOT add it here.
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com) to read the source policy FROM. Sent as the tenantFilter query-string parameter AND injected into the body (upstream reads the body first, then the query). Required ONLY on the build-from-live-tenant branch — when you supply RawJSON in fieldsJson it is ignored. Use cipp_list_tenants to discover available tenants.

[CIPP] Create OR OVERWRITE an Intune device configuration / compliance policy via POST /api/AddPolicy. This is an UPSERT, not a create: verified against CIPP-API master @df3738d, CIPP hands the payload to Set-CIPPIntunePolicy, which lists the tenant's existing policies of the same type and matches on DISPLAY NAME. AddPolicy never passes a LevenshteinDistance, so the helper's default of 0 applies — an EXACT display-name match. On a match CIPP OVERWRITES the existing policy with your whole RAWJson body (destructive: every property you omit is still rewritten from your payload) — PATCH for most TemplateTypes, PUT for TemplateType 'Catalog', and for 'Admin' a POST to updateDefinitionValues that first deletes every definition value already on the configuration; only with no match does it POST a new policy. TEMPLATE-SHADOWING TRAP: when reusableSettings is absent or empty, CIPP resolves a stored template (by TemplateID/TemplateId/TemplateGuid/TemplateGUID/TemplateList.value, else by looking up a saved template whose Displayname equals your displayName) and, if found, REPLACES your RAWJson with the template's — so a caller who supplies both a displayName that collides with a saved template and their own RAWJson silently deploys the TEMPLATE's payload. Compliance policies ('deviceCompliancePolicies') have 'scheduledActionsForRule' stripped ONLY on the UPSERT-match branch: a NEW policy keeps and applies your scheduledActionsForRule, but re-running the same displayName PATCHes the existing policy with the property removed, so scheduled actions are not updated on an edit.

ParamTypeRequiredDefaultDescription
fieldsJsonstringyesPolicy definition as a JSON object merged into the request body. Body keys CIPP actually reads (verified @df3738d; PowerShell binds property names case-insensitively, so the casing shown is convention, not a requirement): TemplateType (string — REQUIRED, and one of exactly: 'AppProtection', 'AppConfiguration', 'deviceCompliancePolicies', 'Admin', 'Device', 'Catalog', 'windowsDriverUpdateProfiles', 'windowsFeatureUpdateProfiles', 'windowsQualityUpdatePolicies', 'hardwareConfigurations', 'windowsQualityUpdateProfiles'. Omitting it fails Set-CIPPIntunePolicy's mandatory binding and the error is returned inside Results with HTTP 200; an unrecognised value matches NO switch arm, makes no Graph call at all, and still returns HTTP 200 reading 'Successfully policy for <tenant>…' — a silent no-op. Always read the Results string), TemplateID / TemplateId / TemplateGuid / TemplateGUID (string) or TemplateList (OBJECT — its .value is read) to deploy a stored template, displayName (string — also the UPSERT match key AND the fallback template lookup key), Description (string), RAWJson (string — the serialized Intune policy graph payload; SILENTLY REPLACED by a resolved template's payload, see the tool description), AssignTo (string), AssignmentFilterName or assignmentFilter (string), AssignmentFilterType or assignmentFilterType (string — defaults to 'include' when an AssignmentFilterName is supplied), customGroup (string — when present it OVERWRITES AssignTo), excludeGroup (string), replacemap (OBJECT keyed by tenant domain, e.g. {"contoso.onmicrosoft.com":{"%OLD%":"NEW"}} — each property drives a regex replace over RAWJson; a plain STRING here does nothing), reusableSettings (ARRAY — its .Count is tested; a plain string is truthy with Count 1 and will suppress the template-resolution branch). There is NO 'Count' body key — CIPP never reads one. Do NOT add a 'tenantFilter' key here — the tool injects it; keys merged here override the injected value by exact-name collision.
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Sent in the body as tenantFilter (camelCase, required) and on the query string. Upstream accepts EITHER a plain string OR a picker object here ($Request.Body.tenantFilter.value ? ... : $Request.Body.tenantFilter) — this tool sends the plain string, which works. WARNING: the literal 'AllTenants' is a magic value — upstream expands it to every tenant's defaultDomainName and runs the whole destructive upsert once per tenant. Use cipp_list_tenants to discover available tenants.

[CIPP] Associate an Autopilot device with a user via POST /api/ExecAssignAPDevice (Graph UpdateDeviceProperties). Verified against CIPP-API master @df3738d: the body shape is INCONSISTENT — device and serialNumber are plain strings, but 'user' is an OBJECT and upstream reads user.addedFields.userPrincipalName and user.addedFields.addressableUserName. WARNING: a plain UPN string in 'user' makes both of those null, so CIPP posts {"userPrincipalName":null,"addressableUserName":null}, the device is associated with nobody, and the response is still HTTP 200 OK reading 'Successfully assigned device ... to ' with a blank name — a silent wrong outcome with no detectable failure. See the fieldsJson description for the working shape.

ParamTypeRequiredDefaultDescription
fieldsJsonstringyesAssignment configuration as JSON object merged into the request body. Body keys CIPP reads: device (STRING — the Autopilot device GUID, interpolated into the Graph URI), serialNumber (STRING — hardware serial, used only for the log/result text), user (OBJECT — NOT a string: upstream reads user.addedFields.userPrincipalName and user.addedFields.addressableUserName). Working example: {"device":"<autopilot-device-guid>","serialNumber":"ABC123","user":{"addedFields":{"userPrincipalName":"user@contoso.com","addressableUserName":"Firstname Lastname"}}}. Passing user as a bare UPN string assigns the device to nobody while still returning HTTP 200 OK. Tool injects tenantFilter — do NOT add it here.
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Sent in the body as tenantFilter (camelCase, required) and on the query string. Use cipp_list_tenants to discover available tenants.

[CIPP] Assign an Intune policy to groups, users, or all devices via POST /api/ExecAssignPolicy. Verified against CIPP-API master @df3738d: assignmentMode and assignmentDirection are TWO DIFFERENT keys and are easy to confuse. assignmentMode is an append/replace switch: upstream defaults it to 'append' when the key is absent or blank, but it does NOT sanity-check a value that is present — only the literal 'append' (and 'replace' with an assignmentDirection) preserves the policy's existing assignments. Anything else, including 'Include'/'Exclude', falls through to full-REPLACE and WIPES every existing assignment. That is why the typed parameter refuses it — and why an assignmentMode smuggled in through fieldsJson is dangerous, not inert, which is why BOTH keys are re-validated on the merged body rather than on the typed parameter alone. assignmentDirection is the include/exclude selector (upstream lowercases it and discards anything that is not 'include' or 'exclude'). WARNING: assignmentMode 'replace' OVERWRITES the policy's existing assignments, and 'replace' together with assignmentDirection 'exclude' is upstream's clear-all-exclusions path. Use the typed assignmentMode / assignmentDirection parameters below; both are refused before dispatch if the value is not one CIPP recognises.

ParamTypeRequiredDefaultDescription
assignmentDirectionstringnonullOptional: whether this assignment INCLUDES or EXCLUDES the named groups. Sent in the body as assignmentDirection. Accepted values: 'include' or 'exclude' (case-insensitive; anything else is refused before dispatch, because CIPP silently discards unrecognised values). Omit for CIPP's default behaviour. WARNING: 'exclude' combined with assignmentMode 'replace' is upstream's clear-all-exclusions path.
assignmentModestringnonullOptional: how this assignment combines with the policy's existing assignments. Sent in the body as assignmentMode. Accepted values: 'append' (add to existing assignments — CIPP's default when omitted) or 'replace' (OVERWRITE the existing assignments). Anything else is refused before dispatch. This is NOT 'Include'/'Exclude' — see assignmentDirection for that.
fieldsJsonstringyesAssignment configuration as JSON object merged into the request body. Body keys CIPP reads (PowerShell binds property names case-insensitively; the casing shown is convention): ID (string — the policy GUID), Type (string — the Graph policy resource type), AssignTo (string — the broad targets upstream recognises are exactly 'allLicensedUsers', 'AllDevices' and 'AllDevicesAndUsers' (case-insensitive); the literal 'on' means do-not-assign. ANY other value is treated as an Azure AD group DISPLAY NAME and looked up by name — there is no 'AllUsers' and no 'customGroup' broad target here, and a name that matches no group throws 'No groups found matching the specified name(s)' with HTTP 500), GroupIds (comma-separated group GUIDs), GroupNames (comma-separated group names), ExcludeGroupIds (comma-separated group GUIDs to EXCLUDE), ExcludeGroupNames (comma-separated group names to exclude), AssignmentFilterName (string), AssignmentFilterType (string — 'include' or 'exclude'), excludeGroup (string), platformType (string). There is NO 'customGroup' key on THIS endpoint — CIPP never reads one here (it belongs to /api/AddPolicy); supplying it does nothing. Two keys are re-validated on the MERGED body: 'assignmentMode' and 'assignmentDirection'. Prefer the typed parameters for both. A value merged here that disagrees with the typed parameter, or a second, differently-cased spelling of either key, is refused before dispatch; when the typed parameter is omitted, a value merged here must still be one the typed parameter would accept ('append'/'replace', 'include'/'exclude'). Tool injects tenantFilter — do NOT add it here. Keys are passed verbatim.
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Sent in the body as tenantFilter (camelCase, required) and on the query string. Use cipp_list_tenants to discover available tenants.

[CIPP] Delete an Intune assignment filter via DELETE /api/ExecAssignmentFilter. Verified against CIPP-API master @df3738d: ID and Action are both HARD REQUIREMENTS, not optional as previously documented — upstream throws 'Filter ID is required' without an ID, and its action switch has exactly one case, the literal 'Delete', with a default arm that throws "Unknown action: <value>". This tool therefore requires id and defaults action to 'Delete', refusing any other verb before dispatch. WARNING: policies using this filter lose their filter-based targeting.

ParamTypeRequiredDefaultDescription
actionstringno"Delete"Optional: the action verb, sent as the body key 'Action'. 'Delete' is the ONLY verb this endpoint implements and is the default; any other value is refused before dispatch because upstream would throw "Unknown action".
idstringyesREQUIRED: the assignment filter GUID to delete. Sent as the body key 'ID'. Upstream throws 'Filter ID is required' if it is missing. Use cipp_list_assignment_filters to find valid IDs.
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Sent as 'tenantFilter' in the body and on the query string (upstream reads Query.TenantFilter first, then Body.tenantFilter). Use cipp_list_tenants to discover available tenants.

[CIPP] Execute a remote action on an Intune device via POST /api/ExecDeviceAction. The device is identified by GUID (PascalCase — preserve casing) and the verb is sent as Action (PascalCase). Actions accepted by THIS tool: syncDevice, rebootNow, wipe, retire, remoteLock, rotateLocalAdminPassword, windowsDefenderScan, windowsDefenderUpdateSignatures, rotateBitLockerKeys. Use cipp_list_devices first to find GUIDs. WARNING: 'wipe' and 'retire' are irreversible and will erase device data — use 'syncDevice' or 'rebootNow' for non-destructive actions. Verified against CIPP-API master @df3738d: upstream branches on Action and implements four bespoke branches — setDeviceName, users and createDeviceLogCollectionRequest (none of which are in this tool's allow-list, so the input and user parameters below are unreachable — see their descriptions) and 'wipe', which IS. Every OTHER action in the allow-list falls through to the default branch, which forwards the whole request body to Graph unchanged. 'wipe' is the exception: CIPP discards your body and rebuilds it from exactly five recognised names — keepUserData, keepEnrollmentData, useProtectedWipe and persistEsimDataPlan (each coerced with [System.Convert]::ToBoolean, so the strings 'true'/'false' work) plus macOsUnlockCode when non-blank. Anything else you send with a wipe, including via additionalFieldsJson, is dropped.

ParamTypeRequiredDefaultDescription
actionstringyesAction to perform. Sent in the body as Action (PascalCase). Valid values: 'syncDevice', 'rebootNow', 'wipe', 'retire', 'remoteLock', 'rotateLocalAdminPassword', 'windowsDefenderScan', 'windowsDefenderUpdateSignatures', 'rotateBitLockerKeys'. Anything else is refused by this tool before dispatch.
additionalFieldsJsonstringnonullForward-compatibility escape hatch: a JSON object whose properties are merged into the request body LAST, overriding any of the typed parameters above. Use only for new CIPP fields that this tool does not yet expose. TWO keys are re-validated on the MERGED body and may NOT be overridden here: 'Action' (the operation) and 'GUID' (the device it runs on) — an override that disagrees with the typed 'action' or 'guid' parameter, or a second, differently-cased spelling of either key, is refused before dispatch, because those two are what this call was reviewed as and 'wipe'/'retire' are irreversible. Every other key keeps its last-wins behavior. Keys are passed verbatim — caller is responsible for exact spec casing.
guidstringyesThe Intune device GUID. Use cipp_list_devices to find valid GUIDs. Sent in the body as GUID (PascalCase).
inputstringnonullINERT for CIPP — do not use. Sent in the body as input (lowercase), but CIPP reads Body.input ONLY inside its 'setDeviceName' branch, and 'setDeviceName' is not in this tool's action allow-list, so this value never reaches any CIPP code path. It is NOT the new password for rotateLocalAdminPassword (that action takes no input). CIPP never reads it on any action this tool allows; note it is still serialized into the Graph action body on the default-branch actions (it is dropped on 'wipe'), so leave it unset rather than relying on Graph to ignore it.
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Sent as the tenantFilter query-string parameter AND in the body as tenantFilter (camelCase). Use cipp_list_tenants to discover available tenants.
userstringnonullINERT for CIPP — do not use. Sent in the body as user (lowercase, a plain string), but CIPP reads Body.user.value — an OBJECT, not a string — and only inside its 'users' branch, which is not in this tool's action allow-list. CIPP never reads it on any action this tool allows; note it is still serialized into the Graph action body on the default-branch actions (it is dropped on 'wipe'), so leave it unset rather than relying on Graph to ignore it.

[CIPP] Execute a passcode-related action on an Intune device via POST /api/ExecDevicePasscodeAction. Body: {Action (string), GUID (string — device GUID), tenantFilter (string, required)}. Verified against CIPP-API master @df3738d: exactly TWO verbs are intended and get bespoke result messages — 'resetPasscode' and 'removeDevicePasscode'. There is no 'clearPasscode' verb anywhere in CIPP. Action is interpolated straight into the Graph URI (.../managedDevices('')/) with NO allow-list and the result switch has a catch-all arm, so any other Graph-implemented managedDevices action would also execute and report 'Successfully queued <action>'; a verb Graph does not implement produces an invalid path and CIPP's HTTP 500 catch, not a clear validation message. WARNING: passcode actions take effect immediately on the device.

ParamTypeRequiredDefaultDescription
fieldsJsonstringyesAction configuration as JSON object merged into the request body. Body keys CIPP reads: Action (string — the passcode verb; the two INTENDED values, and the only two CIPP writes a bespoke result message for, are 'resetPasscode' — resets the device passcode and returns the new one when Graph supplies it — and 'removeDevicePasscode', which removes the passcode requirement. There is no allow-list, so any other Graph managedDevices action would be interpolated into the URI and executed; 'clearPasscode' does NOT exist upstream and will produce an invalid Graph path), GUID (string — the Intune device GUID; use cipp_list_devices to discover). Tool injects tenantFilter — do NOT add it here.
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Sent in the body as tenantFilter (camelCase, required) and on the query string. Use cipp_list_tenants to discover available tenants.

[CIPP] Edit an existing Intune assignment filter via POST /api/EditAssignmentFilter. Tool injects tenantFilter into the body — caller supplies the rest in fieldsJson. filterId is required; without it upstream throws 'Filter ID is required'. Verified against CIPP-API master @df3738d: ONLY displayName, description and rule are updatable. WARNING: platform and assignmentFilterManagementType are NOT read by this endpoint — CIPP's own source notes they cannot change after creation per Graph API restrictions — so supplying them returns HTTP 200 OK with 'Successfully updated assignment filter' while those two fields are silently unchanged. Delete and recreate the filter to change either.

ParamTypeRequiredDefaultDescription
fieldsJsonstringyesFilter update fields as JSON object merged into the request body (PowerShell binds property names case-insensitively; the casing shown is convention). Body keys CIPP reads — these four and no others: filterId (string — REQUIRED, the assignment filter GUID to edit; use cipp_list_assignment_filters to find it), displayName (string), description (string), rule (string — KQL-style device-property expression). Guard asymmetry: displayName and rule are ignored when blank (an empty string cannot clear them), but description is presence-tracked on null only — an EMPTY STRING for description deliberately CLEARS it. Supplying none of the three PATCHes an empty object and returns 200 having changed nothing. 'platform' and 'assignmentFilterManagementType' are NOT read on this endpoint and are silently ignored (see the tool summary). Tool injects tenantFilter — do NOT add it here.
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Sent as the body key 'tenantFilter' and on the query string. Use cipp_list_tenants to discover available tenants.

[CIPP] Rename and/or re-describe an existing Intune policy via POST /api/EditIntunePolicy. Verified against CIPP-API master @df3738d: this endpoint PATCHes ONLY the policy's name and description on https://graph.microsoft.com/beta/{platformType}/{policyType}/{ID} — the name property is 'displayName', or 'name' when policyType is 'configurationPolicies', and upstream normalizes the six singular app-protection policyType values (e.g. 'androidManagedAppProtection') to their plural Graph collection segments first. You send newDisplayName either way. It cannot change any other policy setting; use cipp_add_policy to rewrite a policy body. Upstream reads NO query string; every field must be in the body. SIX body keys are read (not the four previously documented): ID, policyType, newDisplayName, tenantFilter, platformType and description.

ParamTypeRequiredDefaultDescription
fieldsJsonstringyesPolicy update fields as JSON object merged into the request body (PowerShell binds property names case-insensitively; the casing shown is convention). Body keys CIPP reads: ID (string — the Intune policy GUID being edited; required), policyType (string — the Graph collection segment, e.g. 'deviceConfigurations', 'deviceCompliancePolicies', 'configurationPolicies'), newDisplayName (string — the replacement display name), platformType (string — the Graph service segment; DEFAULTS to 'deviceManagement' when omitted, so only set it for a non-default surface), description (string — PRESENCE-TRACKED: CIPP checks whether the body carries a 'description' property at all, so omitting it leaves the existing description untouched while passing an EMPTY STRING deliberately CLEARS it). Do NOT add 'tenantFilter' here — the tool injects it.
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Injected into the body as tenantFilter (camelCase) — the ONLY place upstream reads it. The tool also sends it on the query string for connector consistency, but this endpoint reads no query string. Use cipp_list_tenants to discover available tenants.

[CIPP] Edit an existing Intune PowerShell or remediation script via POST /api/EditIntuneScript. Verified against CIPP-API master @df3738d: the endpoint switches on the HTTP method and only its 'POST' arm performs the update (Graph PATCH on / with tenantid = Body.TenantFilter and body = Body.IntuneScript); the 'PATCH' arm answers 400 "Method PATCH is not supported.", which is why the client dispatches POST. The tenant reaches upstream through the PascalCase body key TenantFilter, not through the query string.

ParamTypeRequiredDefaultDescription
fieldsJsonstringyesScript update fields as JSON object merged into the request body. Body keys CIPP reads: IntuneScript (string — the new script content as JSON; upstream ConvertFrom-Json's it and forwards it verbatim as the Graph PATCH body), ScriptId (string — the Intune script GUID), ScriptType (string — 'Windows', 'MacOS', 'Remediation' or 'Linux'. SUPPLY IT: the endpoint-probing fallback belongs to this endpoint's GET arm, not the POST arm this tool uses. Omitted, the POST arm sniffs the '@odata.type' inside your IntuneScript payload and DEFAULTS TO 'Windows' when it cannot classify it — PATCHing a MacOS/Remediation script against deviceManagementScripts, the wrong Graph collection — and if the payload carries no '@odata.type' at all the mandatory-parameter binding fails and you get an uncaught HTTP 500), TenantFilter (the tool injects it, so do not add it here in any casing. PowerShell binds property names case-insensitively, so the endpoint is not casing-sensitive; the reason to omit it is simply that a second key differing only in case duplicates the one already injected).
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Injected into the body as TenantFilter (PascalCase) — the key upstream's POST arm reads. Use cipp_list_tenants to discover available tenants.

[CIPP] Rename and/or re-describe an ADMX group-policy configuration via POST /api/EditPolicy. Verified against CIPP-API master @df3738d: despite the generic name this endpoint only PATCHes https://graph.microsoft.com/beta/deviceManagement/groupPolicyConfigurations('{groupid}') with {description, displayName, roleScopeTagIds:['0']} and optionally assigns it — it does NOT edit device configuration or compliance policies (use cipp_edit_intune_policy or cipp_add_policy for those), and any roleScopeTagIds already on the configuration are reset to ['0']. The TENANT KEY IS DIFFERENTLY NAMED, not differently cased: upstream reads Body.tenantid and never looks at tenantFilter or the query string, so this tool seeds tenantid for you from the tenantFilter parameter. WARNING: the endpoint returns a hardcoded HTTP 200 OK from its catch block as well as on success — a failure arrives as English text inside Results, never as a non-200 status, so always read the Results string. CIPP's own source marks this endpoint 'suspect this is deprecated'.

ParamTypeRequiredDefaultDescription
fieldsJsonstringyesPolicy update fields as JSON object merged into the request body. PowerShell binds property names CASE-INSENSITIVELY, so AssignTo/Assignto, DisplayName/Displayname and GroupId/groupid all bind — the earlier warning against those spellings was wrong. WARNING — AN OMITTED KEY IS NOT LEFT ALONE, IT IS CLEARED: upstream builds the PATCH body by string concatenation with no null guard ('{"description":"' + $description + '","displayName":"' + $displayname + '","roleScopeTagIds":["0"]}'), so a key you leave out resolves to $null, goes onto the wire as an EMPTY STRING, and BLANKS that field on the policy — while CIPP still answers HTTP 200 'Successfully edited policy for <tenant>'. Always send BOTH Displayname and Description, even when you mean to change only one. Body keys CIPP reads: groupid (string — REQUIRED, the groupPolicyConfiguration GUID being edited, NOT an Azure AD group id), Displayname (string — the new display name), Description (string — the new description), Assignto (string — omit to leave assignments alone; the literal 'on' is treated as 'do not assign'; 'AllDevicesAndUsers' assigns both all-devices and all-licensed-users, and any other value V is interpolated into '#microsoft.graph.AssignmentTarget', so use 'allDevices' or 'allLicensedUsers'). Do NOT add a 'tenantid' key here unless you intend to override the tenant — the tool seeds it from tenantFilter and caller keys merge last. 'tenantFilter' is NOT read by this endpoint.
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Seeded into the body as tenantid (the ONLY tenant key upstream reads — there is no tenantFilter on this endpoint and no query string is read). Use cipp_list_tenants to discover available tenants.

[CIPP] Look up BitLocker recovery keys via POST /api/ExecBitlockerSearch. Verified against CIPP-API master @df3738d: this is NOT an unfiltered tenant-wide listing — SEARCH CRITERIA ARE MANDATORY. You must supply keyId or deviceId; with neither, upstream returns HTTP 400 'No search criteria provided. Please provide keyId or deviceId.', so the zero-argument call the signature invites always fails and is refused here before dispatch. keyId TAKES PRECEDENCE: when both are supplied upstream uses keyId and ignores deviceId. Every field is read query-first with a body fallback; this tool sends tenantFilter on both and the rest in the body.

ParamTypeRequiredDefaultDescription
additionalFieldsJsonstringnonullForward-compatibility escape hatch: a JSON object whose properties are merged into the request body LAST, overriding any of the typed parameters above. Use only for new CIPP fields that this tool does not yet expose. Keys are passed verbatim — caller is responsible for exact spec casing.
deviceIdstringnonullIntune device GUID to look up that device's recovery keys. Sent in the body as deviceId. EITHER this or keyId is REQUIRED — a call with neither is refused. Ignored when keyId is also supplied. Use cipp_list_devices to discover device GUIDs.
keyIdstringnonullBitLocker recovery-key GUID to look up a specific key directly. Sent in the body as keyId. EITHER this or deviceId is REQUIRED — a call with neither is refused. Takes precedence over deviceId when both are given.
limitstringnonullOptional: cap on the number of recovery keys returned. Sent in the body as limit; upstream casts it to an int and applies no cap when it is absent or 0. Pass as a string, e.g. '50'.
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Sent as the tenantFilter query-string parameter AND injected into the body as tenantFilter. Use cipp_list_tenants to discover available tenants.

[CIPP] Retrieve the LAPS (Local Administrator Password Solution) password for a specific Intune device via POST /api/ExecGetLocalAdminPassword. The body uses PascalCase TenantFilter and lowercase guid (mixed casing per spec — preserve verbatim). Use cipp_list_devices first to find the device GUID.

ParamTypeRequiredDefaultDescription
additionalFieldsJsonstringnonullForward-compatibility escape hatch: a JSON object whose properties are merged into the request body LAST, overriding any of the typed parameters above. Use only for new CIPP fields that this tool does not yet expose. Keys are passed verbatim — caller is responsible for exact spec casing.
guidstringyesThe Intune device GUID. Use cipp_list_devices to find valid GUIDs. Sent in the body as guid (lowercase).
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Sent as the tenantFilter query-string parameter AND in the body as TenantFilter (PascalCase). Use cipp_list_tenants to discover available tenants.

[CIPP] Retrieve the BitLocker recovery key for a specific Intune device via POST /api/ExecGetRecoveryKey. The device is identified by GUID (PascalCase). Optionally, RecoveryKeyType selects the kind of recovery key to fetch when multiple are available. Use cipp_list_devices first to find the device GUID.

ParamTypeRequiredDefaultDescription
additionalFieldsJsonstringnonullForward-compatibility escape hatch: a JSON object whose properties are merged into the request body LAST, overriding any of the typed parameters above. Use only for new CIPP fields that this tool does not yet expose. Keys are passed verbatim — caller is responsible for exact spec casing.
guidstringyesThe Intune device GUID. Use cipp_list_devices to find valid GUIDs. Sent in the body as GUID (PascalCase).
recoveryKeyTypestringnonullOptional: type of recovery key to retrieve when the device has multiple. Sent in the body as RecoveryKeyType (PascalCase).
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Sent as the tenantFilter query-string parameter (required) AND in the body as tenantFilter (lowercase). Use cipp_list_tenants to discover available tenants.

[CIPP] Delete a saved Intune assignment-filter TEMPLATE via POST /api/RemoveAssignmentFilterTemplate. Spec body: {ID (uppercase string — the template GUID)}. Query also accepts ID. Use cipp_list_assignment_filter_templates first to find the template ID. NOTE: this endpoint does NOT take tenantFilter — templates are MSP-scoped, not tenant-scoped. WARNING: removes the template definition. Tenants currently using a filter cloned from this template are unaffected.

ParamTypeRequiredDefaultDescription
fieldsJsonstringyesRemoval configuration as JSON object — the entire request body. Spec body keys (case-sensitive — preserve EXACTLY): ID (uppercase string — the assignment filter template GUID; required). NOTE: this endpoint does NOT take tenantFilter.

[CIPP] Remove a Windows Autopilot deployment profile via POST /api/RemoveAutopilotConfig. Spec body: {ID (uppercase string — the deployment profile GUID), displayName (camelCase string — display name of the profile), assignments (camelCase string — assignment identifiers to remove alongside the profile), tenantFilter (camelCase, required)}. Devices assigned to this profile will need reassignment. WARNING: Deletes an Autopilot deployment profile. Devices using this profile will lose their configuration.

ParamTypeRequiredDefaultDescription
fieldsJsonstringyesRemoval configuration as JSON object merged into the request body. Spec body keys (case-sensitive — preserve EXACTLY): ID (uppercase string — the Autopilot deployment profile GUID; required), displayName (camelCase string — the profile display name), assignments (camelCase string — assignment identifiers to remove alongside the profile, comma-separated). Tool injects tenantFilter — do NOT add it here.
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Sent in the body as tenantFilter (camelCase, required). Use cipp_list_tenants to discover available tenants.

[CIPP] Remove a device from Windows Autopilot via POST /api/RemoveAPDevice. Spec body: {ID (uppercase string — the Autopilot device GUID), tenantFilter (camelCase, required)}. Query also accepts ID and tenantFilter. Use cipp_list_autopilot_devices first to find the device GUID. WARNING: Removes the device from Autopilot. The device will need to be re-registered for future deployments.

ParamTypeRequiredDefaultDescription
fieldsJsonstringyesRemoval configuration as JSON object merged into the request body. Spec body keys (case-sensitive — preserve EXACTLY): ID (uppercase string — the Autopilot device GUID; required). Tool injects tenantFilter — do NOT add it here.
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Sent as the tenantFilter query-string parameter AND injected into the body as tenantFilter (camelCase, required). Use cipp_list_tenants to discover available tenants.

[CIPP] Remove a reusable setting from a tenant's Intune via POST /api/RemoveIntuneReusableSetting. Body: {ID (the reusable setting GUID), DisplayName (optional), tenantFilter (required)}; upstream accepts each of the three on the query string as a fallback. Verified against CIPP-API master @df3738d: ID SPECIFICALLY is required — a DisplayName cannot stand in for it, and without an ID the call returns HTTP 400 regardless of what else you send. Policies referencing the setting may be affected.

ParamTypeRequiredDefaultDescription
fieldsJsonstringyesRemoval configuration as JSON object merged into the request body. Body keys CIPP reads: ID (string — REQUIRED, the reusable setting GUID; it is interpolated into the Graph DELETE URI, and a missing ID is HTTP 400), DisplayName (string — OPTIONAL and cosmetic: it is used ONLY to label the result message and can never identify the setting on its own). Tool injects tenantFilter — do NOT add it here.
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Sent as the tenantFilter query-string parameter AND injected into the body as tenantFilter — upstream reads the body first and falls back to the query, so either works. Required. Use cipp_list_tenants to discover available tenants.

[CIPP] Delete a saved Intune reusable-setting TEMPLATE via POST /api/RemoveIntuneReusableSettingTemplate. Spec body: {ID (uppercase string — the template GUID)}. Query also accepts ID. Use cipp_list_intune_reusable_setting_templates first to find the template ID. NOTE: this endpoint does NOT take tenantFilter — templates are MSP-scoped. WARNING: removes the template definition. Existing tenant deployments cloned from this template are unaffected.

ParamTypeRequiredDefaultDescription
fieldsJsonstringyesRemoval configuration as JSON object — the entire request body. Spec body keys (case-sensitive — preserve EXACTLY): ID (uppercase string — the reusable setting template GUID; required). NOTE: this endpoint does NOT take tenantFilter.

[CIPP] Remove an Intune PowerShell or remediation script via POST /api/RemoveIntuneScript. Body: {ID (the script GUID), ScriptType, DisplayName, TenantFilter}. Verified against CIPP-API master @df3738d: upstream reads $Request.Body.TenantFilter (PascalCase) and reads no query string; this tool already injects that exact key, so no caller workaround is required. Use cipp_list_intune_scripts first to find ID. WARNING: deletes an Intune script — devices will no longer execute it. WARNING: every failure on this endpoint returns HTTP 403 Forbidden (the catch hardcodes it) — a 403 here is NOT necessarily a permissions problem.

ParamTypeRequiredDefaultDescription
fieldsJsonstringyesRemoval configuration as JSON object merged into the request body. Body keys CIPP reads: ID (string — REQUIRED, the script GUID, interpolated into the Graph DELETE URI), ScriptType (string — REQUIRED in practice, and one of exactly 'Windows' → deviceManagementScripts, 'MacOS' → deviceShellScripts, 'Remediation' → deviceHealthScripts, 'Linux' → ConfigurationPolicies. The switch's default arm is $null, so an omitted or unrecognised value builds a null Graph URI, deletes nothing, and returns HTTP 403), DisplayName (string — cosmetic, used only in the result message). Do NOT add a TenantFilter or tenantFilter key here — the tool already injects the PascalCase TenantFilter the endpoint reads.
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). The tool injects this into the body as TenantFilter (PascalCase), which is exactly the key upstream reads — no extra work is needed on your side. Use cipp_list_tenants to discover available tenants.

[CIPP] Delete a saved Intune policy TEMPLATE via POST /api/RemoveIntuneTemplate. Spec body: {ID (uppercase string — the template GUID)}. Query also accepts ID. Use cipp_list_intune_templates first to find the template ID. NOTE: this endpoint does NOT take tenantFilter — templates are MSP-scoped. WARNING: removes the template definition. Tenants with policies cloned from this template are unaffected.

ParamTypeRequiredDefaultDescription
fieldsJsonstringyesRemoval configuration as JSON object — the entire request body. Spec body keys (case-sensitive — preserve EXACTLY): ID (uppercase string — the Intune template GUID; required). NOTE: this endpoint does NOT take tenantFilter.

[CIPP] Remove an Intune device configuration or compliance policy via POST /api/RemovePolicy. Spec body: {ID (uppercase string — the policy GUID), URLName (PascalCase string — the Microsoft Graph URL segment for the resource type, e.g., 'deviceConfigurations', 'deviceCompliancePolicies'), tenantFilter (camelCase, required)}. Query also accepts ID, URLName, and tenantFilter. Use cipp_list_intune_policies first to find ID and URLName. WARNING: Deletes an Intune policy. Devices assigned to this policy will lose the configuration.

ParamTypeRequiredDefaultDescription
fieldsJsonstringyesRemoval configuration as JSON object merged into the request body. Spec body keys (case-sensitive — preserve EXACTLY): ID (uppercase string — the policy GUID; required), URLName (PascalCase string — Microsoft Graph URL segment for the resource type, e.g., 'deviceConfigurations', 'deviceCompliancePolicies', 'configurationPolicies'). Tool injects tenantFilter — do NOT add it here.
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Sent as the tenantFilter query-string parameter AND injected into the body as tenantFilter (camelCase, required). Use cipp_list_tenants to discover available tenants.

[CIPP] Rename an Autopilot device via POST /api/ExecRenameAPDevice. Spec body: {deviceId (camelCase string — the Autopilot device GUID), serialNumber (camelCase string — alternative identifier), displayName (camelCase string — the new device name), tenantFilter (camelCase, required)}. All keys are camelCase. The new name will be applied on the next device sync or reset.

ParamTypeRequiredDefaultDescription
fieldsJsonstringyesRename configuration as JSON object merged into the request body. Spec body keys (camelCase, case-sensitive — preserve EXACTLY): deviceId (string — the Autopilot device GUID; preferred identifier), serialNumber (string — hardware serial number, used when device GUID is not yet known), displayName (string — the new device computer name, e.g., 'DESKTOP-NEW01'). Tool injects tenantFilter — do NOT add it here.
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Sent in the body as tenantFilter (camelCase, required). Use cipp_list_tenants to discover available tenants.

[CIPP] Set the group tag on an Autopilot device via POST /api/ExecSetAPDeviceGroupTag. Spec body: {deviceId (camelCase string — the Autopilot device GUID), serialNumber (camelCase string — alternative identifier), groupTag (camelCase string — the new tag value), tenantFilter (camelCase, required)}. All keys are camelCase. Group tags are used for dynamic group membership and profile assignment.

ParamTypeRequiredDefaultDescription
fieldsJsonstringyesGroup tag configuration as JSON object merged into the request body. Spec body keys (camelCase, case-sensitive — preserve EXACTLY): deviceId (string — the Autopilot device GUID; preferred identifier), serialNumber (string — hardware serial number, used when device GUID is not yet known), groupTag (string — the new group-tag value, e.g., 'Finance', 'Sales-Laptop'). Tool injects tenantFilter — do NOT add it here.
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Sent in the body as tenantFilter (camelCase, required). Use cipp_list_tenants to discover available tenants.

[CIPP] Trigger a sync of all Windows Autopilot devices for a tenant via POST /api/ExecSyncAPDevices. Refreshes the Intune Autopilot device list from the hardware vendor. Spec body required: { tenantFilter (camelCase, required) }.

ParamTypeRequiredDefaultDescription
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Sent as the camelCase 'tenantFilter' body field per spec. Use cipp_list_tenants to discover available tenants.

[CIPP] Synchronize Apple Device Enrollment Program (DEP) devices for a tenant via POST /api/ExecSyncDEP. Spec body is minimal: only {tenantFilter (camelCase, required)}. No additional payload is required to trigger the sync.

ParamTypeRequiredDefaultDescription
fieldsJsonstringyesOptional sync configuration as JSON object merged into the request body. The CIPP spec defines ONLY {tenantFilter (camelCase)} in this body; the tool already injects tenantFilter. This parameter exists only as a forward-compat escape hatch for future fields. Pass an empty object unless instructed otherwise.
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Sent in the body as tenantFilter (camelCase, required). Use cipp_list_tenants to discover available tenants.

Security

ToolPlanAccessSummary
cipp_add_ca_policyProDestructiveCreate a Conditional Access policy for a tenant via POST /api/AddCAPolicy.
cipp_edit_ca_policyProDestructiveModify an existing Conditional Access policy for a tenant via POST /api/EditCAPolicy.
cipp_list_anti_phishingFreeRead-onlyList anti-phishing filter policies for a tenant.
cipp_list_ca_changesFreeRead-onlyList recent changes to Conditional Access policies for a tenant.
cipp_list_ca_policiesFreeRead-onlyList all Conditional Access policies for a tenant.
cipp_list_malware_filtersFreeRead-onlyList malware filter policies for a tenant.
cipp_list_mdo_alertsFreeRead-onlyList Microsoft Defender for Office 365 alerts for a tenant.
cipp_list_named_locationsFreeRead-onlyList named locations (IP ranges and countries) used in Conditional Access policies for a tenant.
cipp_list_safe_attachmentsFreeRead-onlyList Safe Attachments policies for a tenant.
cipp_list_safe_linksFreeRead-onlyList Safe Links policies for a tenant.
cipp_list_security_alertsFreeRead-onlyList Microsoft 365 security alerts for a tenant.
cipp_list_security_incidentsFreeRead-onlyList Microsoft 365 security incidents for a tenant.
cipp_set_mdo_alertProDestructiveUpdate a Microsoft Defender for Office 365 alert via POST /api/ExecSetMdoAlert — CIPP PATCHes Microsoft Graph beta /security/alerts_v2/ as the CIPP application.
cipp_set_security_alertProDestructiveUpdate a Microsoft 365 security alert via POST /api/ExecSetSecurityAlert.
cipp_set_security_incidentProDestructiveUpdate a Microsoft 365 security incident via POST /api/ExecSetSecurityIncident — CIPP PATCHes Microsoft Graph beta /security/incidents/ as the CIPP application.

[CIPP] Create a Conditional Access policy for a tenant via POST /api/AddCAPolicy. The policy itself MUST be supplied as RawJSON (a JSON-STRINGIFIED Graph conditionalAccessPolicy). Verified against CIPP-API master @df3738d: the endpoint builds its call from exactly seven body keys — tenantFilter, RawJSON, NewState, CreateGroups, DisableSD, overwrite, replacename — and reads NO query-string parameter and NO TemplateList. WARNING: it reads the tenant as the SUB-PROPERTY $Request.body.tenantFilter.value, so this tool now sends tenantFilter as the {label, value} object CIPP's own UI posts; a bare JSON string has no 'value' member, which made the endpoint loop over zero tenants and return 200 OK with an empty Results array — creating nothing while looking like success. Passing the literal 'AllTenants' as the tenant deploys the policy to EVERY tenant CIPP manages.

ParamTypeRequiredDefaultDescription
fieldsJsonstringyesPolicy definition as a JSON object, merged into the request body LAST (so it can override the tenantFilter object this tool seeds). Body keys the endpoint actually reads (case-sensitive — preserve EXACTLY): RawJSON (PascalCase string — the REQUIRED JSON-stringified Graph conditionalAccessPolicy resource), NewState (PascalCase string — policy state at creation, e.g. 'donotchange', 'Enabled', 'Disabled', 'enabledForReportingButNotEnforced'), CreateGroups (boolean — auto-create the assignment include/exclude groups the policy references, but ONLY on the replacename='displayName' path; see the per-key rule below), DisableSD (boolean — disable Security Defaults so this CA can take effect), overwrite (camelCase boolean — overwrite an existing policy with the same display name), replacename (camelCase string — one of 'none', 'AllUsers' or 'displayName'; controls display-name placeholder rewriting. 'none' rewrites nothing. 'AllUsers' forces conditions.users.includeUsers to 'All' and CLEARS every include/exclude user and group, so the policy then applies to EVERYONE in the tenant. 'displayName' resolves group display names to ids, creating a group that does not resolve only when CreateGroups is set. The switch has NO default arm, so an unrecognized value — or omitting the key — behaves exactly like 'none'; 'leave' is not a value upstream recognizes). Send REAL JSON booleans, never strings — the three boolean keys are tested differently upstream and only real booleans behave the same across all of them. 'DisableSD' is compared with `-eq $true`, and PowerShell coerces the RIGHT operand to the LEFT's type, so the STRING "false" compares against "True" and does NOTHING, while the STRING "true" DOES disable Security Defaults. 'CreateGroups' is a bare truthiness test (`elseif ($CreateGroups)`), so the string "false" IS true there — but it is read ONLY on the replacename='displayName' path, the sole call site of Convert-GroupNameToId (New-CIPPCAPolicy.ps1:546); with replacename omitted (or any other value) CreateGroups is never consulted and no group is created whatever you send. On that one path it decides what happens to a referenced group that does not resolve in the tenant: create it when truthy, otherwise the helper THROWS — and the endpoint's PER-TENANT catch flattens that message into this tenant's entry in Results and still answers HTTP 200, so the policy is simply not created for it while the call looks like success. 'overwrite' is bare-truthy on the named-location update path (`if ($Overwrite)`) but `-ne $true` on the existing-policy gate. TemplateList is NOT read by this endpoint — a TemplateList-only call ships no RawJSON and creates nothing. Tool injects tenantFilter — do NOT add it here unless you deliberately override the shape (an ARRAY of {label, value} objects deploys to each listed tenant).
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Sent as the body OBJECT 'tenantFilter' = {label, value} because CIPP reads $Request.body.tenantFilter.value — a bare string silently creates nothing. The literal 'AllTenants' (any casing) is REFUSED here: CIPP expands it through Get-Tenants and would create this policy in EVERY tenant it manages. To fan out deliberately, pass an explicit array of {label, value} objects through fieldsJson. Use cipp_list_tenants to discover available tenants.

[CIPP] Modify an existing Conditional Access policy for a tenant via POST /api/EditCAPolicy. The CIPP edit endpoint is intentionally narrow: only the policy state and display name can be updated this way — to change conditions/grant-controls, delete and re-create the policy with cipp_add_ca_policy. Use cipp_list_ca_policies first to find the policy GUID. tenantFilter is sent on BOTH the query string AND inside the body.

ParamTypeRequiredDefaultDescription
fieldsJsonstringnonullUpdated fields as JSON object merged into the request body. Spec body keys (case-sensitive — preserve EXACTLY): GUID (PascalCase, all-caps string — already provided via the policyId typed parameter; pass here only to override), State (PascalCase string — target policy state, common values 'enabled', 'disabled', 'enabledForReportingButNotEnforced'), newDisplayName (camelCase string — rename the policy). Same fields are also accepted as query-string parameters (GUID, State, newDisplayName, tenantFilter). Tool injects tenantFilter — do NOT add it here.
policyIdstringyesThe Conditional Access policy GUID. The CIPP spec body field is named 'GUID' (PascalCase, all-caps) — this typed parameter is exposed as 'policyId' for clarity but the tool maps it correctly. Use cipp_list_ca_policies to find valid GUIDs.
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Sent as the body key 'tenantFilter' (camelCase, required per spec) and as a query parameter. Use cipp_list_tenants to discover available tenants.

[CIPP] List anti-phishing filter policies for a tenant. Returns impersonation protection, spoof settings, and action configurations.

ParamTypeRequiredDefaultDescription
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Use cipp_list_tenants to discover available tenants.

[CIPP] List recent changes to Conditional Access policies for a tenant. Returns modification history with timestamps and actors.

ParamTypeRequiredDefaultDescription
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Use cipp_list_tenants to discover available tenants.

[CIPP] List all Conditional Access policies for a tenant. Returns policy names, state, conditions, and grant controls.

ParamTypeRequiredDefaultDescription
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Use cipp_list_tenants to discover available tenants.

[CIPP] List malware filter policies for a tenant. Returns file type blocking, zero-hour purge, and notification settings.

ParamTypeRequiredDefaultDescription
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Use cipp_list_tenants to discover available tenants.

[CIPP] List Microsoft Defender for Office 365 alerts for a tenant. Returns threat detection alerts for email and collaboration workloads.

ParamTypeRequiredDefaultDescription
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Use cipp_list_tenants to discover available tenants.
ParamTypeRequiredDefaultDescription
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Use cipp_list_tenants to discover available tenants.

[CIPP] List Safe Attachments policies for a tenant. Returns detonation settings, action on detection, and redirect configuration.

ParamTypeRequiredDefaultDescription
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Use cipp_list_tenants to discover available tenants.

[CIPP] List Microsoft 365 security alerts for a tenant. Returns alert severity, status, category, and affected resources.

ParamTypeRequiredDefaultDescription
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Use cipp_list_tenants to discover available tenants.

[CIPP] List Microsoft 365 security incidents for a tenant. Returns incident severity, status, classification, and linked alerts.

ParamTypeRequiredDefaultDescription
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Use cipp_list_tenants to discover available tenants.

[CIPP] Update a Microsoft Defender for Office 365 alert via POST /api/ExecSetMdoAlert — CIPP PATCHes Microsoft Graph beta /security/alerts_v2/ as the CIPP application. Use cipp_list_mdo_alerts first to find the alert. Verified against CIPP-API master @df3738d: every field is read as $Request.Query.X ?? $Request.Body.X (the QUERY value WINS over the body), with keys 'tenantFilter' (camelCase), 'GUID', 'Status', 'Assigned', 'Classification', 'Determination'. Fields you omit are not PATCHed. CIPP does not validate values against Graph with ONE exception: Classification supplied WITHOUT Determination hits a bare `throw` and returns a 500 whose message ('Failed to update incident <id> : ...') never names the missing field — this tool refuses that combination before dispatch instead. WARNING: when 'assigned' is supplied on neither the query nor the body, CIPP falls back to decoding the caller's 'x-ms-client-principal' header — an identity header the CIPP frontend supplies and that a direct API call may not carry — and that fallback is evaluated OUTSIDE CIPP's try/catch, so it can fail the request with an unhandled error rather than a CIPP-formatted one. Supplying 'assigned' avoids the fallback entirely.

ParamTypeRequiredDefaultDescription
additionalFieldsJsonstringnonullForward-compatibility escape hatch: a JSON object whose properties are merged into the request body LAST, overriding any of the typed parameters above. Use only for new CIPP fields not yet exposed as typed parameters. Keys are passed verbatim — caller is responsible for exact spec casing.
alertIdstringnonullMDO alert identifier. Sent as 'GUID' (PascalCase, all-caps) per CIPP spec. Use cipp_list_mdo_alerts to find valid IDs.
assignedstringnonullUPN to assign the alert to. Sent as 'Assigned' (PascalCase); CIPP reads Query.Assigned ?? Body.Assigned and writes it to the Graph 'assignedTo' field — this endpoint DOES honour it (its sibling ExecSetSecurityIncident does not). Supplying it also avoids CIPP's x-ms-client-principal fallback, which is evaluated outside its try/catch and may have nothing to decode on a direct API call.
classificationstringnonullAlert classification (e.g., 'truePositive', 'falsePositive', 'informationalExpectedActivity'). Sent as 'Classification' (PascalCase); forwarded verbatim to Microsoft Graph. REQUIRES 'determination' — CIPP throws when it is missing and answers with an opaque 500; this tool refuses the pair up front.
determinationstringnonullAlert determination (e.g., 'malware', 'phishing', 'compromisedUser', 'maliciousUserActivity', 'notMalicious', 'lineOfBusinessApplication'). Sent as 'Determination' (PascalCase); forwarded verbatim to Microsoft Graph. Mandatory whenever 'classification' is supplied (including via additionalFieldsJson).
statusstringnonullNew alert status. Sent as 'Status' (PascalCase) per CIPP spec. Microsoft Graph accepts values like 'newAlert', 'inProgress', 'resolved', 'dismissed'; CIPP forwards verbatim.
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Sent both on the query string as 'tenantFilter' and in the body as 'tenantFilter' (camelCase); CIPP prefers the query value. Use cipp_list_tenants to discover available tenants.

[CIPP] Update a Microsoft 365 security alert via POST /api/ExecSetSecurityAlert. Use cipp_list_security_alerts first to find the alert. The CIPP body uses PascalCase keys throughout: 'GUID' (alert ID), 'Provider', 'Status', 'Vendor', plus 'tenantFilter' (camelCase). The same fields are also accepted as query-string parameters; the client places tenantFilter on the query string in addition to the body. Status accepted values are vendor- and provider-specific (e.g., for Microsoft Graph alerts: 'newAlert', 'inProgress', 'resolved', 'dismissed'); CIPP forwards the value verbatim to Microsoft Graph and does not validate.

ParamTypeRequiredDefaultDescription
additionalFieldsJsonstringnonullForward-compatibility escape hatch: a JSON object whose properties are merged into the request body LAST, overriding any of the typed parameters above. Use only for new CIPP fields not yet exposed as typed parameters. Keys are passed verbatim — caller is responsible for exact spec casing.
alertIdstringnonullSecurity alert identifier. Sent as 'GUID' (PascalCase, all-caps) per CIPP spec. Use cipp_list_security_alerts to find valid IDs.
providerstringnonullAlert provider name (the security product that raised the alert). Sent as 'Provider' (PascalCase) per CIPP spec.
statusstringnonullNew alert status. Sent as 'Status' (PascalCase) per CIPP spec. Vendor-/provider-specific values; CIPP forwards verbatim — common Graph values are 'newAlert', 'inProgress', 'resolved', 'dismissed'.
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Sent both on the query string as TenantFilter and in the body as 'tenantFilter' (camelCase). Use cipp_list_tenants to discover available tenants.
vendorstringnonullAlert vendor name. Sent as 'Vendor' (PascalCase) per CIPP spec.

[CIPP] Update a Microsoft 365 security incident via POST /api/ExecSetSecurityIncident — CIPP PATCHes Microsoft Graph beta /security/incidents/ as the CIPP application. Use cipp_list_security_incidents first to find the incident. Verified against CIPP-API master @df3738d: this endpoint reads EVERY field from the request BODY only and has NO query-string fallback (unlike ExecSetSecurityAlert and ExecSetMdoAlert, which read $Request.Query.X ?? $Request.Body.X), so the tenantFilter the client adds to the query string is ignored here — the body is what counts. Body keys: 'tenantFilter' (camelCase), 'GUID', 'Status', 'Classification', 'Determination'. Fields you omit are not PATCHed. Classification WITHOUT Determination hits a bare `throw` and returns a 500 that never names the missing field, so this tool refuses that pair before dispatch. Three further keys CIPP reads are not exposed as typed parameters and must go through additionalFieldsJson verbatim: 'Severity' — read as the SUB-PROPERTY Body.Severity.value, so it MUST be an object like {"value":"medium"} and a bare string is silently ignored; 'Comment' — written to Graph as 'resolvingComment'; and 'AssignToSelf' — a real JSON boolean, converted OUTSIDE CIPP's try/catch, so a non-boolean value fails the request unhandled.

ParamTypeRequiredDefaultDescription
additionalFieldsJsonstringnonullForward-compatibility escape hatch: a JSON object whose properties are merged into the request body LAST, overriding any of the typed parameters above. Use only for new CIPP fields not yet exposed as typed parameters. Keys are passed verbatim — caller is responsible for exact spec casing.
assignedstringnonullNOT SUPPORTED on this endpoint — leave unset; a value is REFUSED before dispatch. Verified against CIPP-API master @df3738d: Invoke-ExecSetSecurityIncident never reads Body.Assigned (its sibling Invoke-ExecSetMdoAlert does — the two endpoints are not symmetric). CIPP fills Graph 'assignedTo' only from an undocumented 'AssignToSelf' boolean, which resolves the assignee from the CIPP frontend's 'x-ms-client-principal' header rather than from any UPN you supply. A UPN passed here would be dropped silently while the rest of the update succeeded, reporting an assignment that never happened. Assign in the Defender portal, or pass 'Assigned' through additionalFieldsJson (forwarded verbatim, unchanged) if a future CIPP release starts reading it.
classificationstringnonullIncident classification (e.g., 'truePositive', 'falsePositive', 'informationalExpectedActivity'). Sent as 'Classification' (PascalCase) per CIPP spec; forwarded verbatim to Microsoft Graph. REQUIRES 'determination' — CIPP throws when it is missing and answers with an opaque 500; this tool refuses the pair up front.
determinationstringnonullIncident determination (e.g., 'malware', 'securityPersonnel', 'securityTesting', 'unwantedSoftware', 'other', 'multiStagedAttack', 'compromisedUser', 'phishing', 'maliciousUserActivity', 'notMalicious', 'notEnoughDataToValidate', 'confirmedActivity', 'lineOfBusinessApplication'). Sent as 'Determination' (PascalCase) per CIPP spec; forwarded verbatim to Microsoft Graph. Mandatory whenever 'classification' is supplied (including via additionalFieldsJson).
incidentIdstringnonullSecurity incident identifier. Sent as 'GUID' (PascalCase, all-caps) per CIPP spec. Use cipp_list_security_incidents to find valid IDs.
redirectedstringnonullNOT WRITABLE — leave unset; a value is REFUSED before dispatch. CIPP cannot record a redirect target through this endpoint. Verified against CIPP-API master @df3738d: it reads Body.Redirected as a GUARD ($Redirected = $Request.Body.Redirected -as [int]) and, when that parses to 1 or more, REFUSES the whole update — status, severity, classification, determination and comment are all discarded, nothing reaches Graph — while STILL returning 200 OK with 'Refused to update incident <id> because it is redirected to another incident'. The value itself is never stored anywhere. Update the incident this one was redirected TO instead.
statusstringnonullNew incident status. Sent as 'Status' (PascalCase) per CIPP spec. Microsoft Graph accepts values like 'active', 'inProgress', 'resolved', 'redirected'; CIPP forwards verbatim.
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Sent in the body as 'tenantFilter' (camelCase) — the only place this endpoint reads it; the client also puts it on the query string, which CIPP ignores here. Use cipp_list_tenants to discover available tenants.

Conditional Access

ToolPlanAccessSummary
cipp_add_ca_templateProWriteSave a Conditional Access policy as a CIPP template via POST /api/AddCATemplate.
cipp_add_named_locationProWriteCreate a Conditional Access named location via POST /api/AddNamedLocation.
cipp_exec_ca_checkFreeRead-onlySimulate ('what if') a Conditional Access evaluation via POST /api/ExecCACheck — read-only: no sign-in occurs and no policy is changed.
cipp_exec_ca_exclusionProDestructiveManage Conditional Access user exclusions via POST /api/ExecCAExclusion — a WRITE that adds or removes a user's exclusion on a CA policy, and with 'vacation' schedules the add/remove pair as tasks.
cipp_exec_ca_service_exclusionProDestructiveAdd the service-provider exception to a Conditional Access policy for a tenant via POST /api/ExecCAServiceExclusion — a WRITE that edits the named policy.
cipp_exec_named_locationProDestructiveModify or delete an existing Conditional Access named location via POST /api/ExecNamedLocation.
cipp_list_ca_templatesFreeRead-onlyList all Conditional Access policy templates available in CIPP.
cipp_remove_ca_policyProDestructiveDelete a Conditional Access policy from a tenant via POST /api/RemoveCAPolicy (a Graph DELETE on the policy).
cipp_remove_ca_templateProDestructiveDelete a Conditional Access policy template via POST /api/RemoveCATemplate.

[CIPP] Save a Conditional Access policy as a CIPP template via POST /api/AddCATemplate. The entrypoint itself reads two body keys by name — 'tenantFilter' (camelCase, seeded from the parameter) and 'name' (lowercase, which appears ONLY in the success string and the CIPP log line; it is NOT stored as the template's name) — and then hands the ENTIRE request body to New-CIPPCATemplate. THE BODY MUST BE THE GRAPH conditionalAccessPolicy OBJECT ITSELF: displayName, state, conditions{users, applications, locations, ...}, grantControls. New-CIPPCATemplate reads only conditions.locations.includeLocations/excludeLocations, conditions.users.includeUsers/excludeUsers/includeGroups/excludeGroups and conditions.applications.includeAuthenticationContextClassReferences — resolving those GUIDs to display names and attaching a LocationInfo (and, when auth contexts are used, AuthContextInfo) member — and stores every other property verbatim. There is NO 'policySource' key: that name appears nowhere as an AddCATemplate body key in CIPP-API @df3738d (the name does occur elsewhere upstream, e.g. in unrelated Intune and compliance helpers, but never on this endpoint), so a body that only references an existing policy stores a template with no policy content in it. ALWAYS include 'displayName' inside the body — the stored row is just {JSON, RowKey, PartitionKey, GUID}, and Invoke-ListCAtemplates rehydrates each row from its JSON and sorts and presents on displayName, so a template without one lists with a blank name. The client also copies tenantFilter onto the query string, but Invoke-AddCATemplate reads only $Request.Body.tenantFilter, so that copy is inert. On success Results is the string "Created CA Template <name> with GUID <guid>" — emitted even when the body carried no policy content, so it is not by itself evidence that a usable template was stored.

ParamTypeRequiredDefaultDescription
fieldsJsonstringyesThe template body as a JSON object, merged into the request body LAST (so anything here overrides the seeded 'tenantFilter'). This object must BE the Graph conditionalAccessPolicy resource — displayName, state, conditions, grantControls — because the whole body is forwarded to New-CIPPCATemplate verbatim and CIPP's exact casing is required. 'name' (lowercase) appears ONLY in the success string and the CIPP log line; it is not stored as the template's name. The name the template lists under is the 'displayName' INSIDE the policy JSON you send, since Invoke-ListCAtemplates sorts and displays on displayName — always include it. There is no 'policySource' body key on this endpoint upstream, so a policy-reference-only body creates an empty template at HTTP 200.
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Sent as the body key 'tenantFilter' (camelCase) — the only place CIPP reads it. Use cipp_list_tenants to discover available tenants.

[CIPP] Create a Conditional Access named location via POST /api/AddNamedLocation. This endpoint has NO 'tenantFilter': it loops over $request.body.selectedTenants.value, so the tenant selector is a LabelValue OBJECT (or array of them) shaped {"value":"<defaultDomainName>"}. WARNING — a plain string has no '.value', the create loop then runs ZERO times, and the endpoint returns HTTP 200 with {"Results":[]} having created nothing and reported no failure. Pass the selectedTenants parameter (comma-separated tenant default domains) and this tool builds the object array. WARNING — the literal 'AllTenants' is special-cased upstream and expands to EVERY tenant CIPP manages, creating the location in all of them; CIPP then narrows the list to the calling API client's permitted tenants (Test-CippAccess), and any tenant filtered out is simply absent from Results with no error. Other body keys (exact casing): 'Type' (PascalCase — the value 'IPLocation' selects the IP branch; ANY other value, including a missing one, falls through to the COUNTRIES branch), 'policyName' (camelCase, the display name), 'Ips' (newline-separated CIDR ranges, IP branch), 'Trusted' (boolean, IP branch, becomes isTrusted), 'Countries' (array of LabelValue objects read as '.value', countries branch), 'includeUnknownCountriesAndRegions' (boolean, countries branch). Send REAL JSON booleans — both are forwarded straight into the Graph payload. Per-tenant failures come back as text inside Results, never as an HTTP error.

ParamTypeRequiredDefaultDescription
fieldsJsonstringyesLocation fields as a JSON object merged into the request body LAST (so anything here overrides the seeded 'selectedTenants'). Body keys read upstream (exact casing): 'Type' ('IPLocation' selects the IP branch; anything else, or absent, means the countries branch), 'policyName' (string, display name), 'Ips' (newline-separated CIDR ranges, IP branch), 'Trusted' (real JSON boolean, IP branch), 'Countries' (array of LabelValue objects {"label":"<name>","value":"<isoCode>"} read as '.value', countries branch), 'includeUnknownCountriesAndRegions' (real JSON boolean, countries branch). There is NO 'tenantFilter' on this endpoint. Booleans are forwarded into the Graph payload, so send real JSON true/false, not strings.
selectedTenantsstringyesComma-separated tenant default domain names to create the named location in (e.g. "contoso.onmicrosoft.com,fabrikam.onmicrosoft.com"). Sent as the body key 'selectedTenants', built into CIPP's object array [{"value":"<domain>"}] because Invoke-AddNamedLocation reads $request.body.selectedTenants.value — a plain string creates NOTHING and still returns HTTP 200. WARNING: the single literal value 'AllTenants' expands upstream to EVERY tenant CIPP manages. Use cipp_list_tenants to discover domains.

[CIPP] Simulate ('what if') a Conditional Access evaluation via POST /api/ExecCACheck — read-only: no sign-in occurs and no policy is changed. Required body key: 'tenantFilter' (camelCase, seeded from the parameter). WARNING — 'userID' is read upstream as $Request.Body.userID.value, i.e. a LabelValue OBJECT {"label":"<id>","value":"<id>"}, NOT a plain string. A bare string resolves to null and the evaluation runs against a BLANK user while still returning HTTP 200 with plausible-looking results; pass the userId parameter and this tool builds the wrapper. Seven further keys are also read as '.value' LabelValue objects: 'ClientAppType', 'Country', 'DevicePlatform', 'IncludeApplications', 'SignInRiskLevel', 'UserRiskLevel', 'authenticationFlow' (whose .value becomes signInConditions.authenticationFlow.transferMethod). 'IpAddress' (PascalCase) is the ONE flat string — read directly, with no '.value'. When 'IncludeApplications' is absent CIPP substitutes the fixed app id 67ad5377-2d78-4ac2-a867-6300cda00e85. The client also copies tenantFilter onto the query string, but Invoke-ExecCACheck reads only $Request.Body.tenantFilter, so that copy is inert.

ParamTypeRequiredDefaultDescription
fieldsJsonstringyesAdditional CA evaluation parameters as a JSON object, merged into the request body LAST — anything here overrides the 'tenantFilter' and 'userID' seeded from the parameters. Keys pass through verbatim, so CIPP's exact casing is required. 'userID': a LabelValue OBJECT {"label":"<id>","value":"<id>"}, never a string (prefer the userId parameter, which builds it). 'IpAddress': a plain string — the only flat key. 'ClientAppType', 'Country', 'DevicePlatform', 'IncludeApplications', 'SignInRiskLevel', 'UserRiskLevel', 'authenticationFlow': LabelValue objects, read as '.value'. Any key CIPP does not read is ignored silently.
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Sent as the body key 'tenantFilter' (camelCase) — the only place CIPP reads it. Use cipp_list_tenants to discover available tenants.
userIdstringnonullDirectory object ID (GUID) of the user whose sign-in is being simulated. Sent as the body key 'userID' wrapped in CIPP's LabelValue shape {"label":"<value>","value":"<value>"}, because Invoke-ExecCACheck reads $Request.Body.userID.value. Omit it only if you supply a correctly-wrapped 'userID' object through fieldsJson — with neither, the what-if is submitted with an EMPTY userId and returns HTTP 200 for a blank user.

[CIPP] Manage Conditional Access user exclusions via POST /api/ExecCAExclusion — a WRITE that adds or removes a user's exclusion on a CA policy, and with 'vacation' schedules the add/remove pair as tasks. SIDE EFFECT ON EVERY CALL, including ExclusionType='remove': before it looks at 'vacation' or 'ExclusionType' at all, CIPP ensures a security group named 'Vacation Exclusion - <policy displayName>' exists in the target tenant — CREATING it through New-CIPPGroup when absent — and PATCHes that group into the policy's conditions.users.excludeGroups when it is not already there. Treat this tool as a policy write even when you are only removing a user. Required body key: 'tenantFilter' (camelCase, seeded from the parameter). THE USER IS THE TRAP: CIPP reads $Request.Body.UserID and $Request.Body.Username first, then — if $Request.Body.Users is present AT ALL — OVERWRITES both from $Users.value and $Users.addedFields.userPrincipalName. So 'Users' is an OBJECT (or array of objects) shaped {"value":"<objectId>","addedFields":{"userPrincipalName":"<upn>"}}; sent as an array of strings it nulls the user, excludes nobody at HTTP 200, and clobbers a correct top-level 'UserID' you also sent. Pass the userId / userPrincipalName parameters and this tool builds the whole shape. Other keys CIPP reads (exact casing): 'PolicyId', 'ExclusionType' (REQUIRED in practice for every non-vacation call and only settable through fieldsJson — Set-CIPPCAExclusion branches on `-eq 'add'` and `-eq 'remove'`, case-insensitively, with NO default arm, so any other value, including omitting the key, performs no Graph PATCH at all), 'StartDate' and 'EndDate' (epoch), 'vacation' (compared with -eq $true — send a REAL JSON boolean), 'excludeLocationAuditAlerts' (truthiness-tested — send a REAL JSON boolean, because the STRING "false" is TRUTHY in PowerShell and switches the branch ON), 'reference', 'postExecution', plus the travel pair 'CreateTravelPolicy' (real JSON boolean, -eq $true) and 'TravelCountries' (LabelValue objects read as '.value'), which together schedule a temporary travel policy alongside a vacation exclusion. The non-vacation branch never assigns the response $body, so a plain add/remove answers HTTP 200 with NO Results payload at all — an empty response is normal there and is evidence neither of failure nor of the PATCH having happened; confirm the outcome with cipp_list_ca_policies. There are NO top-level 'addedFields' or 'value' body keys — those names exist only INSIDE the 'Users' object and are ignored at the top level. The client also copies tenantFilter onto the query string; CIPP reads only $Request.Body.tenantFilter, so that copy is inert.

ParamTypeRequiredDefaultDescription
fieldsJsonstringyesAdditional exclusion parameters as a JSON object, merged into the request body LAST — anything here overrides the 'tenantFilter', 'UserID', 'Username' and 'Users' seeded from the parameters. Keys pass through verbatim, so CIPP's exact casing is required: 'PolicyId', 'ExclusionType' ('add' or 'remove', case-insensitive — this is the ONLY place it can be set, and there is NO default arm upstream, so any other value or an omitted key performs no Graph PATCH while the endpoint still answers HTTP 200), 'StartDate'/'EndDate' (epoch integers), 'vacation' (real JSON boolean), 'excludeLocationAuditAlerts' (real JSON boolean — the string "false" is TRUTHY upstream), 'reference', 'postExecution', 'CreateTravelPolicy' (real JSON boolean) and 'TravelCountries' (array of LabelValue objects, read as '.value'). 'Users', if you supply it here, must be the OBJECT shape {"value":"<objectId>","addedFields":{"userPrincipalName":"<upn>"}} — an array of strings silently excludes nobody. 'addedFields' and 'value' are NOT top-level keys and are ignored there.
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Sent as the body key 'tenantFilter' (camelCase) — the only place CIPP reads it. Use cipp_list_tenants to discover available tenants.
userIdstringnonullDirectory object ID (GUID) of the user to exclude or un-exclude. Seeds the body key 'UserID' and the 'Users' object CIPP actually reads ($Request.Body.Users.value). Omit only if you supply a correctly-shaped 'Users' object through fieldsJson.
userPrincipalNamestringnonullUser principal name of the same user (e.g. user@contoso.com). Seeds body 'Username' and Users.addedFields.userPrincipalName, which is what the scheduled vacation task uses as its group Member. Requires userId — a UPN alone is refused, because CIPP needs the object ID in Users.value.

[CIPP] Add the service-provider exception to a Conditional Access policy for a tenant via POST /api/ExecCAServiceExclusion — a WRITE that edits the named policy. Required body keys: 'tenantFilter' (camelCase) and 'GUID' (all-caps — the CA policy ID). CIPP resolves each as $Request.Query.X ?? $Request.Body.X; the client puts ONLY tenantFilter on the query string, so 'GUID' travels on the body and resolves through that fallback. Failures are returned as text inside Results with HTTP 200 — check the Results string, not the status code. Use cipp_list_ca_policies to find the policy GUID.

ParamTypeRequiredDefaultDescription
fieldsJsonstringyesRequired JSON object merged into the request body. Body keys read upstream (exact casing): 'GUID' (string, all-caps — the CA policy GUID). It is sent on the BODY only; CIPP's $Request.Query.GUID ?? $Request.Body.GUID fallback resolves it there.
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Sent both on the query string and as the body key 'tenantFilter' (camelCase); CIPP prefers the query copy and falls back to the body. Use cipp_list_tenants to discover available tenants.

[CIPP] Modify or delete an existing Conditional Access named location via POST /api/ExecNamedLocation. Body keys, all camelCase: required 'tenantFilter' (seeded from the parameter), 'namedLocationId' (the location GUID), 'change' and 'input'. CIPP resolves each as $Request.Body.X ?? $Request.Query.X (BODY FIRST); the client puts only tenantFilter on the query string, so the other three travel on the body and resolve through that fallback. 'change' is validated upstream against a fixed set — 'addIp', 'addLocation', 'removeIp', 'removeLocation', 'rename', 'setTrusted', 'setUntrusted', 'delete' — and any other value is rejected. 'input' carries that change's payload (a CIDR range for addIp/removeIp, a country or region code for addLocation/removeLocation, the new display name for rename; unused by setTrusted/setUntrusted/delete) and may be a plain string OR a LabelValue object, because CIPP unwraps '.value' when present. WARNING: change='delete' removes the named location outright. Failures are returned as text inside Results with HTTP 500.

ParamTypeRequiredDefaultDescription
fieldsJsonstringyesRequired JSON object merged into the request body (LAST, so anything here overrides the seeded 'tenantFilter'). Body keys read upstream (exact casing, all camelCase): 'namedLocationId' (string, the location GUID), 'change' (one of 'addIp', 'addLocation', 'removeIp', 'removeLocation', 'rename', 'setTrusted', 'setUntrusted', 'delete' — validated upstream, anything else is rejected), 'input' (the payload for that change: CIDR range, country/region code, or new display name; a plain string or a LabelValue object, since CIPP unwraps '.value'). All three are sent on the BODY only; CIPP's body-first fallback resolves them there.
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Sent both on the query string and as the body key 'tenantFilter' (camelCase); CIPP reads the body copy first and falls back to the query. Use cipp_list_tenants to discover available tenants.

[CIPP] List all Conditional Access policy templates available in CIPP. Returns template names, descriptions, and policy definitions.

[CIPP] Delete a Conditional Access policy from a tenant via POST /api/RemoveCAPolicy (a Graph DELETE on the policy). WARNING: removing a CA policy may immediately change the tenant's security posture. Required body keys: 'tenantFilter' (camelCase) and 'GUID' (all-caps — the policy GUID). CIPP resolves each as $Request.Query.X ?? $Request.Body.X; the client puts ONLY tenantFilter on the query string, so 'GUID' travels on the body and resolves through that fallback. WARNING — if no GUID resolves at all, Invoke-RemoveCAPolicy calls `exit` and returns NO response body: no error, no Results, nothing deleted. A delete that fails returns HTTP 403 with the reason as text inside Results. Use cipp_list_ca_policies to find the policy GUID.

ParamTypeRequiredDefaultDescription
fieldsJsonstringyesRequired JSON object merged into the request body. Body keys read upstream (exact casing): 'GUID' (string, all-caps — the CA policy GUID). It is sent on the BODY only; CIPP's $Request.Query.GUID ?? $Request.Body.GUID fallback resolves it there. Omitting it makes the endpoint exit with an empty response and delete nothing.
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Sent both on the query string and as the body key 'tenantFilter' (camelCase); CIPP prefers the query copy and falls back to the body. Use cipp_list_tenants to discover available tenants.

[CIPP] Delete a Conditional Access policy template via POST /api/RemoveCATemplate. Required body key: 'ID' (all-caps — the template GUID). CIPP resolves it as $Request.Query.ID ?? $Request.Body.ID; the client sends NO query string at all for this endpoint, so 'ID' travels on the body and resolves through that fallback. This endpoint takes no tenant — templates are stored CIPP-side, not per tenant. Success answers HTTP 200 with Results = "Removed Conditional Access Template with ID <id>"; a failure answers HTTP 500 with Results = "Failed to remove Conditional Access template <id>: <error>". Unlike the other CA exec endpoints, this one does NOT flatten failures into a 200 — trust the status code here. Use cipp_list_ca_templates to find the template ID.

ParamTypeRequiredDefaultDescription
fieldsJsonstringyesRequired JSON object merged into the request body. Body keys read upstream (exact casing): 'ID' (string, all-caps — the CA template GUID). It is sent on the BODY only; CIPP's $Request.Query.ID ?? $Request.Body.ID fallback resolves it there.
ToolPlanAccessSummary
cipp_add_safe_links_from_templateProDestructiveDeploy one or more Safe Links policy templates to one or more tenants in bulk via POST /api/AddSafeLinksPolicyFromTemplate.
cipp_add_safe_links_templateProWriteCreate a new Safe Links policy template in the CIPP template store via POST /api/AddSafeLinksPolicyTemplate.
cipp_create_safe_links_policyProDestructiveCreate a new Defender for Office Safe Links policy + rule pair in a tenant via POST /api/ExecNewSafeLinksPolicy.
cipp_create_safe_links_templateProWriteCreate a NEW Safe Links policy template in the CIPP template store from the fields in this request via POST /api/CreateSafeLinksPolicyTemplate.
cipp_delete_safe_links_policyProDestructivePermanently delete a Safe Links policy + rule pair from a tenant via POST /api/ExecDeleteSafeLinksPolicy.
cipp_edit_safe_links_policyProDestructiveModify an existing Safe Links policy + rule pair via POST /api/EditSafeLinksPolicy.
cipp_edit_safe_links_templateProDestructiveReplace the stored contents of an existing Safe Links policy template in CIPP via POST /api/EditSafeLinksPolicyTemplate.
cipp_list_safe_links_detailsFreeRead-onlyGet the full configuration of a specific Safe Links policy in a tenant via POST /api/ListSafeLinksPolicyDetails.
cipp_list_safe_links_template_detailsFreeRead-onlyGet the full configuration of a specific Safe Links policy template stored in CIPP via POST /api/ListSafeLinksPolicyTemplateDetails.
cipp_list_safe_links_templatesFreeRead-onlyList all Safe Links policy templates available in CIPP.
cipp_remove_safe_links_templateProDestructivePermanently delete a Safe Links policy template from the CIPP template store via POST /api/RemoveSafeLinksPolicyTemplate.

Teams & SharePoint

ToolPlanAccessSummary
cipp_add_siteProWriteCreate a new SharePoint site via POST /api/AddSite.
cipp_add_site_bulkProWriteCreate multiple SharePoint sites in bulk via POST /api/AddSiteBulk.
cipp_add_teamProWriteCreate a new Microsoft Team for a tenant via POST /api/AddTeam.
cipp_assign_teams_voice_numberProDestructiveAssign a Teams phone number to a user or resource account — or set a number's emergency location — via POST /api/ExecTeamsVoicePhoneNumberAssignment.
cipp_delete_sharepoint_siteProDestructiveDelete a SharePoint site via POST /api/DeleteSharepointSite.
cipp_get_sharepoint_quotaFreeRead-onlyGet SharePoint Online storage quota and usage for a tenant.
cipp_get_sharepoint_settingsFreeRead-onlyGet SharePoint Online tenant-level settings.
cipp_list_sharepoint_admin_urlFreeRead-onlyGet the SharePoint admin center URL for ONE tenant via GET /api/ListSharepointAdminUrl.
cipp_list_site_membersFreeRead-onlyList members of a SharePoint site via GET /api/ListSiteMembers.
cipp_list_sitesFreeRead-onlyList SharePoint sites (or OneDrive usage accounts) for a tenant via GET /api/ListSites.
cipp_list_teamsFreeRead-onlyList all Microsoft Teams for a tenant.
cipp_list_teams_activityFreeRead-onlyList Microsoft Teams activity reports for a tenant.
cipp_list_teams_lis_locationFreeRead-onlyList ONE tenant's Teams Location Information Service (LIS) locations via GET /api/ListTeamsLisLocation.
cipp_list_teams_voiceFreeRead-onlyList Microsoft Teams voice and telephony configuration for a tenant.
cipp_remove_teams_voice_numberProDestructiveRemove a phone number assignment from a Teams user via POST /api/ExecRemoveTeamsVoicePhoneNumberAssignment.
cipp_set_sharepoint_memberProWriteAdd or REMOVE a user in a SharePoint site role via POST /api/ExecSetSharePointMember.
cipp_set_sharepoint_permissionsProDestructiveGrant or revoke SITE COLLECTION ADMINISTRATOR rights on a OneDrive or SharePoint site via POST /api/ExecSharePointPerms.

[CIPP] Create a new SharePoint site via POST /api/AddSite. Body keys: flat strings 'tenantFilter', 'siteName', 'siteDescription', 'sensitivityLabel'; plus THREE LabelValue objects — 'siteOwner', 'templateName', 'siteDesign' — each shaped {"label":<v>,"value":<v>} because CIPP reads $Body.<key>.value. The tool builds those wrappers for you. REQUIRED upstream: siteName, siteDescription and siteOwner (all three are [Parameter(Mandatory=$true)] on New-CIPPSharepointSite) plus templateName and siteDesign — CIPP splats those two unconditionally, so an absent key binds $null and fails the downstream ValidateSet instead of taking a default; the tool therefore always sends them, defaulting to 'Communication'/'Showcase'. templateName accepts EXACTLY 'Communication' (SITEPAGEPUBLISHING#0) or 'Team' (STS#3) — 'CommunicationSite'/'TeamSite'/an M365 group template are NOT valid. siteDesign accepts EXACTLY 'Topic', 'Showcase', 'Blank' or 'Custom' — a design GUID is NOT valid here. There is NO 'siteUrl' key: the URL is derived as <tenant>.sharepoint.com/sites/<siteName with spaces removed and every character other than A-Z, a-z, 0-9 and '-' stripped> — hyphens SURVIVE, so 'Marketing - Team' becomes /sites/Marketing-Team. The real URL is echoed in the success message ('Successfully created new SharePoint site <name> with URL <url>'), so read it from the response rather than reconstructing it. The site is created in English (LCID 1033) — this endpoint never passes a language.

ParamTypeRequiredDefaultDescription
additionalFieldsJsonstringnonullForward-compatibility escape hatch: a JSON object whose properties are merged into the request body LAST, overriding any of the typed parameters above. Use only for new CIPP fields not yet exposed as typed parameters. Keys are passed verbatim — caller is responsible for exact spec casing and for the LabelValue shape where CIPP reads '.value'.
sensitivityLabelstringnonullOptional sensitivity label ID (Microsoft Purview / MIP label) to apply to the site. Sent as the FLAT body key 'sensitivityLabel' (not a LabelValue object); omitted when not supplied.
siteDescriptionstringyesREQUIRED — description shown on the site. Mandatory upstream (New-CIPPSharepointSite declares -SiteDescription [Parameter(Mandatory=$true)]), so an empty value fails parameter binding and no site is created. Sent as flat body key 'siteDescription'.
siteDesignstringno"Showcase"Site design. EXACTLY 'Topic', 'Showcase' (default), 'Blank' or 'Custom' — a custom design GUID is NOT accepted here. WARNING: 'Custom' cannot deliver a custom design through this endpoint. Upstream hard-resets WebTemplateExtensionId to the all-zero GUID immediately before the switch, and the Custom arm then sends SiteDesignId as that zero GUID with the literal string 'Custom' in WebTemplateExtensionId — a non-GUID in a GUID field. This endpoint exposes no way to supply a real design id, so prefer Topic/Showcase/Blank. Always sent, as the LabelValue object 'siteDesign'.
siteNamestringyesDisplay name for the new SharePoint site. Sent as flat body key 'siteName'. The site URL is derived from it: spaces are removed and every character other than A-Z, a-z, 0-9 and '-' is stripped — hyphens survive ('Marketing - Team' → /sites/Marketing-Team). The created URL comes back in the success message; read it there rather than predicting it.
siteOwnerstringyesREQUIRED — site owner UPN (e.g., 'admin@contoso.com'). Mandatory upstream. Sent as the LabelValue object 'siteOwner': {"label":<upn>,"value":<upn>} — CIPP reads $Body.siteOwner.value, so a plain string binds $null and fails.
templateNamestringno"Communication"Site template. EXACTLY 'Communication' (default, SITEPAGEPUBLISHING#0) or 'Team' (STS#3) — the downstream ValidateSet accepts nothing else; 'CommunicationSite'/'TeamSite' are rejected (the tool maps those two spellings for you). Always sent, as the LabelValue object 'templateName', because CIPP splats $Body.templateName.value unconditionally.
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Sent in the body as 'tenantFilter' (camelCase) and on the query string. Use cipp_list_tenants to discover available tenants.

[CIPP] Create multiple SharePoint sites in bulk via POST /api/AddSiteBulk. Body: 'tenantFilter' (camelCase, seeded by the tool) and 'bulkSites' — an ARRAY OF OBJECTS, one per site. CIPP iterates bulkSites and reads every site field off the ELEMENT, never off the top level: per-element keys are 'siteName', 'siteDescription', 'siteOwner' (all three mandatory downstream), 'templateName' ('Communication' or 'Team'), 'siteDesign' ('Topic'/'Showcase'/'Blank'/'Custom') and optional 'sensitivityLabel'. UNLIKE cipp_add_site these are FLAT STRINGS — the bulk endpoint does NOT dereference '.value', so LabelValue objects fail here. templateName/siteDesign are splatted unconditionally, so give every element a valid value; a missing or invalid one fails that site with 'Failed to create ... Error message: ...' inside the 200 response. WARNING — SILENT NO-OP: the endpoint hard-codes HTTP 200, so a body with no bulkSites array (for example one built from top-level siteName/siteOwner keys, which this endpoint never reads) creates NOTHING and still answers 200 with an empty Results list. The tool refuses that shape before dispatch. There is NO 'siteUrl' key — each URL is derived from its siteName.

ParamTypeRequiredDefaultDescription
fieldsJsonstringyesREQUIRED JSON object merged into the request body. It MUST carry 'bulkSites' as a non-empty ARRAY OF OBJECTS — e.g. {"bulkSites":[{"siteName":"Marketing","siteDescription":"Marketing team site","siteOwner":"admin@contoso.com","templateName":"Communication","siteDesign":"Showcase"}]}. Per-element keys (flat strings, exact casing): siteName, siteDescription, siteOwner (all required), templateName ('Communication'|'Team'), siteDesign ('Topic'|'Showcase'|'Blank'|'Custom'), sensitivityLabel (optional). Top-level site keys are NEVER read by this endpoint, and neither are LabelValue {"value":...} wrappers. Keys are passed verbatim — the tool does not rewrite anything you put here.
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Seeded into the body as 'tenantFilter' (camelCase) — the only tenant key this endpoint reads. Use cipp_list_tenants to discover available tenants.

[CIPP] Create a new Microsoft Team for a tenant via POST /api/AddTeam. TENANT KEY: CIPP reads the tenant from the body key named 'tenantid' and from nowhere else. The NAME is load-bearing, the casing is not — PowerShell reads the body case-insensitively, but this endpoint never reads 'tenantFilter'. The tool ALREADY seeds it from tenantFilter, so never send 'tenantid' through fieldsJson in ANY casing: fieldsJson merges LAST, so an exact-case key retargets the create at another tenant, and a differently-cased one is emitted ALONGSIDE the seeded key (the merge is case-sensitive) rather than replacing it — and with both keys on the wire, which one CIPP then reads is UNDEFINED, so that is not a safe fallback either. REQUIRED: 'displayName' AND 'owner' — CIPP throws 'You have to add at least one owner to the team' (returned as HTTP 500) when 'owner' is absent, so there is no ownerless create. 'owner' may be one UPN or an array of UPNs. Optional: 'description', 'visibility' ('Private' or 'Public', forwarded to Graph as-is). The team is POSTed to Graph beta /teams from the v1.0 'standard' teams template, with every owner bound as an aadUserConversationMember carrying the owner role.

ParamTypeRequiredDefaultDescription
descriptionstringnonullOptional team description. Sent as body key 'description'. Omitted from the body when not supplied.
displayNamestringyesDisplay name for the new team. Sent as body key 'displayName'.
fieldsJsonstringnonullForward-compatibility escape hatch: a JSON object whose properties are merged into the request body LAST, overriding the typed parameters above. Keys are passed verbatim. Do NOT resend 'tenantid' here in ANY casing — the tool already seeds the resolved tenant; an exact-case key sends the create to a different tenant, and a differently-cased one is emitted alongside the seeded key instead of replacing it, leaving which of the two CIPP reads UNDEFINED.
ownerstringyesREQUIRED — at least one owner UPN (e.g., 'admin@contoso.com'). CIPP hard-fails with 'You have to add at least one owner to the team' when the body carries no 'owner'. For several owners pass a comma-separated list; the tool sends them as a JSON array, which is what CIPP enumerates. Sent as body key 'owner'.
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Seeded into the body as 'tenantid' — the ONLY body key CIPP reads for the tenant. The name is load-bearing, the casing is not: 'tenantFilter' is never read there. Also sent on the query string. Use cipp_list_tenants to discover available tenants.
visibilitystringnonullOptional team visibility — 'Private' or 'Public'. Forwarded to the Graph team-create payload verbatim; omitted from the body when not supplied. Sent as body key 'visibility'.

[CIPP] Assign a Teams phone number to a user or resource account — or set a number's emergency location — via POST /api/ExecTeamsVoicePhoneNumberAssignment. Body keys: 'TenantFilter' (PascalCase, seeded by the tool — do NOT resend it through fieldsJson), 'PhoneNumber' (E.164, e.g. '+15551234567'), 'input' (lowercase, an OBJECT — CIPP reads $Body.input.value, so a plain string leaves the target null), 'PhoneNumberType' ('DirectRouting'/'CallingPlan'/'OperatorConnect', mapped to the Graph camelCase value), optional 'AssignmentCategory' (added to the Graph assignNumber payload when present) and 'locationOnly'. WARNING — 'locationOnly' is a bare PowerShell truthiness test, NOT a comparison: the STRING "false" is TRUE in PowerShell, so sending locationOnly as a string silently takes the emergency-location branch (POST updateNumber with locationId set to your identity) and the number is NEVER assigned. The tool sends a real JSON boolean and omits the key entirely when false; never resend locationOnly as a string through fieldsJson. assignNumber is asynchronous (Graph answers 202), so success means the assignment was submitted. Note CIPP returns HTTP 403 for ANY failure of this endpoint — a 403 here is its generic error status, not necessarily a permissions problem.

ParamTypeRequiredDefaultDescription
assignmentCategorystringnonullOptional assignment category, added to the Graph assignNumber payload as assignmentCategory only when present (ignored on the locationOnly path). Sent as body key 'AssignmentCategory' (PascalCase).
fieldsJsonstringnonullForward-compatibility escape hatch: a JSON object whose properties are merged into the request body LAST, overriding the typed parameters above. Keys are passed verbatim. Do NOT resend 'TenantFilter' (the tool already seeds the resolved tenant) and do NOT resend 'locationOnly' as a string.
identitystringyesThe target. For an ASSIGNMENT (locationOnly=false, the default): the user's UPN or object GUID — CIPP resolves a UPN to an object id through Graph. For locationOnly=true: the emergency LOCATION id, which CIPP sends as locationId. Sent as the OBJECT 'input': {"label":<v>,"value":<v>} — CIPP reads $Body.input.value, so a plain string leaves the target null.
locationOnlybooleannofalsefalse (default) assigns the number to the identity. true instead sets the number's emergency location, using 'identity' as the location id — the number is NOT assigned on that path. Sent as the JSON boolean 'locationOnly' and OMITTED from the body when false, because CIPP truthiness-tests this key (any non-empty string, including "false", takes the location branch).
phoneNumberstringyesThe phone number in E.164 format, e.g. '+15551234567'. Sent as body key 'PhoneNumber' and used as telephoneNumber on both the assign and the emergency-location call. Use cipp_list_teams_voice to discover the tenant's numbers.
phoneNumberTypestringnonullNumber type: 'DirectRouting', 'CallingPlan' or 'OperatorConnect' — CIPP maps those three, case-insensitively, to the Graph values directRouting/callingPlan/operatorConnect and PASSES ANY OTHER VALUE THROUGH UNMAPPED for Graph to reject (the mapper validates nothing). Sent as body key 'PhoneNumberType' and effectively REQUIRED on the assign path: CIPP writes numberType into the assignNumber payload unconditionally, so omitting this sends an EMPTY numberType and Graph fails the call — which this endpoint reports as its generic HTTP 403. Unused on the locationOnly path. Omitted from the body when not supplied.
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Seeded into the body as 'TenantFilter' (PascalCase — the only key CIPP reads) and sent on the query string. Use cipp_list_tenants to discover available tenants.

[CIPP] Delete a SharePoint site via POST /api/DeleteSharepointSite. Required body keys (exact casing): 'tenantFilter' (camelCase) and 'SiteId' (PascalCase). SiteId MUST be a GUID — CIPP regex-validates it and throws 'SiteId must be a valid GUID' for a site URL or any other identifier; discover the GUID with cipp_list_sites. WARNING: for a GROUP-CONNECTED site CIPP calls GroupSiteManager/Delete, which deletes the backing Microsoft 365 group AND its Team along with the site; other sites go through SPSiteManager/delete. The group-connected path is documented upstream as a SOFT delete that registers the site in SharePoint's deleted-sites list; the SPSiteManager path's retention behaviour is SharePoint's own and CIPP asserts nothing about it, so do not promise that site is recoverable. Both run in the BACKGROUND — a success response means the deletion was initiated, not finished.

ParamTypeRequiredDefaultDescription
fieldsJsonstringyesRequired JSON object merged into the request body. It MUST carry 'SiteId' (PascalCase) and the value MUST be a GUID — e.g. {"SiteId":"b1f0e9e2-4b7a-4c3d-9a1e-0f2c6d8a1234"}. CIPP rejects a non-GUID with 'SiteId must be a valid GUID', so a site URL will not work; use cipp_list_sites to get the site's GUID. Keys are passed verbatim — caller is responsible for exact spec casing.
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Sent on the body as 'tenantFilter' (camelCase) per CIPP spec. Use cipp_list_tenants to discover available tenants.

[CIPP] Get SharePoint Online storage quota and usage for a tenant. Returns total storage, used storage, and per-site breakdown.

ParamTypeRequiredDefaultDescription
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Use cipp_list_tenants to discover available tenants.

[CIPP] Get SharePoint Online tenant-level settings. Returns sharing defaults, external sharing policy, and site creation settings.

ParamTypeRequiredDefaultDescription
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Use cipp_list_tenants to discover available tenants.

[CIPP] Get the SharePoint admin center URL for ONE tenant via GET /api/ListSharepointAdminUrl. Query: 'tenantFilter' (camelCase, REQUIRED) — CIPP-API master @df3738d refuses the call outright without it and answers the literal 'TenantFilter is required'.

ParamTypeRequiredDefaultDescription
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Use cipp_list_tenants to discover available tenants.

[CIPP] List members of a SharePoint site via GET /api/ListSiteMembers. Returns the user / group membership for the site identified by SiteId. CIPP query keys per spec: 'tenantFilter' (camelCase, required) and 'SiteId' (PascalCase). NOTE: this endpoint is in the live CIPP spec but missing from the checked-in CIPP-API.json — verify behavior against the live backend if responses look unexpected.

ParamTypeRequiredDefaultDescription
siteIdstringnonullSharePoint site ID whose members to list. Sent as the 'SiteId' query parameter (PascalCase) per CIPP spec. Use cipp_list_sites to discover site IDs.
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Sent as the 'tenantFilter' query parameter (camelCase, required) per CIPP spec. Use cipp_list_tenants to discover available tenants.

[CIPP] List SharePoint sites (or OneDrive usage accounts) for a tenant via GET /api/ListSites. Returns site URL, display name, owner, storage used/allocated, and last-activity data. REQUIRED CIPP query parameter 'Type': CIPP returns HTTP 400 'Type is required' if it is omitted. Valid values are 'SharePointSiteUsage' (SharePoint team sites — isPersonalSite eq false) and 'OneDriveUsageAccount' (personal OneDrive sites — isPersonalSite eq true). Defaults to 'SharePointSiteUsage', which is the value that lists SharePoint team sites.

ParamTypeRequiredDefaultDescription
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Use cipp_list_tenants to discover available tenants.
typestringno"SharePointSiteUsage"SharePoint report scope, sent as the CIPP 'Type' query parameter (required by CIPP). Valid values: 'SharePointSiteUsage' (SharePoint team sites, the default) or 'OneDriveUsageAccount' (personal OneDrive sites). Case-insensitive; normalized to the canonical CIPP casing before the call.

[CIPP] List all Microsoft Teams for a tenant. Returns team display name, visibility, member count, and archive status.

ParamTypeRequiredDefaultDescription
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Use cipp_list_tenants to discover available tenants.

[CIPP] List Microsoft Teams activity reports for a tenant. Returns usage metrics including messages, calls, and meetings per team.

ParamTypeRequiredDefaultDescription
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Use cipp_list_tenants to discover available tenants.

[CIPP] List ONE tenant's Teams Location Information Service (LIS) locations via GET /api/ListTeamsLisLocation. Returns network subnets, addresses, and emergency calling locations. Query: 'tenantFilter' (camelCase, REQUIRED). CIPP's Teams helper declares the tenant mandatory at CIPP-API master @df3738d, so a call without one dies on a parameter bind that CIPP returns as HTTP 403 — that 403 is a missing-parameter failure, not an authorization verdict.

ParamTypeRequiredDefaultDescription
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Use cipp_list_tenants to discover available tenants.

[CIPP] List Microsoft Teams voice and telephony configuration for a tenant. Returns calling plans, phone numbers, and voice policies.

ParamTypeRequiredDefaultDescription
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Use cipp_list_tenants to discover available tenants.

[CIPP] Remove a phone number assignment from a Teams user via POST /api/ExecRemoveTeamsVoicePhoneNumberAssignment. UNLIKE cipp_assign_teams_voice_number, this endpoint uses camelCase 'tenantFilter' in the body. Body keys (exact spec casing): 'tenantFilter' (camelCase, required), 'AssignedTo' (PascalCase, string — the user UPN/ID currently holding the number), 'PhoneNumber' (PascalCase string), 'PhoneNumberType' (PascalCase string).

ParamTypeRequiredDefaultDescription
fieldsJsonstringyesRequired JSON object merged into the request body. Spec-listed body keys (exact casing): 'AssignedTo' (PascalCase, the current owner UPN/ID), 'PhoneNumber' (PascalCase), 'PhoneNumberType' (PascalCase). Caller is responsible for exact spec casing.
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Sent on the body as 'tenantFilter' (camelCase) per CIPP spec. Use cipp_list_tenants to discover available tenants.

[CIPP] Add or REMOVE a user in a SharePoint site role via POST /api/ExecSetSharePointMember. Body keys: 'tenantFilter' (camelCase), 'user' (lowercase, an OBJECT — CIPP reads $Body.user.value, so a plain string throws 'No user was selected.'; the tool builds the wrapper), 'Add' (JSON boolean, compared with -eq $true), 'Role' (PascalCase key; one of 'Owners', 'Members' or 'Visitors', matched case-insensitively upstream — CIPP defaults to 'Members' when absent), 'SharePointType' (PascalCase) and 'GroupID'/'URL'. WARNING — 'SharePointType' is the SITE KIND, not the role: CIPP compares it to the literal 'Group' to choose the M365-group (Graph) path; anything else takes the classic SharePoint role-group path. ROUTING: Owners/Members on a group-connected site ('Group') go through the backing M365 group and need 'GroupID' (a group GUID, or a mail / mailNickname / proxyAddress CIPP resolves via Graph). Visitors ALWAYS, and every classic or communication site, go through the site's SharePoint role group and need 'URL' (the site URL) — CIPP throws 'No site URL was provided for this site.' without it. add=false REMOVES the user from that role.

ParamTypeRequiredDefaultDescription
addbooleanyesREQUIRED. true ADDS the user to the role; false REMOVES them. Sent as the JSON boolean 'Add' (CIPP evaluates $Body.Add -eq $true). There is deliberately no default: upstream treats an absent 'Add' as false, i.e. a REMOVAL.
additionalFieldsJsonstringnonullForward-compatibility escape hatch: a JSON object whose properties are merged into the request body LAST, overriding any of the typed parameters above. Use only for new CIPP fields not yet exposed as typed parameters. Keys are passed verbatim — caller is responsible for exact spec casing and shape.
groupIdstringnonullIdentifier of the Microsoft 365 group backing a group-connected site: a group GUID, or a mail address / mailNickname / proxyAddress that CIPP resolves through Graph. Required when sharePointType='Group' and role is Owners or Members. Sent as body key 'GroupID' (PascalCase, all-caps ID).
rolestringno"Members"SharePoint role — one of 'Owners', 'Members' (the CIPP default) or 'Visitors', plural. Matched case-insensitively upstream, and this tool normalises the casing for you; anything else answers HTTP 400 "Invalid role '<value>'." naming the three valid roles. Sent as body key 'Role'. Note 'Visitors' always takes the classic role-group path and therefore requires url.
sharePointTypestringnonullSite kind — 'Group' for a group-connected (Microsoft 365 group / Team) site, or 'Site' for a classic or communication site. This is NOT the membership role (use the role parameter for that). Sent as body key 'SharePointType'; omitted when not supplied, which CIPP treats as a classic site.
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Sent in the body as 'tenantFilter' (camelCase — the only key CIPP reads) and on the query string. Use cipp_list_tenants to discover available tenants.
urlstringnonullFull site URL (e.g., 'https://contoso.sharepoint.com/sites/Marketing'). Required for classic/communication sites and for the 'Visitors' role on ANY site — those paths manage the SharePoint role group over REST and CIPP throws 'No site URL was provided for this site.' when it is missing. Sent as body key 'URL' (all-caps). Discover site URLs with cipp_list_sites.
userstringyesUPN of the user being added or removed (e.g., 'user@contoso.com'). Sent as the OBJECT 'user': {"label":<upn>,"value":<upn>} — CIPP reads $Body.user.value and throws 'No user was selected.' for a plain string.

[CIPP] Grant or revoke SITE COLLECTION ADMINISTRATOR rights on a OneDrive or SharePoint site via POST /api/ExecSharePointPerms. This is NOT a read-access grant: CIPP sets the IsSiteAdmin flag on the target site's user entry and calls the result 'a site collection admin of <url>' — in SharePoint that is full control of the whole site collection. The usual reason to use it is giving an admin access to a departed employee's OneDrive; revoking clears the same flag. The CIPP body uses MIXED casing: 'RemovePermission' (PascalCase), 'UPN' (all-caps — the OWNER of the OneDrive being modified), 'URL' (all-caps — the site/OneDrive URL), 'onedriveAccessUser' (camelCase) and 'user' (lowercase), plus 'tenantFilter' (camelCase). The user being granted or revoked is read as onedriveAccessUser ?? user, so EITHER key works — supplying neither returns HTTP 400 'No user specified.'. Both keys accept a UPN, an array of UPNs, or a {"value":<upn>} object; CIPP normalises all three. 'RemovePermission' is evaluated as ($RemovePermission -ne $true) → grant, so omitted or false GRANTS and true REVOKES; the tool sends a real JSON boolean (the string 'true'/'false' also compares correctly here, unlike cipp_assign_teams_voice_number's locationOnly). There is NO 'siteUrl' key — the URL goes in 'URL'. RESPONSE: one Results entry PER USER, inside an HTTP 200 — a user who could not be resolved or updated comes back as 'Failed to change access for <user> on <url> - <reason>' in that list, not as an error status; only a failure BEFORE the per-user loop (unresolvable OneDrive URL, unresolvable site scope, or no valid user) returns 400. Read every Results string rather than trusting the status code.

ParamTypeRequiredDefaultDescription
additionalFieldsJsonstringnonullForward-compatibility escape hatch: a JSON object whose properties are merged into the request body LAST, overriding any of the typed parameters above. Use only for new CIPP fields not yet exposed as typed parameters. Keys are passed verbatim — caller is responsible for exact spec casing.
onedriveAccessUserstringnonullUPN of the user being made — or un-made — a SITE COLLECTION ADMIN of someone else's OneDrive or of a SharePoint site. That is full control of the site collection, not read access. Sent as body key 'onedriveAccessUser' (camelCase); CIPP prefers this key over 'user'.
removePermissionbooleannonulltrue REVOKES the site collection admin rights; false or omitted GRANTS them (CIPP computes $IsSiteAdmin = $RemovePermission -ne $true). Sent as the JSON boolean 'RemovePermission'; omitted from the body when not supplied.
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Sent in the body as 'tenantFilter' (camelCase) and on the query string. Use cipp_list_tenants to discover available tenants.
upnstringnonullUPN of the OneDrive OWNER — the person whose site is being changed, NOT the person being granted rights. Sent as body key 'UPN' (all-caps). It is used ONLY to derive the site URL when 'url' is omitted: CIPP resolves that user's OneDrive site collection through Graph and throws 'Could not determine the OneDrive site URL for <upn>' if they have no provisioned OneDrive. When 'url' IS supplied, 'UPN' is ignored entirely — so pass one or the other, never a mismatched pair (a wrong UPN alongside a URL is silently ignored, not an error).
urlstringnonullURL of the SharePoint site or OneDrive being modified. Sent as body key 'URL' (all-caps) — distinct from the OneDrive owner UPN. Use cipp_list_sites to discover site and OneDrive URLs.
userstringnonullUPN of the user being made — or un-made — a site collection admin of the target site/OneDrive. Sent as body key 'user' (lowercase). CIPP reads onedriveAccessUser ?? user, so this is the fallback key for the same person — supply this OR onedriveAccessUser; supplying neither returns HTTP 400 'No user specified.'.

Standards

ToolPlanAccessSummary
cipp_add_bpa_templateProWriteSave a new Best Practice Analyzer template via POST /api/AddBPATemplate.
cipp_add_standards_templateProDestructiveCreate or REPLACE a reusable CIPP standards template via POST /api/AddStandardsTemplate.
cipp_deploy_standardsProDestructiveCreate or REPLACE a tenant's standards deployment via POST /api/AddStandardsDeploy.
cipp_drift_cloneProWriteClone a drift template into a new standards baseline via POST /api/ExecDriftClone.
cipp_list_bpaFreeRead-onlyList Best Practice Analyzer results for a tenant.
cipp_list_bpa_templatesFreeRead-onlyList saved Best Practice Analyzer templates.
cipp_list_domain_analyserFreeRead-onlyRun detailed domain analysis for a tenant.
cipp_list_domain_healthFreeRead-onlyRun ONE real-time DNS / email-security check against ONE domain via GET /api/ListDomainHealth.
cipp_list_standard_templatesFreeRead-onlyList saved CIPP standard templates.
cipp_list_standardsFreeRead-onlyList deployed CIPP standards for a tenant.
cipp_list_standards_compareFreeRead-onlyCompare a tenant's current configuration against the CIPP standards template.
cipp_list_tenant_driftFreeRead-onlyDetect configuration drift for a tenant.
cipp_remove_bpa_templateProDestructiveRemove a saved Best Practice Analyzer template via POST /api/RemoveBPATemplate.
cipp_remove_standardProDestructiveRemove a tenant's deployed standards row via GET /api/RemoveStandard.
cipp_remove_standard_templateProDestructiveRemove a saved CIPP standards template via POST /api/RemoveStandardTemplate.
cipp_run_bpaProWriteQueue a Best Practice Analyzer run for a tenant via POST /api/ExecBPA.
cipp_standard_convertProDestructiveConvert EVERY legacy standards row in the entire CIPP instance to the current StandardsTemplateV2 format via GET /api/ExecStandardConvert.
cipp_standards_runProDestructiveTrigger a CIPP standards ENFORCEMENT run via GET /api/ExecStandardsRun.
cipp_update_drift_deviationProDestructiveUpdate drift deviation statuses for a tenant, or clear that tenant's drift customizations, via POST /api/ExecUpdateDriftDeviation.

[CIPP] Save a new Best Practice Analyzer template via POST /api/AddBPATemplate. Required body keys (exact spec casing, both lowercase): 'name' (string, the template name) and 'style' (string enum 'Tenant' | 'Table' — controls report layout). This endpoint has NO 'tenantFilter' field — templates are tenant-agnostic.

ParamTypeRequiredDefaultDescription
fieldsJsonstringyesRequired JSON object merged into the request body. Spec-listed body keys (exact casing, all lowercase): 'name' (string), 'style' (string enum: 'Tenant' | 'Table'). NO 'tenantFilter' field. Caller is responsible for exact spec casing.

[CIPP] Create or REPLACE a reusable CIPP standards template via POST /api/AddStandardsTemplate. WARNING — 'tenantFilter' is NOT a string here: the standards engine (Get-CIPPStandards) ENUMERATES the template's tenantFilter and reads each element's '.value' (and '.type'), so the body key must be an ARRAY of objects — [{"value":"contoso.onmicrosoft.com"}]. A template saved with a bare string matches NO tenant and is silently never applied, while the POST still answers HTTP 200 'Successfully added template'. This tool seeds that array from the tenantFilter parameter; pass the literal 'AllTenants' to target every tenant, which produces the exact element the engine's AllTenants branch looks for. Upstream stores the body verbatim and stamps GUID / createdAt / updatedBy / updatedAt onto it: supplying an existing 'GUID' in fieldsJson OVERWRITES that template (Table.Force upsert on RowKey = GUID); omitting it creates a new one.

ParamTypeRequiredDefaultDescription
fieldsJsonstringyesRequired JSON object merged into the request body AFTER the seeded 'tenantFilter' array, so any key you supply wins — override 'tenantFilter' only with a full ARRAY of {"value":...} objects, never a bare string. Body keys upstream reads by name: 'GUID' (string — supply to overwrite an existing template, omit to create a new one), 'createdAt' (string, defaulted to now when absent), 'templateName' (string, used in the audit log line). Every other key is stored verbatim as the template's standard settings.
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com), or the literal 'AllTenants'. Seeded into the body as the array [{"value":"<this value>","label":"<this value>"}] — the {value,label} element shape the standards engine reads. Only '.value' is load-bearing; 'label' is display-only in the CIPP UI. Use cipp_list_tenants to discover available tenants.

[CIPP] Create or REPLACE a tenant's standards deployment via POST /api/AddStandardsDeploy. Upstream (Invoke-AddStandardsDeploy) reads the target tenant from exactly one place — the lowercase body key 'tenant' — which this tool seeds from the tenantFilter parameter, so you do NOT need to pass it in fieldsJson. WARNING — this is an UPSERT that REPLACES rather than merges: the row is written with RowKey = the tenant domain under Table.Force, so the tenant's entire existing standards deployment is overwritten. The WHOLE body (minus keys named 'None' or starting 'Select_') becomes that tenant's stored Standards settings blob, and 'v2.1' is added. The false-value strip is TOP-LEVEL ONLY and narrower than it sounds: upstream removes a key solely when it sits at the ROOT of the body, is literally named Alert, Remediate or Report (matched case-insensitively), and its value is false. NESTED per-standard switches are stored VERBATIM — {"phishProtection":{"remediate":false}} keeps that false — so a per-standard switch you set false PERSISTS in the stored deployment rather than being dropped. The endpoint always answers HTTP 200 — a failure is reported as text inside Results, not as a status code.

ParamTypeRequiredDefaultDescription
fieldsJsonstringyesRequired JSON object merged into the request body AFTER the seeded 'tenant' key, so any key you supply wins. Everything here is stored verbatim as the tenant's Standards settings — one key per standard, each an object of per-standard switches, e.g. {"phishProtection":{"remediate":true,"report":true}} (that exact key is special-cased upstream: a truthy 'remediate' makes CIPP rewrite it to {remediate, URL}, where the URL is derived by calling .split() on the request's x-ms-original-url HEADER — so when that header is absent the call throws and the WHOLE deployment save is abandoned, reported as HTTP 200 with Results 'Failed to add standard: You cannot call a method on a null-valued expression'. If you see that text, drop phishProtection from the body and re-send). Pass to store a deployment with no standards. Key casing does not matter — upstream property lookup is case-insensitive.
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Seeded into the body as the lowercase key 'tenant' — the only tenant key upstream reads. It is also sent on the query string, which this endpoint ignores. Use cipp_list_tenants to discover available tenants.

[CIPP] Clone a drift template into a new standards baseline via POST /api/ExecDriftClone. Required body key (exact spec casing): 'id' (lowercase string — the source drift template/snapshot ID). This is the ONLY field in the spec body — no 'tenantFilter', no 'templateName'.

ParamTypeRequiredDefaultDescription
fieldsJsonstringyesRequired JSON object merged into the request body. Spec-listed body keys (exact casing): 'id' (LOWERCASE string — the source drift template ID). This is the ONLY field. Caller is responsible for exact spec casing.

[CIPP] List Best Practice Analyzer results for a tenant. Returns pass/fail status for security, identity, and configuration checks.

ParamTypeRequiredDefaultDescription
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Use cipp_list_tenants to discover available tenants.

[CIPP] List saved Best Practice Analyzer templates. Returns template names and configured checks.

[CIPP] Run detailed domain analysis for a tenant. Returns comprehensive DNS configuration, email authentication, and security posture.

ParamTypeRequiredDefaultDescription
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Use cipp_list_tenants to discover available tenants.

[CIPP] Run ONE real-time DNS / email-security check against ONE domain via GET /api/ListDomainHealth. This is a per-CHECK, per-DOMAIN probe, not a tenant-wide report: you name the check in 'action' and the domain in 'domain', and the response is that single check's raw findings. Both are REQUIRED — upstream (Invoke-ListDomainHealth.ps1) answers HTTP 400 before running anything when either query key is missing, which is why this tool now takes them as typed parameters (previously it exposed only tenantFilter and every call 400'd). For the per-domain SCORECARD — CIPP's stored, pre-computed analyser findings for every domain in the tenant in one response — use cipp_list_domain_analyser instead; refresh those stored findings with cipp_run_domain_analyser. To cover SPF + DKIM + DMARC + MX for one domain, call this tool once per check; there is no combined mode, because four upstream responses cannot be returned as one without wrapping them. NOTE ON TENANT SCOPE: upstream reads NO tenant key here. tenantFilter is sent as request telemetry only; the per-tenant Domains row is scoped by the connected CIPP API client's own allowed-tenant list, and the DNS lookups themselves are public data. A malformed domain is rejected with HTTP 400 'Domain: <x> is invalid'. NOT EXPOSED, deliberately: upstream also reads ExpectedInclude/Record (ReadSpfRecord), Selector (ReadDkimRecord), Subdomains (TestHttpsCertificate) and ExpectedTarget (ReadAutoDiscover) off the query string, and this tool sends none of them — so TestHttpsCertificate reports upstream's server-side default subdomain 'www', and ReadDkimRecord uses the selectors already stored for the domain (falling back to the Microsoft selectors) rather than any you name. 'Selector' is the one that must stay off: supplying it makes upstream WRITE the selector list back onto the Domains table row for admin/editor callers, and this tool is ReadOnly. The 12 actions are pinned to CIPP-API @df3738d; this endpoint has no fieldsJson escape hatch, so an action CIPP adds later needs a StackJack change before it can be reached.

ParamTypeRequiredDefaultDescription
actionstringyesREQUIRED. Which single check to run, sent as the query key 'Action'. Exactly one of: GetDkimSelectors (the DKIM selectors CIPP has stored for the domain, as a comma-joined string), ListDomainInfo (the domain's stored CIPP Domains-table row, including its last analyser result), ReadAutoDiscover, ReadDkimRecord, ReadDmarcPolicy, ReadMXRecord, ReadNSRecord, ReadSpfRecord, ReadWhoisRecord, TestDNSSEC, TestHttpsCertificate, TestMtaSts. Matched case-insensitively (upstream dispatches with a PowerShell switch, which is), and the canonical spelling above is what is sent. Omitting it is what produced ticket #106's 400.
domainstringyesREQUIRED. The single domain the check resolves against, e.g. contoso.com — a bare registrable domain (subdomains are fine), NOT a URL and NOT the tenant's onmicrosoft.com id unless that is genuinely the domain you want checked. Sent as the query key 'Domain', trimmed. Upstream validates its shape and answers HTTP 400 'Domain: <x> is invalid' for anything that does not match. INTERNATIONALISED DOMAINS MUST BE SUPPLIED IN PUNYCODE: upstream's regex allows an 'xn--' prefix but contains no Unicode range at all, so a Unicode IDN such as muenchen written with an umlaut is rejected as invalid — convert it to its xn-- form first. Use cipp_list_domains to enumerate a tenant's domains.
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Use cipp_list_tenants to discover available tenants. NOTE: Invoke-ListDomainHealth reads no tenant key — this value is sent on the query string as CIPP request telemetry and does not scope the result. What you can see is decided by the connected CIPP API client's own allowed-tenant list.

[CIPP] List saved CIPP standard templates. Returns template names and configured security/configuration baselines for reuse across tenants.

[CIPP] List deployed CIPP standards for a tenant. Returns standard names, applied settings, and compliance state.

ParamTypeRequiredDefaultDescription
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Use cipp_list_tenants to discover available tenants.

[CIPP] Compare a tenant's current configuration against the CIPP standards template. Returns differences and compliance gaps.

ParamTypeRequiredDefaultDescription
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Use cipp_list_tenants to discover available tenants.

[CIPP] Detect configuration drift for a tenant. Returns settings that have changed from the deployed standard baseline.

ParamTypeRequiredDefaultDescription
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Use cipp_list_tenants to discover available tenants.

[CIPP] Remove a saved Best Practice Analyzer template via POST /api/RemoveBPATemplate. Required body key (exact spec casing): 'TemplateName' (PascalCase string). The same 'TemplateName' value is also placed on the query string by the client. NOTE: this endpoint identifies templates by NAME, not ID. Use cipp_list_bpa_templates to find the template name.

ParamTypeRequiredDefaultDescription
fieldsJsonstringyesRequired JSON object merged into the request body. Spec-listed body keys (exact casing): 'TemplateName' (PascalCase string — distinct from 'templateId'). Caller is responsible for exact spec casing.

[CIPP] Remove a tenant's deployed standards row via GET /api/RemoveStandard. This cannot be undone — the tenant stops being covered by the standards it had deployed. The row is identified by the query parameter 'ID' ONLY (there is no body fallback), and for this table that id IS THE TENANT DOMAIN, because Invoke-AddStandardsDeploy writes RowKey = the tenant. An ID that matches no standards row is NOT a silent success: upstream does not guard the delete, so the empty match is passed to AzBobbyTables' MANDATORY -Entity parameter and the resulting bind error is caught and answered as HTTP 500 with {"Results":"Failed to remove standard"}. A 500 from this tool therefore usually means 'no such deployment' rather than a CIPP outage — verify the ID with cipp_list_standards. A real removal answers HTTP 200 'Successfully removed standards deployment'.

ParamTypeRequiredDefaultDescription
idstringyesThe standards-deployment row to remove, which for this table is the TENANT DOMAIN (e.g. contoso.onmicrosoft.com) — not a GUID. Required. Sent as the query key 'ID'. Use cipp_list_standards to see the deployed rows.

[CIPP] Remove a saved CIPP standards template via POST /api/RemoveStandardTemplate. Required body key (exact spec casing): 'ID' (PascalCase, all-caps — the template ID). The same 'ID' value is also placed on the query string by the client. Use cipp_list_standard_templates to find the template ID.

ParamTypeRequiredDefaultDescription
fieldsJsonstringyesRequired JSON object merged into the request body. Spec-listed body keys (exact casing): 'ID' (string, all-caps — the template ID). Caller is responsible for exact spec casing.

[CIPP] Queue a Best Practice Analyzer run for a tenant via POST /api/ExecBPA. May take several minutes — use cipp_list_bpa to read the cached results first. Upstream (Invoke-ExecBPA) reads the tenant as `$Request.Query.tenantFilter ? $Request.Query.tenantFilter.value : $Request.Body.tenantfilter.value`: BOTH branches dereference a nested '.value', so the tenant must be an OBJECT — {"tenantfilter":{"value":"contoso.onmicrosoft.com"}} — never a bare string. This tool sends exactly that body shape, and deliberately sends NO tenantFilter on the query string: a present query value takes the first branch, where '.value' on a plain string is null, which would queue the BPA UNSCOPED (one collect task per BPA template against a null tenant) behind an HTTP 200 'BPA queued for execution'. There is no response-side signal for that failure — a tenant-restricted CIPP API client is refused with 403, but an AllTenants-scoped client, which is StackJack's normal CIPP credential, passes straight through — so the body-only call is the only safe shape. Casing is NOT the issue here: PowerShell property lookup is case-insensitive, so 'tenantFilter' and 'tenantfilter' name the same body key.

ParamTypeRequiredDefaultDescription
fieldsJsonstringyesRequired JSON object merged into the request body AFTER the seeded 'tenantfilter' object, so any key you supply wins. Pass for the normal case — this endpoint reads no other body key. If you do override 'tenantfilter', it MUST be an object carrying a 'value' property; a bare string makes upstream read null and queue an unscoped run behind an HTTP 200.
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Seeded into the body as {"tenantfilter":{"value":"<this value>"}} — the nested-object shape upstream dereferences. It is deliberately NOT sent on the query string: upstream reads the query branch first and cannot dereference a plain string there, which would leave the run unscoped. Use cipp_list_tenants to discover available tenants.

[CIPP] Convert EVERY legacy standards row in the entire CIPP instance to the current StandardsTemplateV2 format via GET /api/ExecStandardConvert. WARNING — INSTANCE-WIDE AND ONE-WAY: upstream (Invoke-ExecStandardConvert) reads NOTHING from the request, so this cannot be scoped to a tenant or to a single standard. It reads every row of the 'standards' table and writes ONE StandardsTemplateV2 template per legacy ROW — i.e. per tenant deployment, with that row's individual standards folded into the template's 'standards' object, named 'Converted Legacy Template for <tenant>' and marked runManually — then DELETES every original 'standards' row. The delete pass is unconditional while the template write is not, so a legacy row whose standards all convert to nothing is deleted with NO replacement template. This is a one-time migration, not a per-tenant operation — running it again after a successful pass finds nothing left to convert.

[CIPP] Trigger a CIPP standards ENFORCEMENT run via GET /api/ExecStandardsRun. Scope rides the query string: tenantFilter (query key 'tenantFilter') is REQUIRED here, and optional templateId (query key 'templateId') narrows the run to one template instead of all of them. WARNING — BLAST RADIUS: upstream defaults an absent tenantFilter to 'allTenants' and an absent templateId to '*', then runs with -Force, so passing tenantFilter='allTenants' enforces EVERY standards template against EVERY tenant in the CIPP instance. Upstream never refuses and answers HTTP 200 with a message naming the scope it used, e.g. 'Successfully started Standards Run for tenant: contoso.onmicrosoft.com - Template: All' — read that message back to confirm the scope. Enforcement is queued and may take several minutes. Inspect with cipp_list_standards / cipp_list_standards_compare before enforcing.

ParamTypeRequiredDefaultDescription
templateIdstringnonullOptional standards template id to enforce. Sent as the query key 'templateId'. When omitted upstream uses '*', meaning every template configured for the scoped tenant(s).
tenantFilterstringyesThe tenant to enforce standards against, as its default domain name (e.g. contoso.onmicrosoft.com). Required. Sent as the query key 'tenantFilter'. Passing 'allTenants' enforces against EVERY tenant in the CIPP instance — only do so deliberately.

[CIPP] Update drift deviation statuses for a tenant, or clear that tenant's drift customizations, via POST /api/ExecUpdateDriftDeviation. Upstream (Invoke-ExecUpdateDriftDeviation) reads the tenant ONLY from the body key 'TenantFilter', which this tool seeds from the tenantFilter parameter — you do NOT need to pass it in fieldsJson. TWO BRANCHES: (1) removeDriftCustomization=true DELETES every drift customization row stored for the tenant and ignores 'deviations' entirely; (2) otherwise upstream walks 'deviations' — an ARRAY OF OBJECTS, not a string — each shaped {"standardName":"...","status":"...","receivedValue":"..."}. Two status values act beyond a status update: 'DeniedRemediate' schedules a one-off remediation task (PLUS a 12-hour RECURRING one when 'persistentDeny' is truthy), and 'deniedDelete' issues a Graph BETA DELETE against the policy parsed out of that deviation's 'receivedValue'. BOOLEAN TRAP — upstream tests these flags for PowerShell truthiness, where the STRING "false" evaluates TRUE; send real JSON booleans, never quoted ones.

ParamTypeRequiredDefaultDescription
fieldsJsonstringyesRequired JSON object merged into the request body LAST, so any key here overrides the keys this tool seeds. Body keys upstream reads: 'deviations' (an ARRAY of objects, each with 'standardName', 'status' and — for 'deniedDelete' — a 'receivedValue' holding the policy JSON), 'reason' (string; also base64-encoded into the Intune multi-admin-approval justification header on deletes), 'persistentDeny' (JSON boolean — when truthy, a 'DeniedRemediate' deviation ALSO gets a 12-hour recurring remediation task; upstream casts it with [bool], and [bool]"false" is TRUE in PowerShell, so a quoted "false" turns it ON). Keys you supply are passed through verbatim and never rewritten.
removeDriftCustomizationbooleannofalseWhen true, sends the real JSON boolean RemoveDriftCustomization:true, which makes upstream DELETE every drift customization row stored for this tenant and skip 'deviations' completely. Left false (the default), the key is OMITTED rather than sent. Never route this through fieldsJson as a quoted string: upstream does a bare truthiness test, so "false" would take the delete branch.
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Seeded into the body as 'TenantFilter' — the only place upstream reads the tenant; the query-string copy is ignored. Use cipp_list_tenants to discover available tenants.

Audit

ToolPlanAccessSummary
cipp_add_alertProDestructiveCreate (or REPLACE) an AUDIT-LOG alert rule via POST /api/AddAlert — a row in CIPP's WebhookRules table that fires when matching unified-audit-log events arrive.
cipp_exec_add_alertProWriteRaise a one-off CIPP notification via POST /api/ExecAddAlert — it fires CIPP's OWN configured notification channels and/or writes a CIPP log entry.
cipp_list_alertsFreeRead-onlyList the CIPP alerts queue via GET /api/ListAlertsQueue.
cipp_list_audit_log_searchesFreeRead-onlyGET /api/ListAuditLogSearches — THREE unrelated views behind one endpoint, selected by 'type', each returning a DIFFERENT row shape.
cipp_list_audit_log_testFreeRead-onlyTest audit log availability and configuration.
cipp_list_audit_logsFreeRead-onlyList CIPP's ALERT-MATCHED audit records via GET /api/ListAuditLogs — NOT the Microsoft 365 unified audit log.
cipp_list_logsFreeRead-onlyList CIPP's own operation logs (errors, status messages, per-API activity) via GET /api/ListLogs — the post-write verification surface: after a CIPP write answers 200, check here whether the operation…
cipp_list_pending_webhooksFreeRead-onlyList webhook subscriptions that are pending validation or delivery via GET /api/ListPendingWebhooks.
cipp_list_signin_logsFreeRead-onlyList Azure AD sign-in logs for a tenant.
cipp_list_webhook_alertsFreeRead-onlyList configured webhook alert subscriptions.
cipp_remove_queued_alertProDestructivePermanently delete one queued alert via POST /api/RemoveQueuedAlert.
cipp_search_audit_logsProWritePOST /api/ExecAuditLogSearch — TWO unrelated mechanisms behind one endpoint, selected by the 'Action' key.

[CIPP] Create (or REPLACE) an AUDIT-LOG alert rule via POST /api/AddAlert — a row in CIPP's WebhookRules table that fires when matching unified-audit-log events arrive. It is NOT a scheduled/recurring alert: upstream reads exactly eight body keys — 'conditions', 'tenantFilter', 'excludedTenants', 'actions', 'RowKey', 'logbook' (only its .value, stored as the rule's LogType), 'AlertComment', 'CustomSubject' — and IGNORES every other key. In particular 'command', 'count', 'postExecution', 'preset', 'recurrence' and 'startDateTime' are never read: a caller who passes 'recurrence'/'startDateTime' expecting a schedule gets an unscheduled audit-log rule back with a 200 and no indication the fields were dropped. UPSERT, not create-only: supplying 'RowKey' REPLACES that existing row wholesale (only its Disabled flag is carried over); omitting it mints a new GUID. Success is 200 {'Results':['Added Audit Log Alert for N tenants...']}; Microsoft can take up to four hours to start delivering the events. Injection guard: WHEN PRESENT, each condition's Operator.value must be one of eq/ne/like/notlike/match/notmatch/gt/lt/ge/le/in/notin/contains/notcontains and Property.label must match ^[a-zA-Z0-9_.]+$, else 400. Both checks are SKIPPED when the field is absent, so it is not a well-formedness check: a condition with no Operator.value or no Property.label (including the plain-string case above) is stored with a 200 and simply never fires.

ParamTypeRequiredDefaultDescription
fieldsJsonstringyesAlert configuration as a JSON object, merged LAST (it can override the wrapped tenantFilter). Keys CIPP actually reads — passed through verbatim, never rewritten by this tool: 'conditions' (ARRAY OF OBJECTS, not a string — each {"Property":{"label":"<field>"},"Operator":{"label":"<display>","value":"eq"},"Input":{"value":"<match>"}}; a plain string passes the injection guard untouched and is then stored verbatim as a rule that can never match, accepted with a 200); 'actions' (ARRAY OF OBJECTS, not strings — the dispatcher runs `(...CIPPAction | ConvertFrom-Json).value`, so each entry needs a 'value' from disableUser / becremediate / generatemail / generatePSA / generateWebhook, plus a 'label' which is all the queue listing displays; an array of plain strings yields .value = null and NO action ever runs); 'excludedTenants' (array of tenant objects, same shape as tenantFilter); 'RowKey' (string — REPLACES that existing rule; omit to create); 'logbook' ({label,value}; only .value is stored, as the rule's LogType); 'AlertComment' (string); 'CustomSubject' (string). Keys upstream NEVER reads, silently dropped: 'command', 'count', 'postExecution', 'preset', 'recurrence', 'startDateTime'. Example: {"logbook":{"label":"Audit Log","value":"Audit Log"},"conditions":[{"Property":{"label":"Operation"},"Operator":{"label":"is","value":"eq"},"Input":{"value":"UserLoggedIn"}}],"actions":[{"label":"Generate Email","value":"generatemail"}],"AlertComment":"..."}
tenantFilterstringyesTarget tenant's default domain (e.g. contoso.onmicrosoft.com) — the same identifier the audit-log pipeline matches on. The tool WRAPS it into the tenant-object collection CIPP actually stores: [{"value":"<domain>","label":"<domain>"}]. This wrapping is load-bearing: the rule engine tests `$ExpandedTenants.value -contains $TenantFilter`, so a bare string (what this tool sent before) is stored as a rule that can NEVER match and never fires. Pass 'AllTenants' to deliberately create an ALL-TENANTS sweep — the engine treats value='AllTenants' as matching every tenant. To target several tenants, a tenant group, or to make the rule visible to a tenant-restricted CIPP API client, override 'tenantFilter' via fieldsJson with the full array: each entry {value,label} for a tenant (add 'customerId' — the tenant GUID — because the queue listing's access check reads it), or {type:'Group',value:'<groupId>',label:'<name>'} for a group.

[CIPP] Raise a one-off CIPP notification via POST /api/ExecAddAlert — it fires CIPP's OWN configured notification channels and/or writes a CIPP log entry. It creates no rule and schedules nothing. Upstream reads exactly SIX body keys: 'tenantFilter', 'text', 'sendEmailNow', 'sendWebhookNow', 'sendPsaNow', 'writeLog'. WARNING — the flags must be REAL JSON BOOLEANS (this tool's typed bool parameters emit them correctly): every channel is gated on `-eq $true`, but the outer guard tests sendEmailNow for bare truthiness, so a truthy non-true value such as "false", "1" or "yes" enters the notification branch, sends NOTHING, and — with writeLog not exactly true — returns 200 with an EMPTY body having written no log at all, which is strictly worse than sending no flags (that path always logs). WARNING — omitting tenantFilter is silent misattribution: upstream falls back to `$env:TenantID`, the CIPP host/partner tenant, and passes it to every notification, so the alert is attributed and scoped to the partner rather than your customer, with a 200 and no warning. Keys upstream NEVER reads: 'Severity' (severity is hardcoded to 'Alert' — every entry logs at that level whatever you send), 'email', 'webhook', 'logsToInclude', 'onePerTenant'. Neither the recipients, the URLs, nor the TITLE can be set per request: Send-CIPPAlert always receives the HARD-CODED title 'CIPP Notification Test' plus the channel type, your 'text' as the content and the tenant (the webhook call additionally carries InvokingCommand='Invoke-ExecAddAlert'), and delivery goes wherever CIPP's own notification configuration points. Everything the reader needs must therefore be inside 'text' — a genuine customer alert raised here still arrives titled 'CIPP Notification Test'.

ParamTypeRequiredDefaultDescription
fieldsJsonstringnonullOptional extra body keys, merged LAST (so they override the typed parameters above). Upstream reads only tenantFilter/text/sendEmailNow/sendWebhookNow/sendPsaNow/writeLog — anything else is accepted and ignored. Prefer the typed parameters: JSON booleans passed here are honoured, but string booleans are the trap described in the tool description. Normally leave null.
sendEmailNowbooleannonullSend an email notification immediately, to whatever recipients CIPP's own notification settings define (NOT settable per request). Emitted as a real JSON boolean — required, because upstream tests `-eq $true`.
sendPsaNowbooleannonullRaise a PSA ticket immediately through CIPP's configured PSA integration. Emitted as a real JSON boolean.
sendWebhookNowbooleannonullPost a webhook notification immediately, to the URL CIPP's own notification settings define (NOT settable per request). Emitted as a real JSON boolean.
tenantFilterstringnonullTarget tenant domain (e.g. contoso.onmicrosoft.com), sent as the 'tenantFilter' body key. OPTIONAL because the endpoint is declared AnyTenant and works without one — but see the tool warning: when omitted, CIPP silently substitutes its own host/partner tenant ($env:TenantID) and attributes the alert there. Pass it whenever the alert is about a specific customer. Use cipp_list_tenants to discover available tenants.
textstringnonullAlert text — the notification body and the message written to the CIPP log. Sent as the 'text' body key. Every upstream path consumes it; omitting it produces an alert with empty content.
writeLogbooleannonullAlso write the alert to the CIPP log (visible via cipp_list_logs). Emitted as a real JSON boolean. When any of the send* flags is set, this is what decides whether the response carries 'Successfully generated alert.' or an empty body.

[CIPP] List the CIPP alerts queue via GET /api/ListAlertsQueue. Returns two kinds of row, each stamped with the exact EventType literal cipp_remove_queued_alert needs: audit-log webhook rules (EventType 'Audit log Alert' — Tenants, human-readable Conditions/Actions, excludedTenants, LogType, RowKey, PartitionKey, RepeatsEvery='When received', AlertComment, CustomSubject, Enabled, plus a RawAlert copy of the stored objects) and hidden Get-CippAlert* scheduled tasks (EventType 'Scheduled Task'). The second kind REUSES the same field names for different data — Conditions is the task Name, Actions is its PostExecution, LogType is always the literal 'Scripted' (not a log type), RepeatsEvery is its Recurrence, RawAlert is the raw table row rather than parsed objects, and it adds a ScriptName field — so read a row's EventType before interpreting any of its other fields. NOT TENANT-SCOPED AND NOT FILTERABLE — upstream reads NOTHING from the query string and NOTHING from the body; the only thing it takes from the request is the CIPP API client's own tenant allow-list (Test-CIPPAccess -TenantList), which drops rows whose tenants that client cannot see and drops nothing at all when it holds 'AllTenants'. Expect rows for OTHER tenants in the response and filter client-side on each row's Tenants array.

ParamTypeRequiredDefaultDescription
fieldsJsonstringnonullINERT on this endpoint. Upstream never touches $Request.Body, so anything passed here is transmitted and has no effect: the spec's 'Actions'/'AlertComment'/'command'/'logbook'/'postExecution'/'preset'/'recurrence'/'startDateTime' body keys are not read, and neither is any other key. Retained only so existing callers keep working — leave it null.
tenantFilterstringyesTenant domain (e.g. contoso.onmicrosoft.com). Sent as the tenantFilter query string for connector consistency, but CIPP IGNORES it — this endpoint reads no query parameter, so it does NOT scope the response to this tenant. Use cipp_list_tenants to discover available tenants.

[CIPP] GET /api/ListAuditLogSearches — THREE unrelated views behind one endpoint, selected by 'type', each returning a DIFFERENT row shape. (1) type='Searches' (the default): the audit-log searches CIPP recorded for THIS tenant in the last 7 days, each resolved live against Microsoft Graph. Rows are Graph query objects — displayName, status, filterStartDateTime, filterEndDateTime. This is the view the CIPP web UI itself uses, and it is how you confirm a search made with cipp_search_audit_logs exists and see whether Graph has finished it. The 7-day window is on the row's LAST-WRITE time (CIPP rewrites a row as it processes the search), so a search over an old date range still appears, and one created eight days ago and never touched since does not. (2) type='SearchResults' + searchId: the actual audit records of one finished search. This is the ONLY way to read a search's results — cipp_search_audit_logs creates and queues searches, it never returns rows. (3) type='Ledger': CIPP's own processing ledger — SearchId, Query, MatchedRules, TotalLogs, MatchedLogs, CippStatus — which answers 'did CIPP turn this search into alerts?' rather than 'what does Graph say?'. TWO WARNINGS ON THE LEDGER: it filters on each search's WINDOW START against now minus 'days' (default 1), NOT on when the search was created, so a search covering an older date range is invisible here however recently it was made; and it applies NO tenant predicate at all, so it can return other tenants' searches — filter client-side. Sending no type at all used to reach the ledger by accident, which made every ordinary search look missing (Featurebase #166); the default is now 'Searches'.

ParamTypeRequiredDefaultDescription
daysintegernonullLedger lookback in days, applied to each search's WINDOW START (not its creation time). Only meaningful with type='Ledger', where CIPP defaults it to 1; refused with any other type, whose branches never read it. Must be 1 or greater — upstream subtracts it from now, so 0 falls back to the default 1 and a negative pushes the threshold into the future and returns nothing with a 200; both are refused here. A search over an old date range needs a days value large enough to reach that window's start, not merely to reach the day the search was made.
searchIdstringnonullId of the search whose results to read — the Graph query id that cipp_search_audit_logs returned. REQUIRED with type='SearchResults' and refused with any other type, where upstream reads it on no branch and would silently return the whole list instead.
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Sent as the 'tenantFilter' query string per the CIPP spec (required — the endpoint answers 400 'TenantFilter is required' without it). Use cipp_list_tenants to discover available tenants. Note that it scopes types 'Searches' and 'SearchResults' but NOT 'Ledger', whose upstream branch reads it only to decide the request is well-formed.
typestringno"Searches"Which view to return. 'Searches' (the default) = this tenant's searches from the last 7 days with live Graph status. 'SearchResults' = the rows of one finished search; REQUIRES searchId. 'Ledger' = CIPP's processing ledger (SearchId, Query, MatchedRules, TotalLogs, MatchedLogs, CippStatus) for searches whose WINDOW starts within 'days', not tenant-scoped. Matched case-insensitively; any other value is refused before dispatch, because upstream would silently fall through to the ledger and hand back the wrong shape with a 200.

[CIPP] Test audit log availability and configuration. Returns whether unified audit logging is enabled and accessible. UPSTREAM-BROKEN: at CIPP-API master @df3738d this endpoint cannot succeed for any caller — it hands its own helper a SearchId that helper does not declare and omits the row count the helper requires, so the request fails inside CIPP and comes back as HTTP 500 no matter what is sent. Expect it to fail until CyberDrain fixes the endpoint.

[CIPP] List CIPP's ALERT-MATCHED audit records via GET /api/ListAuditLogs — NOT the Microsoft 365 unified audit log. This reads CIPP's own 'AuditLogs' table, and the only writer of that table is CIPP's audit-log alert engine: a record is stored ONLY when it matched the $Where clauses of an ENABLED CIPP audit-log alert rule for that tenant. It never calls Graph and never calls the Office 365 Management API. So a tenant with no matching alert rule returns zero rows however much really happened in M365, and that empty answer is correct rather than a failure — check cipp_list_alerts for a row whose EventType is 'Audit log Alert' and whose Tenants array covers this tenant (or 'AllTenants') before concluding anything is missing. For an ad-hoc search of the real unified audit log, create one with cipp_search_audit_logs and then read it back with cipp_list_audit_log_searches (type='SearchResults'). Each row is {LogId, Timestamp, Tenant, Title, Data}. THE TWO CLOCKS ARE DIFFERENT: the startDate/endDate/relativeTime filters compare the table row's WRITE time (when CIPP stored the record), while the 'Timestamp' field in the response is the M365 event's own CreationTime — so a record created long before CIPP stored it is found by its storage time and displayed by its event time. With none of startDate, endDate or relativeTime, CIPP defaults to the last 7 days.

ParamTypeRequiredDefaultDescription
endDatestringnonullEnd of the window, compared against the row's STORAGE time. Same formats as startDate; sent as 'EndDate'. ONLY HONOURED TOGETHER WITH startDate — upstream nests the end condition inside the start condition, so an endDate sent on its own is silently dropped AND cancels the default 7-day window, returning the tenant's entire table with a 200. Always pass startDate with it.
logIdstringnonullFetch one specific stored record by id — the 'LogId' of a row this tool returned. Must be a GUID; CIPP validates it and rejects anything else. Sent as 'LogId'. IT SHORT-CIRCUITS EVERYTHING: when present, upstream drops the tenant predicate and every date condition and matches the id alone (against both RowKey and OriginalEntityId), so tenantFilter, startDate, endDate and relativeTime all have no effect.
relativeTimestringnonullRelative window, e.g. '7d', '24h', '30m' — digits followed by exactly one of d (days), h (hours) or m (minutes). Sent as 'RelativeTime'. OVERRIDES startDate and endDate. The form is strict: a value that does not match that pattern also cancels the default 7-day window and leaves CIPP comparing against empty dates, so the call returns nothing useful. Use one of the three example shapes.
startDatestringnonullStart of the window, compared against the row's STORAGE time. ISO 8601 (e.g. '2026-09-01T00:00:00Z') or unix epoch SECONDS as an all-digit string. Sent as the 'StartDate' query parameter. Supplying any of startDate/endDate/relativeTime REPLACES CIPP's default last-7-days window.
tenantFilterstringyesTarget tenant domain — it MUST be the tenant's defaultDomainName, the .onmicrosoft.com value cipp_list_tenants reports, NOT a vanity domain. Upstream matches rows with Tenant eq '<this value>' and then post-filters every surviving row against the set of known defaultDomainName values, so a vanity domain such as contoso.com returns an empty list with a 200 and no error rather than a mismatch you can see. 'AllTenants' drops the tenant predicate and returns every tenant this CIPP client can see. IGNORED ENTIRELY when logId is supplied.

[CIPP] List CIPP's own operation logs (errors, status messages, per-API activity) via GET /api/ListLogs — the post-write verification surface: after a CIPP write answers 200, check here whether the operation actually logged an error. HOW FILTERING WORKS (all of these are CIPP's own rules): without filter='true' (the LITERAL string — CIPP compares strings, so '1'/'yes' silently disable filtering), CIPP returns TODAY's logs only and IGNORES every other parameter. Dates MUST be yyyyMMdd (e.g. 20260826): a dashed ISO date passes CIPP's validation and then matches ZERO rows with a clean 200. tenant is a case-insensitive SUBSTRING match on the tenant column (or an exact tenant-ID match); omit it or pass 'AllTenants' for all tenants. severity is comma-separated from: Info, Warn, Warning, Error, Critical, Alert (default: all). api is a REGEX match on the API column (e.g. 'ExecBulkLicense').

ParamTypeRequiredDefaultDescription
apistringnonullRegex matched against the CIPP API/function name that wrote the log entry (e.g. 'ExecBulkLicense'). Requires filter='true'.
endDatestringnonullEnd date in yyyyMMdd form ONLY. Requires filter='true'.
filterstringnonullPass the LITERAL string 'true' to enable filtering (dates/severity/api/tenant beyond today). Any other value = today's logs only, all other parameters ignored.
severitystringnonullComma-separated severities from: Info, Warn, Warning, Error, Critical, Alert. Requires filter='true'.
startDatestringnonullStart date in yyyyMMdd form ONLY (e.g. 20260826). Dashed dates silently match nothing. Requires filter='true'.
tenantstringnonullTenant to scope to — substring match on the tenant domain, or an exact tenant ID. Omit (or 'AllTenants') for all tenants.

[CIPP] List webhook subscriptions that are pending validation or delivery via GET /api/ListPendingWebhooks. Returns webhook URLs, statuses, retry counts, and queued events. No request parameters per spec.

[CIPP] List Azure AD sign-in logs for a tenant. Returns user sign-in events, IP addresses, locations, and authentication details. May return large datasets for active tenants. Consider using cipp_search_audit_logs with date filters for targeted results.

ParamTypeRequiredDefaultDescription
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Use cipp_list_tenants to discover available tenants.

[CIPP] List configured webhook alert subscriptions. Returns webhook URLs, event types, and enabled status.

[CIPP] Permanently delete one queued alert via POST /api/RemoveQueuedAlert. 'EventType' is NOT a free-form category — it is the TABLE SELECTOR, with exactly one magic literal: 'Audit log Alert' deletes from WebhookRules, and EVERY other value (including a missing one) silently targets ScheduledTasks. Since the row is then found by RowKey alone with no tenant or type check, a wrong EventType makes this delete search the WRONG TABLE, where the RowKey matches nothing. CIPP passes that empty result straight into the table client, so the answer is either a 200 that deleted nothing or a 500 from the delete call — CIPP's own source does not decide which. Either way it can never delete the row you meant. This tool therefore accepts only the two literals cipp_list_alerts actually stamps and refuses anything else before dispatch. NOT tenant-scoped. Deletion is immediate and irreversible — read the row with cipp_list_alerts first and copy its RowKey and EventType verbatim.

ParamTypeRequiredDefaultDescription
eventTypestringyesWhich table the alert lives in, copied from the EventType field of the cipp_list_alerts row. Exactly two values are accepted (case-insensitive): 'Audit log Alert' for an audit-log webhook rule (WebhookRules — what cipp_add_alert creates), or 'Scheduled Task' for a hidden Get-CippAlert* scheduled task (ScheduledTasks). Any other value is refused before dispatch, because upstream would silently fall through to ScheduledTasks and delete from the wrong table.
fieldsJsonstringnonullOptional extra body keys, merged LAST, so they can override 'ID' above. One key is re-validated on the MERGED body: 'EventType', which may NOT be overridden here — an override that disagrees with the typed 'eventType' parameter, or a second, differently-cased spelling of the key, is refused before dispatch, because EventType is the TABLE SELECTOR: an override merged after the validated seed sends this delete at the OTHER table, where the RowKey-only filter matches a different row or none at all. Upstream reads only 'EventType' and 'ID'; both are also accepted as query parameters, but this tool sends the body, which upstream falls back to. Keys are passed verbatim. Normally leave null.
idstringyesRowKey of the alert to delete, copied verbatim from cipp_list_alerts. Sent as the 'ID' body key (PascalCase, all-caps). Upstream filters on RowKey ALONE within the table eventType selects, so an id from the other table simply matches nothing.

[CIPP] POST /api/ExecAuditLogSearch — TWO unrelated mechanisms behind one endpoint, selected by the 'Action' key. (1) CREATE A SEARCH (the default, action omitted): the body must carry tenantFilter + startTime + endTime, then CIPP SPLATS THE WHOLE BODY into New-CippAuditLogSearch — so it answers 400 'Invalid parameters: <keys>' for ANY key that is not one of that command's parameters. The accepted set is whatever (Get-Command New-CippAuditLogSearch).Parameters.Keys returns, matched case-insensitively: the 12 real parameters — TenantFilter, StartTime, EndTime, DisplayName, RecordTypeFilters, KeywordFilters, OperationsFilters, UserPrincipalNameFilters, IPAddressFilters, ObjectIdFilters, AdministrativeUnitFilters, ProcessLogs — PLUS PowerShell's common parameters and WhatIf/Confirm, which slip through the guard. NEVER send WhatIf: that command gates creation on ShouldProcess, so WhatIf:true clears the whitelist, creates NOTHING and still answers 200. Any other unknown key fails the entire request — there is no forward-compatibility hatch here. Returns {resultText, state, details}: state='success' carrying the new Graph search, state='warning' when unified auditing is disabled for the tenant, or state='error' with resultText='Failed to initiate search' and NO details when Graph returns neither an id nor AuditingDisabledTenant. ALL THREE ARE HTTP 200 — always read 'state', never the status code. (2) QUEUE AN EXISTING SEARCH FOR PROCESSING: action='ProcessLogs' (the ONLY value the switch has a case for) + searchId + tenantFilter; CIPP reads that search from Graph beta the FIRST time it sees the id for that tenant, stores it in its AuditLogSearches table and bridges it into the audit-log coverage ledger; re-queuing an id it already holds skips Graph entirely, just resets that row's CippStatus to 'Pending' and bridges an EMPTY search status. Either way it answers "Search '<name>' queued for processing." — as a JSON STRING body, where the create branch returns an object. NEITHER branch ever returns audit-log rows — this endpoint creates and queues searches, it does not read results. Do not confuse action='ProcessLogs' (branch selector) with the body key ProcessLogs=true (a switch on the CREATE branch that also registers the new search for CIPP alert processing).

ParamTypeRequiredDefaultDescription
actionstringnonullLeave NULL to create a new search. The ONLY value CIPP has a case for is 'ProcessLogs' (matched case-insensitively), which switches to the queue-an-existing-search branch and requires searchId. Any other value — including the fictional 'Submit'/'GetSearchResults' this tool used to document — is REFUSED before dispatch: upstream has no such case, so the request would fall through to the create branch where the 'Action' key itself fails the parameter whitelist, guaranteeing a 400.
additionalFieldsJsonstringnonullJSON object merged into the request body LAST, overriding the typed parameters above. NOT a forward-compatibility escape hatch — on the create branch any key outside New-CippAuditLogSearch's parameter set fails the WHOLE request with 400 'Invalid parameters: <key>'. This is where the real (previously undocumented) filters go: 'DisplayName' (string; defaults to 'CIPP Audit Search - <timestamp>'), 'RecordTypeFilters' (array of Graph record types from CIPP's ValidateSet, e.g. 'azureActiveDirectory', 'azureActiveDirectoryStsLogon', 'exchangeAdmin', 'sharePointFileOperation' — an unlisted value throws), 'KeywordFilters' (a SINGLE string, not an array), 'OperationsFilters' (array, e.g. ['UserLoggedIn']), 'UserPrincipalNameFilters' (array), 'IPAddressFilters' (array), 'ObjectIdFilters' (array), 'AdministrativeUnitFilters' (array), 'ProcessLogs' (boolean — also store the new search for CIPP alert processing). Example: {"OperationsFilters":["UserLoggedIn"],"RecordTypeFilters":["azureActiveDirectoryStsLogon"]}
endTimestringnonullEnd of the search window — REQUIRED when creating a search (see startTime); ignored with action='ProcessLogs'. ISO 8601 or unix epoch SECONDS as an all-digit string. Sent as body key 'EndTime'.
psObjectstringnonullNOT A CIPP FIELD. There is no PSObject body key anywhere in this endpoint and it is not a New-CippAuditLogSearch parameter, so sending it could only ever produce 400 'Invalid parameters: PSObject'. Supplying it is REFUSED before dispatch. Operation/record/user/IP filtering uses the real keys via additionalFieldsJson — OperationsFilters, RecordTypeFilters, KeywordFilters, UserPrincipalNameFilters, IPAddressFilters, ObjectIdFilters, AdministrativeUnitFilters.
searchIdstringnonullId of an EXISTING Microsoft Graph auditLog query to queue for CIPP processing. Valid ONLY with action='ProcessLogs'; supplying it without that action is REFUSED before dispatch, because 'SearchId' is not a New-CippAuditLogSearch parameter and would 400 the whole create request. This queues the search — it does NOT fetch its results.
startTimestringnonullStart of the search window — REQUIRED when creating a search (CIPP answers 400 'StartTime and EndTime are required'); omit only with action='ProcessLogs', which ignores it. ISO 8601 (e.g. '2026-01-01T00:00:00Z') or unix epoch SECONDS as an all-digit string. Sent as body key 'StartTime'.
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Sent as the 'tenantFilter' query string AND the 'tenantFilter' body key. Required by BOTH branches — the create branch answers 400 'TenantFilter is required' without it (CIPP resolves body keys case-insensitively, so camelCase 'tenantFilter' satisfies its TenantFilter parameter). Use cipp_list_tenants to discover available tenants.

GDAP

ToolPlanAccessSummary
cipp_add_gdap_roleProDestructiveCreate GDAP role→group mappings in your PARTNER tenant via POST /api/ExecAddGDAPRole.
cipp_approve_gdap_inviteProDestructiveKick off CIPP's sweep of RECENTLY ACTIVATED GDAP relationships via GET /api/ExecGDAPInviteApproved.
cipp_auto_extend_gdapProWriteAuto-extend an expiring GDAP relationship via POST /api/ExecAutoExtendGDAP.
cipp_delete_gdap_inviteProDestructiveRevoke a pending GDAP invitation via DELETE /api/ExecGDAPInvite.
cipp_delete_gdap_relationshipProDestructiveDelete a GDAP relationship via POST /api/ExecDeleteGDAPRelationship.
cipp_delete_gdap_role_mappingProDestructiveDelete a GDAP role mapping via POST /api/ExecDeleteGDAPRoleMapping.
cipp_delete_gdap_role_templateProDestructivePermanently delete a stored GDAP role template via DELETE /api/ExecGDAPRoleTemplate?Action=Delete.
cipp_list_gdap_accessFreeRead-onlyList the access assignments of ONE GDAP relationship via GET /api/ListGDAPAccessAssignments: the security groups granted access through that relationship and the roles each group maps to.
cipp_list_gdap_invitesFreeRead-onlyList pending GDAP relationship invitations across your partner tenant.
cipp_list_gdap_relationshipsFreeRead-onlyList the GDAP delegated admin relationships of your partner tenant via GET /api/ListGDAPRelationships: each relationship's id, customer tenant, requested roles, status, duration and expiry.
cipp_list_gdap_rolesFreeRead-onlyList all GDAP (Granular Delegated Admin Privileges) roles available in your partner tenant.
cipp_list_partner_relationshipsFreeRead-onlyList partner relationships (DAP/GDAP) for a specific tenant.
cipp_patch_gdap_access_assignmentProDestructiveReconcile one GDAP relationship's access assignments against a stored GDAP role template, via PATCH /api/ExecGDAPAccessAssignment.
cipp_remove_gdap_ga_roleProDestructiveRemove the Global Administrator role from a GDAP relationship via POST /api/ExecGDAPRemoveGArole.

[CIPP] Create GDAP role→group mappings in your PARTNER tenant via POST /api/ExecAddGDAPRole. NOT tenant-scoped (no tenantFilter) — this always acts on the CSP/partner tenant itself. 'action' is REQUIRED here and selects one of three upstream branches. It is required deliberately: CIPP defaults a MISSING Action to 'AddRoleSimple', so an empty payload would silently run the branch described below. • 'ListGroups' — read-only; returns the partner tenant's security groups (excluding All Users / AdminAgents / HelpdeskAgents / SalesAgents). No other field is read. • 'AddRoleSimple' — WARNING, HIGH BLAST RADIUS: for every role with no matching 'M365 GDAP <RoleName>' group, CIPP BULK-CREATES a new Entra security group in the partner tenant (Graph POST /groups). If body key 'gdapRoles' is ABSENT, CIPP substitutes its own list of 15 default roles (Application Administrator, User Administrator, Intune Administrator, Exchange Administrator, Security Administrator, Cloud App Security Administrator, Cloud Device Administrator, Teams Administrator, SharePoint Administrator, Authentication Policy Administrator, Privileged Role Administrator, Privileged Authentication Administrator, Billing Administrator, Global Reader, Domain Name Administrator) — i.e. up to 15 groups created in one call. Always send 'gdapRoles' unless you intend exactly that. • 'AddRoleAdvanced' — maps EXISTING partner groups to roles from body key 'mappings'. A mapping whose GroupId is not an existing partner group is SILENTLY SKIPPED (no result row for it). Both write branches upsert rows into CIPP's GDAPRoles table with -Force, REPLACING any existing mapping for the same group id — which is why this tool is marked Destructive. Body keys CIPP actually reads: 'Action', 'gdapRoles', 'customSuffix', 'mappings', 'templateId'. Keys the CIPP OpenAPI spec lists but this endpoint NEVER reads (sending them does nothing): 'Reference', 'gdapTemplate', 'inviteCount', 'replace'. EVERY branch — ListGroups included — returns the SAME raw CIPP envelope {"Results":[...]}; there is no bare array anywhere. ListGroups fills it with group OBJECTS ({id, displayName}). AddRoleSimple fills it with plain STRINGS, e.g. 'Created M365 GDAP Global Reader', '<group name> already exists', 'Could not create GDAP group: <graph error>', plus 'Added role mappings to template <id>' when templateId was supplied. AddRoleAdvanced fills it with OBJECTS — {"state":"success|error","resultText":"..."}, including {"state":"success","resultText":"All role mappings already exist"} when nothing needed writing — so a 200 there can carry error rows and be a PARTIAL result.

ParamTypeRequiredDefaultDescription
actionstringyesREQUIRED. Which upstream branch to run — exactly one of 'ListGroups', 'AddRoleAdvanced', 'AddRoleSimple' (case-insensitive; any other value is refused before the call is made). Sent as body key 'Action' (PascalCase). Required because CIPP's own default for a missing Action is 'AddRoleSimple', which creates up to 15 Entra security groups.
fieldsJsonstringnonullOptional additional body keys as a JSON object, merged AFTER 'action' (so a caller-supplied key wins). One key is re-validated on the MERGED body: 'Action', which may NOT be overridden here — an override that disagrees with the typed 'action' parameter, or a second, differently-cased spelling of it, is refused before dispatch, because the typed parameter is the operation this call was reviewed as and 'AddRoleSimple' bulk-creates security groups in your partner tenant. Keys are passed verbatim and never rewritten. 'gdapRoles' (camelCase, AddRoleSimple only) — an array of OBJECTS, NOT an array of GUID strings: each item is {"label":"<role display name>","value":"<roleDefinitionId GUID>"} (CIPP also accepts {"Name":...,"ObjectId":...}). 'label' becomes the group name 'M365 GDAP <label>' and 'value' becomes the mapping's roleDefinitionId. Passing plain GUID strings makes CIPP's $RoleName null and it then calls .replace() on null — an unhandled terminating error returned as HTTP 500. 'customSuffix' (camelCase, AddRoleSimple only, string) — appended to created group names: 'M365 GDAP <label> - <customSuffix>'. 'mappings' (camelCase, AddRoleAdvanced only) — an array of objects, each requiring 'GroupId' (PascalCase, an EXISTING partner security group id), 'RoleName' (PascalCase) and 'roleDefinitionId' (camelCase). 'templateId' (camelCase, AddRoleSimple only, string) — when present the resulting mappings are also written into that GDAP role template. Example: {"gdapRoles":[{"label":"Global Reader","value":"f2ef992c-3afb-46b9-b7cf-a126ee74c451"}]}

[CIPP] Kick off CIPP's sweep of RECENTLY ACTIVATED GDAP relationships via GET /api/ExecGDAPInviteApproved. This is NOT a targeted approval and it takes no parameters: the endpoint reads NOTHING from the request. It cannot approve a specific invite — customers approve the invite themselves in the Microsoft admin portal. What it does: CIPP lists every stored GDAP invite, asks Graph for delegatedAdminRelationships with status 'active', and for each match starts a durable orchestration that creates the access assignments from that invite's stored role mappings and then deletes the invite row. The work is ASYNCHRONOUS — the response is always the fixed string {"Results":["Processing recently activated GDAP relationships"]}, returned identically whether the sweep queued many relationships or none at all. A 200 here therefore proves only that the sweep was triggered; it carries no per-invite outcome. To see the result, poll cipp_list_gdap_invites (processed invites disappear) or cipp_list_gdap_access.

[CIPP] Auto-extend an expiring GDAP relationship via POST /api/ExecAutoExtendGDAP. The CIPP spec defines exactly one body field — 'ID' (PascalCase, all-caps, the GDAP relationship ID) — and accepts the same value as the 'ID' query parameter. NOT tenant-scoped (no tenantFilter). Pass keys verbatim — caller is responsible for exact spec casing.

ParamTypeRequiredDefaultDescription
fieldsJsonstringyesExtension configuration as JSON object. Required body keys per spec: 'ID' (PascalCase, all-caps) — the GDAP relationship ID. Example: {"ID":"<gdap-relationship-guid>"}. Same field is accepted as a query parameter; CIPP forwards either form. Keys are passed verbatim — caller is responsible for exact spec casing.

[CIPP] Revoke a pending GDAP invitation via DELETE /api/ExecGDAPInvite. NOT tenant-scoped (no tenantFilter). WARNING about CIPP's default: this endpoint dispatches on body key 'Action', and a MISSING Action defaults to 'Create' — which would CREATE a new delegated admin relationship instead of deleting one. This tool therefore ALWAYS sends an Action, defaulting to 'Delete'. • 'Delete' (default) — removes the invite row from CIPP's GDAPInvites table. It does NOT terminate an already-approved GDAP relationship (use cipp_delete_gdap_relationship for that). An inviteId that matches no row returns HTTP 200 with {"Message":"Invite not found"} — check the Message, a 200 alone does not prove a deletion. • 'Update' — rewrites only the Technician and Reference fields on an existing invite row. • 'Create' — a full CREATE, reachable only by asking for it explicitly: CIPP POSTs a NEW delegatedAdminRelationship to Graph (duration P730D; autoExtendDuration P180D, or PT0S when the roleMappings include the Global Administrator role 62e90394-69f5-4237-9190-012177145e10), locks it for approval, and writes a GDAPInvites row. Returns {"Message":..., "Invite":} including the customer-facing InviteUrl. 'inviteId' is ignored on this branch. Body keys CIPP actually reads: 'Action', 'InviteId', 'Reference', 'roleMappings'. Keys the CIPP OpenAPI spec lists but this endpoint NEVER reads (sending them does nothing): 'gdapTemplate', 'inviteCount', and a top-level 'roleDefinitionId'. Use cipp_list_gdap_invites to find invite IDs.

ParamTypeRequiredDefaultDescription
actionstringnonullWhich branch to run — one of 'Delete' (DEFAULT when omitted), 'Update', or 'Create' (case-insensitive; any other value is refused before the call is made). Sent as body key 'Action' (PascalCase). Omitting it here is safe: this tool substitutes 'Delete', so CIPP's own 'Create' default can never be reached by accident.
fieldsJsonstringnonullOptional additional body keys as a JSON object, merged AFTER the typed parameters (so a caller-supplied key wins). One key is re-validated on the MERGED body: 'Action', which may NOT be overridden here — an override that disagrees with the typed 'action' parameter, or a second, differently-cased spelling of it, is refused before dispatch, because 'Create' would POST a brand-new delegated admin relationship from a tool named delete; ask for that branch through the typed 'action' parameter instead. Keys are passed verbatim and never rewritten. The only useful key is 'roleMappings' (camelCase, 'Create' branch only): an array of objects each carrying 'roleDefinitionId' (camelCase) — CIPP reads ONLY that property off each item and sends it as the relationship's unifiedRoles, so a {label,value} pair is NOT the shape this endpoint wants. Example: {"roleMappings":[{"roleDefinitionId":"f2ef992c-3afb-46b9-b7cf-a126ee74c451"}]}
inviteIdstringnonullInvite ID to act on — the GDAP relationship id that is the invite row's RowKey. Sent as body key 'InviteId' (PascalCase). Required in practice for 'Delete' and 'Update' (omitting it returns 200 with Message 'Invite not found'); ignored for 'Create'. Use cipp_list_gdap_invites to find valid invite IDs.
referencestringnonullOptional reference/audit string stored on the invite row. Sent as body key 'Reference' (PascalCase). Read only by the 'Create' and 'Update' branches; ignored on 'Delete'.

[CIPP] Delete a GDAP relationship via POST /api/ExecDeleteGDAPRelationship. The CIPP spec defines exactly one body field — 'GDAPId' (PascalCase, mixed-case 'GDAP' + 'Id' — case-sensitive) — accepted as the matching 'GDAPId' query parameter. NOT tenant-scoped (no tenantFilter). WARNING: This permanently terminates the GDAP relationship with the tenant. You will lose delegated access. Use cipp_list_gdap_relationships to find the GDAPId. Pass keys verbatim — caller is responsible for exact spec casing.

ParamTypeRequiredDefaultDescription
fieldsJsonstringyesRelationship identification as JSON object. Required body key per spec: 'GDAPId' (PascalCase, mixed-case 'GDAP' + 'Id', case-sensitive). Example: {"GDAPId":"<gdap-relationship-guid>"}. Same field is accepted as a query parameter. Keys are passed verbatim — caller is responsible for exact spec casing.

[CIPP] Delete a GDAP role mapping via POST /api/ExecDeleteGDAPRoleMapping. The CIPP spec defines exactly one body field — 'GroupId' (PascalCase) — accepted as the matching 'GroupId' query parameter. NOT tenant-scoped (no tenantFilter). Removes the association between an Azure AD group and the delegated role mapping. Pass keys verbatim — caller is responsible for exact spec casing.

ParamTypeRequiredDefaultDescription
fieldsJsonstringyesRole mapping identification as JSON object. Required body key per spec: 'GroupId' (PascalCase) — the Azure AD group ID whose mapping is being removed. Example: {"GroupId":"<group-guid>"}. Same field is accepted as a query parameter. Keys are passed verbatim — caller is responsible for exact spec casing.

[CIPP] Permanently delete a stored GDAP role template via DELETE /api/ExecGDAPRoleTemplate?Action=Delete. NOT tenant-scoped (no tenantFilter). This cannot be undone. CIPP selects the Add / Edit / Delete branches from the QUERY STRING ($Request.Query.Action), never from the body, so this tool always sends Action=Delete; the template id rides the BODY as 'TemplateId'. The two are NOT interchangeable — a 'TemplateId' in the QUERY is checked first by CIPP and short-circuits into a single-template READ before the Action switch is ever evaluated, which is why nothing here ever puts the id on the query string. StackJack exposes no tool that lists GDAP role templates — obtain the template id from the CIPP web UI before calling this. A TemplateId that matches no stored template returns HTTP 200 with {"Results":"Template <id> not found"} rather than an error, so read Results: a 200 alone does not prove a deletion (a real one reads 'Deleted template <id>').

ParamTypeRequiredDefaultDescription
fieldsJsonstringnonullOptional additional body keys as a JSON object, merged AFTER 'templateId' (so a caller-supplied key wins). Keys are passed through verbatim. Body keys CIPP reads on the Delete branch: 'TemplateId'. ('OriginalTemplateId', 'RoleMappings' and 'GroupId' belong to the Add/Edit branches, which this tool does not select.)
templateIdstringyesTemplate ID (the template's RowKey) to delete. Required. Sent as the body key 'TemplateId' (PascalCase) — the exact key CIPP's Delete branch reads. Never sent on the query string, where it would turn the call into a read.

[CIPP] List the access assignments of ONE GDAP relationship via GET /api/ListGDAPAccessAssignments: the security groups granted access through that relationship and the roles each group maps to. This is a single-relationship read — CIPP interpolates the relationship id into the Graph path and has no list-everything form, so the id is required and a call without it cannot succeed. To find relationship ids (with customer tenant, roles and expiry) use cipp_list_gdap_relationships. Returns raw CIPP JSON.

ParamTypeRequiredDefaultDescription
idstringyesREQUIRED. The GDAP relationship id whose access assignments are listed. Sent as the 'Id' query argument (PascalCase, what CIPP reads). Use cipp_list_gdap_relationships to find it.

[CIPP] List pending GDAP relationship invitations across your partner tenant. Returns invite status, customer details, and requested roles.

[CIPP] List the GDAP delegated admin relationships of your partner tenant via GET /api/ListGDAPRelationships: each relationship's id, customer tenant, requested roles, status, duration and expiry. This is the list-all read; pass id to fetch one relationship. Use the relationship id with cipp_list_gdap_access (access assignments), cipp_patch_gdap_access_assignment and cipp_delete_gdap_relationship. Returns raw CIPP JSON.

ParamTypeRequiredDefaultDescription
idstringnonullOptional. A GDAP relationship id to fetch just that relationship. Omit to list them all.

[CIPP] List all GDAP (Granular Delegated Admin Privileges) roles available in your partner tenant. Returns role names, IDs, and descriptions.

[CIPP] List partner relationships (DAP/GDAP) for a specific tenant. Returns relationship type, status, and delegated permissions.

ParamTypeRequiredDefaultDescription
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Use cipp_list_tenants to discover available tenants.

[CIPP] Reconcile one GDAP relationship's access assignments against a stored GDAP role template, via PATCH /api/ExecGDAPAccessAssignment. NOT tenant-scoped (no tenantFilter). 'ResetMappings' is the ONLY operation this endpoint implements — every other Action value (and omitting it) returns HTTP 200 carrying {"Results":[{"state":"error","resultText":"Invalid action"}]} — so this tool always sends Action='ResetMappings'; you do not choose it. What it actually does: it loads the template's role mappings, then for the relationship it DELETES every existing access assignment whose group is not in the template, PATCHes assignments whose role does not match, and CREATES the missing ones. WARNING — a roleTemplateId that matches no stored template is NOT rejected: the mapping set comes back EMPTY and the reconciliation loop then queues a DELETE for EVERY existing access assignment on the relationship, stripping the delegated access. Confirm the id with a template listing before calling. (An EMPTY/whitespace roleTemplateId is refused here before the call; a wrong-but-non-empty one cannot be detected client-side.) Stale group ids inside the template are self-healing: CIPP re-resolves them by group name and writes the correction back to the template. Groups it cannot resolve at all are reported as errors and their mappings are dropped for this run. Returns raw CIPP JSON: {"Results":[{"state":"success|error","resultText":"..."}]} — one row per change attempted, so a 200 with error rows is a partial result, not a failure.

ParamTypeRequiredDefaultDescription
fieldsJsonstringnonullOptional additional body keys as a JSON object, merged AFTER the typed parameters (so a caller-supplied key wins). CIPP reads only 'Action', 'Id' and 'RoleTemplateId' on this endpoint; no other key has any effect. TWO keys are re-validated on the MERGED body and may NOT be overridden here: 'Action', which this tool fixes to 'ResetMappings' (every other value reaches upstream's 'Invalid action' arm and reconciles nothing while still answering HTTP 200), and 'RoleTemplateId', which is the DESIRED STATE the reconciliation deletes and recreates assignments to match — an override there reconciles against a different template, and one matching no stored template resolves to an EMPTY mapping set that deletes every existing assignment. For either key an override that disagrees with the reviewed value, or a second differently-cased spelling of it, is refused before dispatch; supply the template through the typed 'roleTemplateId' parameter. 'Id' still overrides. Keys are passed verbatim.
idstringyesREQUIRED. The GDAP relationship ID whose access assignments are reconciled. Sent as body key 'Id' (PascalCase). Use cipp_list_gdap_relationships to find it.
roleTemplateIdstringyesREQUIRED. The GDAP role template ID (its RowKey in CIPP's GDAPRoleTemplates table) whose role→group mappings become the desired state. Sent as body key 'RoleTemplateId' (PascalCase). WARNING: an id that matches no template resolves to an EMPTY mapping set, which makes CIPP delete every existing access assignment on the relationship.

[CIPP] Remove the Global Administrator role from a GDAP relationship via POST /api/ExecGDAPRemoveGArole. The CIPP spec defines exactly one body field — 'GDAPId' (PascalCase, mixed-case 'GDAP' + 'Id') — accepted as the matching 'GDAPId' query parameter. NOT tenant-scoped (no tenantFilter). Reduces delegated privileges by removing the GA role assignment from this relationship. Pass keys verbatim — caller is responsible for exact spec casing.

ParamTypeRequiredDefaultDescription
fieldsJsonstringyesGDAP GA role removal parameters as JSON object. Required body key per spec: 'GDAPId' (PascalCase, mixed-case 'GDAP' + 'Id', case-sensitive) — the GDAP relationship ID. Example: {"GDAPId":"<gdap-relationship-guid>"}. Same field is accepted as a query parameter. Keys are passed verbatim — caller is responsible for exact spec casing.

Scheduler

ToolPlanAccessSummary
cipp_add_scheduled_itemProDestructiveCreate, edit, or immediately re-run a CIPP scheduled task via POST /api/AddScheduledItem.
cipp_list_scheduled_item_detailsFreeRead-onlyGet details for a single scheduled item via POST /api/ListScheduledItemDetails.
cipp_list_scheduled_itemsFreeRead-onlyList all scheduled tasks and jobs in CIPP.
cipp_remove_scheduled_itemProDestructiveRemove a scheduled task from CIPP.
cipp_run_scheduler_billingProDestructiveTrigger an immediate scheduler billing run via GET /api/ExecSchedulerBillingRun.

[CIPP] Create, edit, or immediately re-run a CIPP scheduled task via POST /api/AddScheduledItem. BODY KEYS CIPP ACTUALLY READS: 'Name'; 'tenantFilter' (object — the '.value' is stored as the task's tenant); 'command' (object or bare string — the '.value' must name an existing CIPP cmdlet); 'parameters'; 'Recurrence'; 'Trigger'; 'RowKey'; 'reference'; 'ScheduledTime' (epoch seconds); 'DesiredStartTime' (epoch-seconds STRING); 'RunNow'; 'DisallowDuplicateName'; 'postExecution'. CIPP additionally reads 'excludedTenants', 'AlertComment', 'CustomSubject', 'PsaTicketStrategy', 'Tag' and 'AdditionalProperties', which this tool exposes only through additionalFieldsJson. WARNING — ~19 BODY KEYS ARE SILENTLY DROPPED: CIPP builds the stored task from an explicit key list, so 'taskType', 'RawJsonParameters', 'overwrite', 'advancedParameters', 'CippCustomVariables', 'CippScriptedAlerts', 'CippWebhookAlerts', 'backup', 'ca', 'email', 'groups', 'intunecompliance', 'intuneconfig', 'intuneprotection', 'antiphishing', 'antispam', 'users', 'psa' and 'webhook' reach storage NOWHERE. Their parameters below are retained for forward-compatibility, are marked IGNORED, and setting them changes nothing. WARNING — EDITING REPLACES THE TASK: passing rowKey rewrites the whole stored entity (only its Disabled state is carried over), so any field you omit is CLEARED, not preserved. Read the task with cipp_list_scheduled_item_details first and resend every field you want to keep. WARNING — ERRORS ARRIVE AS HTTP 200: CIPP returns refusals as a plain string in Results with a 200 status — "Error - The command 'X' does not exist and cannot be scheduled.", "Error - The command 'X' is not permitted to run as a scheduled task." (unauthorized module or blocked command), and "Task with name X already exists" (disallowDuplicateName). Always read the Results string; a 200 is NOT proof the task was created. Success reads "Successfully added task: <name>. It will run in <relative time>." or "Task <name> scheduled to run now". commandJson is effectively REQUIRED — omitting it returns the "does not exist" error. The 'hidden' flag is QUERY-ONLY (CIPP reads only $Request.Query.hidden and never a body key), and this tool's client method has no query-string overload, so hidden cannot be set here — additionalFieldsJson will NOT work for it. Use additionalFieldsJson for the read-but-unexposed keys above and for forward-compatibility with new CIPP fields. Verified against CIPP-API master @df3738d.

ParamTypeRequiredDefaultDescription
additionalFieldsJsonstringnonullForward-compatibility escape hatch: a JSON object whose properties are merged into the request body LAST, overriding any of the typed parameters above. Keys are passed VERBATIM and are never coerced, with ONE exception and TWO re-checks that run on the MERGED body — the typed-parameter validation above cannot see a value that arrived through this hatch. EXCEPTION: 'DesiredStartTime' is re-normalized to CIPP's epoch-seconds STRING wire format however it arrived (an ISO-8601 value is converted for you; anything else is refused), because CIPP casts it with [int64] inside a catch that only warns and would otherwise schedule at the next 15-minute interval while still reporting success. RE-CHECKS: (1) a merged 'RunNow' that satisfies upstream's `-eq $true` — a real JSON true, the STRING "true", or 1 — together with a non-empty 'RowKey' is refused when any other body key is present, because on that path CIPP discards the ENTIRE request body and re-runs the stored task unchanged; do it in two calls instead. (2) a second, differently-cased spelling of 'RunNow', 'RowKey' or 'DesiredStartTime' is refused, because it ships alongside the seeded key rather than replacing it and CIPP resolves body members without regard to case. (3) when you ALSO passed the typed 'runNow' or 'rowKey' parameter, a 'RunNow' or 'RowKey' here that disagrees with it is refused before dispatch — RunNow selects which branch CIPP takes and RowKey selects which stored task is edited or re-run, and editing replaces that task's whole stored entity; the comparison is exact and type-aware, so the string "true" does not agree with a typed true. If you OMIT the typed parameter, a 'RunNow' or 'RowKey' supplied here keeps its documented last-wins behavior and only checks (1) and (2) apply to it. Use it for the keys CIPP reads but this tool does not expose — 'excludedTenants' (array of {value,label,type}), 'AlertComment', 'CustomSubject', 'PsaTicketStrategy', 'Tag', 'AdditionalProperties' (an ARRAY of {Key,Value} objects — CIPP iterates it as a COLLECTION and reads .Key/.Value off each element, so a plain object such as {"Foo":"Bar"} yields no .Value, is silently dropped, and the column stores '' while the task is still created and reported successful) — and for new CIPP fields. It CANNOT set 'hidden', which CIPP reads only from the query string.
advancedParametersbooleannonullIGNORED at CIPP-API @df3738d — 'advancedParameters' is not read. NO effect. Retained for forward-compatibility only.
antiphishingbooleannonullIGNORED at CIPP-API @df3738d — 'antiphishing' is not read. NO effect. Retained for forward-compatibility only.
antispambooleannonullIGNORED at CIPP-API @df3738d — 'antispam' is not read. NO effect. Retained for forward-compatibility only.
backupValuestringnonullIGNORED at CIPP-API @df3738d — 'backup' is not read. NO effect. Retained for forward-compatibility only.
cabooleannonullIGNORED at CIPP-API @df3738d — 'ca' is not read. NO effect. Retained for forward-compatibility only.
cippCustomVariablesbooleannonullIGNORED at CIPP-API @df3738d — 'CippCustomVariables' is not read. NO effect. Retained for forward-compatibility only.
cippScriptedAlertsbooleannonullIGNORED at CIPP-API @df3738d — 'CippScriptedAlerts' is not read. NO effect. Retained for forward-compatibility only.
cippWebhookAlertsbooleannonullIGNORED at CIPP-API @df3738d — 'CippWebhookAlerts' is not read. NO effect. Retained for forward-compatibility only.
commandJsonstringnonullCommand to execute on schedule. Maps to body field 'command'. Pass a JSON object string (e.g., '{"label":"Standards","value":"ExecStandardsRun"}') — CIPP reads '.value', falling back to a bare string. EFFECTIVELY REQUIRED: the cmdlet must exist and belong to CIPPCore/CIPPAlerts/CIPPStandards/CIPPTests/CIPPDB/CippExtensions/CIPPActivityTriggers and not be on CIPP's blocked-command list, or the call returns HTTP 200 with an "Error - The command ... " string in Results and nothing is scheduled.
desiredStartTimestringnonullDesired start time. Maps to body field 'DesiredStartTime', which CIPP casts with [int64] — so the wire format is EPOCH SECONDS AS A STRING. Pass epoch seconds, or an ISO-8601 timestamp (e.g. '2026-09-01T14:00:00Z') which this tool converts to epoch seconds for you; an ISO value with no offset is read as UTC. Anything else is refused before dispatch, because CIPP swallows a failed cast with a server-side warning and silently falls back to the existing/next-interval ScheduledTime while still reporting success.
disallowDuplicateNamebooleannonullIf true, CIPP refuses the create when a non-Completed/non-Failed task with the same Name exists, returning HTTP 200 with "Task with name X already exists" in Results. Maps to body field 'DisallowDuplicateName'. Not consulted on the rowKey + runNow=true re-run path.
emailbooleannonullIGNORED at CIPP-API @df3738d — the top-level 'email' key is not read (post-execution email is selected via postExecutionChannels). NO effect. Retained for forward-compatibility only.
groupsbooleannonullIGNORED at CIPP-API @df3738d — 'groups' is not read. NO effect. Retained for forward-compatibility only.
intunecompliancebooleannonullIGNORED at CIPP-API @df3738d — 'intunecompliance' is not read. NO effect. Retained for forward-compatibility only.
intuneconfigbooleannonullIGNORED at CIPP-API @df3738d — 'intuneconfig' is not read. NO effect. Retained for forward-compatibility only.
intuneprotectionbooleannonullIGNORED at CIPP-API @df3738d — 'intuneprotection' is not read. NO effect. Retained for forward-compatibility only.
namestringyesDisplay name for the scheduled task. Maps to body field 'Name'. Ignored when rowKey + runNow=true re-runs a stored task.
overwritebooleannonullIGNORED at CIPP-API @df3738d — 'overwrite' is not read; supplying an existing rowKey is what overwrites a task. NO effect. Retained for forward-compatibility only.
parametersJsonstringnonullParameters object passed to the scheduled command. Maps to body field 'parameters'. Pass a JSON object string. CIPP enumerates its properties and DROPS any whose value is null or an empty string. This is the ONLY parameter channel that works.
postExecutionChannelsstringnonullPost-execution alert channels, comma-separated. ACCEPTED VALUES (case-insensitive): webhook, email, psa — CIPP recognises no others, and any other value is refused before dispatch. Sent as body field 'postExecution' in the object form CIPP actually reads, {"Webhook":true,"Email":true,"PSA":true} with real JSON booleans; CIPP turns that into the stored comma-joined string and wildcard-matches it at alert time. An array of plain strings — the previous shape — matched nothing and stored NO channels while still returning success.
psabooleannonullIGNORED at CIPP-API @df3738d — the top-level 'psa' key is not read (PSA post-execution is selected via postExecutionChannels). NO effect. Retained for forward-compatibility only.
rawJsonParametersstringnonullDEPRECATED and REFUSED — do not use this parameter; it is retained only so an existing caller gets a clear error instead of an unknown-parameter failure, and every value supplied is refused before dispatch. 'RawJsonParameters' is never read at CIPP-API @df3738d: CIPP populates task parameters ONLY from the 'parameters' object. Passing it used to schedule a task with EMPTY parameters and report success. Use the 'parametersJson' parameter instead — it is the only parameter channel that works.
recurrenceJsonstringnonullRecurrence configuration. Maps to body field 'Recurrence'. Pass a JSON object string; CIPP reads '.value' when present, else stores the value as-is. An empty/'0' recurrence marks the task one-shot (completed after its run).
referencestringnonullFree-form audit reference string stored on the task. Maps to body field 'reference'.
rowKeystringnonullExisting row key — supply it to EDIT that task (the stored entity is replaced; omitted fields are cleared) or, with runNow=true, to re-run it unchanged. Maps to body field 'RowKey'. Leave null to create.
runNowbooleannonullIf true, queue the task to run immediately. Maps to body field 'RunNow'. WARNING: when rowKey names an EXISTING task, CIPP discards the entire request body and re-runs the STORED task unchanged — every other field here (name, tenantFilter, command, parameters, recurrence...) is ignored. This tool refuses that combination when you also supply edit fields; edit first with runNow omitted, then call again with rowKey + runNow=true.
scheduledTimeintegernonullUnix epoch SECONDS for the scheduled execution time. Maps to body field 'ScheduledTime'. 0/omitted means the next 15-minute interval. A parseable desiredStartTime OVERWRITES this value.
taskTypeJsonstringnonullIGNORED at CIPP-API @df3738d — 'taskType' is not among the keys CIPP copies into the stored task, so this has NO effect. Retained for forward-compatibility only.
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com) the scheduled task runs against. Sent as body field 'tenantFilter' wrapped as {label, value} — VERIFIED: CIPP reads '.value' (falling back to a bare string) and stores it as the task's tenant. Use cipp_list_tenants to discover available tenants.
triggerJsonstringnonullTrigger configuration. Maps to body field 'Trigger'. Pass a JSON object string; CIPP stores it as compressed JSON. A Trigger whose Type is 'DeltaQuery' also provisions a delta query, and a failure there fails the whole call.
usersbooleannonullIGNORED at CIPP-API @df3738d — 'users' is not read. NO effect. Retained for forward-compatibility only.
webhookbooleannonullIGNORED at CIPP-API @df3738d — the top-level 'webhook' key is not read (webhook post-execution is selected via postExecutionChannels). NO effect. Retained for forward-compatibility only.

[CIPP] Get details for a single scheduled item via POST /api/ListScheduledItemDetails. Required body key (exact spec casing): 'RowKey' (PascalCase — the scheduled item's row key). Same value also accepted as a query parameter. Use cipp_list_scheduled_items first to find the row key.

ParamTypeRequiredDefaultDescription
fieldsJsonstringyesRequired JSON object merged into the request body. Spec-listed body keys (exact casing): 'RowKey' (PascalCase string — the scheduled item row key). Example: {"RowKey":"<row-key>"}. Caller is responsible for exact spec casing.

[CIPP] List all scheduled tasks and jobs in CIPP. Returns task name, command, schedule, and last execution status. These are CIPP-level scheduled items, not tenant-specific.

[CIPP] Remove a scheduled task from CIPP. Use cipp_list_scheduled_items first to find the task ID. These are CIPP-level scheduled items, not tenant-specific.

ParamTypeRequiredDefaultDescription
idstringyesScheduled item ID to remove. Use cipp_list_scheduled_items to find valid IDs.

[CIPP] Trigger an immediate scheduler billing run via GET /api/ExecSchedulerBillingRun. Forces CIPP to evaluate billing-related scheduled jobs out-of-cycle. No request parameters per spec. NOT tenant-scoped.

Utility

ToolPlanAccessSummary
cipp_breach_searchProWriteRUN a Have I Been Pwned breach search for a tenant via POST /api/ExecBreachSearch.
cipp_geoip_lookupFreeRead-onlyLook up geographic location information for an IP address via POST /api/ExecGeoIPLookup.
cipp_get_alertsFreeRead-onlyGet current CIPP system alerts and notifications.
cipp_get_queue_statusFreeRead-onlyRead CIPP's own background-job queue via GET /api/ListCippQueue.
cipp_get_versionFreeRead-onlyGet the current CIPP instance version and build information.
cipp_graph_requestProWriteExecute a single custom Microsoft Graph API request against a tenant via GET /api/ListGraphRequest.
cipp_list_breaches_accountFreeRead-onlyList known data breaches for ONE account or domain via GET /api/ListBreachesAccount.
cipp_list_breaches_tenantFreeRead-onlyList known data breaches associated with ONE tenant's domain via GET /api/ListBreachesTenant.
cipp_list_csp_skuFreeRead-onlyList the CSP (Cloud Solution Provider) SKUs and license offerings available to ONE tenant via GET /api/ListCSPsku.
cipp_list_users_and_groupsFreeRead-onlyList ONE tenant's users and groups together for quick directory browsing via GET /api/ListUsersAndGroups.
cipp_manage_csp_licenseProDestructiveAdd, change, remove, cancel or schedule the removal of Sherweb CSP license subscriptions via POST /api/ExecCSPLicense.
cipp_universal_searchFreeRead-onlySearch across all CIPP data for a tenant including users, devices, groups, and policies.
cipp_universal_search_v2FreeRead-onlySearch CIPP's cached tenant data with the V2 search engine via GET /api/ExecUniversalSearchV2. At CIPP-API master @df3738d the endpoint searches ONE data type per call, chosen by 'type': Users (the…

[CIPP] RUN a Have I Been Pwned breach search for a tenant via POST /api/ExecBreachSearch. WARNING — this returns NO breach data. Upstream calls New-BreachTenantSearch SYNCHRONOUSLY: despite CIPP's own docstring and the '#Move to background job' to-do comment above the call, nothing is queued — the request BLOCKS while every domain on the tenant is looked up and the hits are written to CIPP's UserBreaches table, and only then answers HTTP 200 with one canned sentence: 'Executing Search for <tenant>. This may take up to 24 hours to complete.' That sentence describes a background job CIPP does not actually run, so read a 200 as 'the search has already finished', never as 'a job was accepted and is still working'. Because the lookup runs inline, a tenant with many domains can exceed the Azure Functions HTTP timeout and surface as a gateway timeout instead of the 200 — that is a SLOW search rather than necessarily a failed one, and the table write may still have landed, so check cipp_list_breaches_tenant before re-running. Read the actual breach sources, dates and exposed data types with cipp_list_breaches_tenant — do NOT parse this response for breach records. Upstream reads exactly ONE field, from the BODY: 'tenantFilter' (camelCase); the tool seeds it. No other body key is read, so this endpoint cannot be targeted at a single email address.

ParamTypeRequiredDefaultDescription
additionalFieldsJsonstringnonullForward-compatibility escape hatch: a JSON object whose properties are merged into the request body LAST, overriding any of the typed parameters above. Upstream reads no body key other than 'tenantFilter', so anything else passed here is inert until CIPP adds one. NOTE a 'tenantFilter' passed HERE merges last and WINS — upstream reads the tenant from the BODY only (the query string this tool also sets is inert on this endpoint), so it retargets the search at THAT customer and only the returned sentence names which tenant actually ran. Use the typed parameter. Keys are passed verbatim — caller is responsible for exact spec casing.
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Upstream reads 'tenantFilter' from the BODY (the tool also sends it on the query string, which this endpoint ignores). Use cipp_list_tenants to discover available tenants.

[CIPP] Look up geographic location information for an IP address via POST /api/ExecGeoIPLookup. Returns country, city, ISP, and threat intelligence data. CIPP body field is 'IP' (uppercase) per spec — case-sensitive. This endpoint is NOT tenant-scoped. Use additionalFieldsJson only for forward-compatibility with new CIPP fields not yet exposed as typed parameters.

ParamTypeRequiredDefaultDescription
additionalFieldsJsonstringnonullForward-compatibility escape hatch: a JSON object whose properties are merged into the request body LAST, overriding any of the typed parameters above. Use only for new CIPP fields that this tool does not yet expose. Keys are passed verbatim — caller is responsible for exact spec casing.
ipstringyesThe IP address to look up (IPv4 or IPv6). Maps to body field 'IP' (uppercase) per the CIPP spec.

[CIPP] Get current CIPP system alerts and notifications. Returns alert messages, severity, and recommended actions.

[CIPP] Read CIPP's own background-job queue via GET /api/ListCippQueue. Pass the queueId that a queued read handed back in Metadata.QueueId (cipp_get_mailbox_rules is the common one) to see how that job is doing: each entry carries Name, Reference, Status ('Running', 'Completed' or 'Completed (with errors)'), TotalTasks / CompletedTasks / RunningTasks / FailedTasks with the matching percentages, a per-task list and timestamps (that is the classic queue-table backend; on a CIPPNG instance the same endpoint can also report 'Failed' or 'Not found', and a queueId spanning several chained orchestrator runs comes back as ONE rolled-up entry rather than each run separately — so treat the status set as open and do not branch on it as if it were closed). An id CIPP never minted comes back as an EMPTY array. The queueId lookup applies NO time window — unlike the reference and no-argument branches, which are limited to the last three hours — so a finished job's entry stays readable until CIPP's nightly table-cleanup timer reaps CippQueue rows whose last write is over 7 days old. Inside that week an empty array means the id is not in the queue table at all rather than a completed job having expired out of it; past it a long-finished id comes back empty too, so do not read an empty array as proof the id was never minted. Pass reference instead to see one reference's queues from the last three hours, or neither to list the last three hours of every queue. This endpoint is instance-wide rather than tenant-scoped, so it takes no tenant. Reading the queue never starts, cancels or alters a job.

ParamTypeRequiredDefaultDescription
queueIdstringnonullOptional. The queue entry to read, exactly as a queued tool returned it in Metadata.QueueId. Sent as CIPP's 'QueueId'. An unknown id returns an empty array rather than an error.
referencestringnonullOptional. A queue reference name, sent as CIPP's 'Reference' — returns that reference's queues from the last three hours.

[CIPP] Execute a single custom Microsoft Graph API request against a tenant via GET /api/ListGraphRequest. Use this for read-only Graph queries that don't have a dedicated CIPP MCP tool. Spec query parameters (case-sensitive — preserve EXACTLY): tenantFilter (camelCase string, REQUIRED — target tenant domain), Endpoint (PascalCase string — Graph endpoint path, e.g., '/users', '/groups'), AsApp (PascalCase string — boolean-as-string, when 'true' uses application permissions), Version (PascalCase string — Graph API version, e.g., 'v1.0' or 'beta'), graphFilter (camelCase string — OData $filter expression), Sort (PascalCase string — OData $orderby expression), expand (camelCase string — OData $expand expression), ListProperties (PascalCase string — properties to return), CountOnly (PascalCase string — boolean-as-string, return only the result count), NoPagination (PascalCase string — boolean-as-string, disable auto-pagination), manualPagination (camelCase string — boolean-as-string), IgnoreErrors (PascalCase string — boolean-as-string), ReverseTenantLookup (PascalCase string — boolean-as-string), ReverseTenantLookupProperty (PascalCase string), SkipCache (PascalCase string — boolean-as-string), QueueId (PascalCase string — CIPP queue id for batched calls), QueueNameOverride (PascalCase string), nextLink (camelCase string — Graph @odata.nextLink for resuming pagination). The current tool signature only exposes tenantFilter and endpoint (mapped to the Endpoint query parameter); other query parameters are not yet exposed. For multi-request bulk Graph operations use cipp_bulk_graph_request instead. Returns raw Graph JSON. Use with caution — broad access bypasses CIPP's domain-specific safety prompts.

ParamTypeRequiredDefaultDescription
endpointstringyesMicrosoft Graph API endpoint path (e.g., '/users', '/groups', '/devices', '/directoryRoles', '/policies/identityProtection/...'). Sent as the Endpoint query-string parameter (PascalCase). The leading slash is conventional but CIPP accepts either form.
fieldsJsonstringnonullOptional fields to merge into the request body as a JSON object. The live spec marks the body required even on this GET endpoint, but lists no required properties — the request must carry an empty (or caller-supplied) JSON object to satisfy the spec. The published optional body keys appear inherited and are typically left empty. Pass null to send an empty body.
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Sent as the tenantFilter query-string parameter (camelCase, required per spec). Use cipp_list_tenants to discover available tenants.

[CIPP] List known data breaches for ONE account or domain via GET /api/ListBreachesAccount. Returns breach sources, dates, and types of exposed data. Query: 'account' (lowercase, REQUIRED). At CIPP-API master @df3738d a value containing '@' is looked up as an individual account and anything else is treated as a domain, so the same parameter serves both.

ParamTypeRequiredDefaultDescription
accountstringyesREQUIRED. An email address (looked up as an account) or a bare domain (looked up as a domain). Sent as the 'account' query parameter.

[CIPP] List known data breaches associated with ONE tenant's domain via GET /api/ListBreachesTenant. Returns breach sources, dates, and affected accounts. Query: 'tenantFilter' (camelCase, REQUIRED). WARNING — this endpoint fails SILENTLY: at CIPP-API master @df3738d the tenant becomes the storage partition key and the answer is always HTTP 200, so a wrong or missing tenant returns an empty list that reads as 'no breaches' rather than as an error. Confirm the tenant name against cipp_list_tenants before treating an empty result as clean.

ParamTypeRequiredDefaultDescription
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Use cipp_list_tenants to discover available tenants.

[CIPP] List the CSP (Cloud Solution Provider) SKUs and license offerings available to ONE tenant via GET /api/ListCSPsku. Returns SKU names, IDs, and pricing tiers. Query: 'tenantFilter' (camelCase, REQUIRED). The catalog is sourced from Sherweb at CIPP-API master @df3738d, and a tenant with no Sherweb mapping returns an EMPTY LIST at HTTP 200 rather than an error — so an empty result means 'not mapped' as often as it means 'no SKUs'.

ParamTypeRequiredDefaultDescription
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Use cipp_list_tenants to discover available tenants.

[CIPP] List ONE tenant's users and groups together for quick directory browsing via GET /api/ListUsersAndGroups. Returns combined results with type indicators. Query: 'tenantFilter' (camelCase, REQUIRED) — CIPP-API master @df3738d runs the Graph batch against that tenant, and without one the batch runs against the CIPP installation's own partner tenant or returns nothing at all.

ParamTypeRequiredDefaultDescription
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Use cipp_list_tenants to discover available tenants.

[CIPP] Add, change, remove, cancel or schedule the removal of Sherweb CSP license subscriptions via POST /api/ExecCSPLicense. This spends money — it drives Set-SherwebSubscription / Remove-SherwebSubscription against the tenant's live CSP subscriptions. Upstream dispatches on the body key 'Action' (PascalCase), whose real vocabulary is Add | Remove | NewSub | Cancel | ScheduleRemoval; the tool takes it as a typed parameter, normalizes the casing and REFUSES anything else — including an 'Action' supplied through fieldsJson, which is re-validated against the same list AFTER the merge and must name the SAME verb as the typed parameter: a different verb, or a second case-differing spelling of the key, is refused rather than dispatched, so the verb you pass is always the verb that runs and only one 'Action' ever reaches the wire. That matters because CIPP does nothing for an unrecognized or missing Action while still answering HTTP 200 'License change executed successfully.' — a typo would read as a completed license change. The TARGET is guarded the same way: this endpoint reads the tenant from the BODY only (the query string the tool also sets is inert here), so a 'tenantFilter' supplied through fieldsJson must name the SAME tenant as the typed parameter, under exactly that one spelling of the key — a different tenant would bill a different customer, and a second case-differing spelling puts both keys on the wire, so which customer CIPP would bill is ambiguous; either is refused before anything is sent. Body keys per action: Add → 'SKU' + 'Add' (units to add); Remove → 'SKU' + 'Remove' (units to remove); NewSub → 'SKU' + 'Quantity'; Cancel → 'SubscriptionIds'; ScheduleRemoval → 'SKU' + optional 'Remove' (default 1) + optional 'DaysBeforeRenewal' (default 3), which creates a CIPP scheduled task ahead of the renewal date rather than acting now. 'SKU' accepts a LabelValue object {label,value} (upstream unwraps .value) or a bare string — use cipp_list_csp_sku to discover SKUs. NOTE: 'iagree' is NOT read anywhere in this endpoint — it is inert and is not the interlock that commits an order; the Action alone commits. Only ScheduleRemoval returns a descriptive result; every other action returns the fixed string 'License change executed successfully.', so the response does not confirm what changed.

ParamTypeRequiredDefaultDescription
actionstringyesThe CSP operation to perform. Accepted (case-insensitive; the tool sends CIPP's exact literal as body key 'Action'): 'Add' (add units to an existing SKU), 'Remove' (remove units now), 'NewSub' (order a new subscription at 'Quantity'), 'Cancel' (cancel the subscriptions in 'SubscriptionIds'), 'ScheduleRemoval' (schedule a decrease for shortly before the renewal date). Anything else is refused — CIPP would silently do nothing and still report success.
fieldsJsonstringyesThe rest of the license operation as a JSON object, merged into the body LAST (so it overrides the typed parameters above). Keys upstream reads: 'SKU' (PascalCase — a LabelValue {label,value}, unwrapped via SKU.value, or a bare SKU string; required for every action except Cancel), 'Add' (PascalCase — units to add, Add action), 'Remove' (PascalCase — units to remove; also the count for ScheduleRemoval, default 1), 'Quantity' (PascalCase — units for NewSub), 'SubscriptionIds' (PascalCase — the subscription ids to cancel), 'DaysBeforeRenewal' (PascalCase number — ScheduleRemoval only, default 3; a value below 1 is NOT clamped to 1, it is RESET to the default 3, so pass 1 explicitly for a one-day lead. 'Remove' is the key that clamps to 1). Example: {"SKU":{"label":"Microsoft 365 Business Premium","value":"<sku-id>"},"Add":10}. 'iagree' is inert — CIPP never reads it. Keys are passed verbatim — caller is responsible for exact spec casing — with ONE exception: an 'Action' here is re-validated against the same closed vocabulary AFTER the merge and must name the same verb as the typed 'action' parameter, and is then normalized onto the single body key 'Action'. A verb that disagrees with the typed one is refused rather than silently replacing it, and so is any other casing of the key (e.g. 'action'), which would put two spellings on the wire and leave which verb CIPP honours ambiguous — simplest is to omit 'Action' here and pass the typed parameter. A 'tenantFilter' here is held to the same rule and for the same reason: upstream reads the tenant from the BODY only, so it must name the same tenant as the typed 'tenantFilter' parameter (compared case-insensitively) under exactly that one casing — a mismatch is refused rather than billing a different customer, and so is any other casing of the key (e.g. 'TenantFilter'), which would put two spellings on the wire and leave which customer CIPP bills ambiguous. Simplest is to omit it: the tool injects it.
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Sent as the 'tenantFilter' body field (camelCase, required) per the CIPP spec. The tool injects this for you. Use cipp_list_tenants to discover available tenants.

[CIPP] Search across all CIPP data for a tenant including users, devices, groups, and policies. Returns matching results from all categories. This is a broad search across all data types. For targeted lookups, use specific list tools (cipp_list_users, cipp_list_devices) which are faster.

ParamTypeRequiredDefaultDescription
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Use cipp_list_tenants to discover available tenants.

[CIPP] Search CIPP's cached tenant data with the V2 search engine via GET /api/ExecUniversalSearchV2. At CIPP-API master @df3738d the endpoint searches ONE data type per call, chosen by 'type': Users (the default), Groups, Applications or Licenses. Query: 'searchTerms' (camelCase, REQUIRED), optional 'limit' (upstream default 10) and optional 'type'. It reads no tenant filter: it searches every tenant the CIPP API client is scoped to. For targeted lookups, use the specific list tools (cipp_list_users, cipp_list_groups), which are faster.

ParamTypeRequiredDefaultDescription
limitintegernonullOptional. Maximum number of results to return. Sent as the 'limit' query parameter; CIPP defaults to 10 when it is omitted.
searchTermsstringyesREQUIRED. The text to search for across CIPP's cached tenant data. Sent as the 'searchTerms' query parameter.
typestringnonullOptional. What to search: Users (default), Groups, Applications or Licenses. Sent as the 'type' query parameter; CIPP searches Users when it is omitted.

Diagnostics

ToolPlanAccessSummary
cipp_app_insights_queryProRead-onlyQuery Application Insights telemetry for the CIPP backend via GET /api/ExecAppInsightsQuery.
cipp_cipp_db_cacheFreeRead-onlyStart a CIPP database cache SYNC via GET /api/ExecCIPPDBCache.
cipp_clone_templateProWriteClone a CIPP template (CA, standards, alert, etc.) via POST /api/ExecCloneTemplate.
cipp_cpv_refreshProWriteRefresh CSP Vendor (CPV) consent and permissions across managed tenants via GET /api/ExecCPVRefresh.
cipp_delete_graph_explorer_presetProDestructiveDelete a saved Graph Explorer preset via DELETE /api/ExecGraphExplorerPreset.
cipp_download_cipp_logsProWriteGenerate SAS-signed URLs to download CIPP application logs via POST /api/ExecCippLogsSas.
cipp_durable_functionsProRead-onlyRead CIPP Durable Functions state via GET /api/ExecDurableFunctions.
cipp_edit_templateProDestructiveEdit a stored CIPP template (CA, standards, alert, Intune, etc.) via POST /api/ExecEditTemplate.
cipp_extension_ninja_one_queueFreeRead-onlyList the NinjaOne extension processing queue via GET /api/ExecExtensionNinjaOneQueue.
cipp_list_admin_portal_licensesFreeRead-onlyList the low-friction trial license allotments visible from the M365 admin portal for a tenant via GET /api/ListAdminPortalLicenses (camelCase 'tenantFilter' query, required).
cipp_list_api_testFreeRead-onlyRun the CIPP API self-test via GET /api/ListApiTest.
cipp_list_azure_ad_connect_statusFreeRead-onlyGet ONE tenant's Entra Connect (Azure AD Connect) status via GET /api/ListAzureADConnectStatus: whether directory sync is on, the sync interval, password hash sync and pass-through authentication…
cipp_list_backupProRead-onlyList CIPP backup snapshots via GET /api/ExecListBackup.
cipp_list_check_ext_alertsFreeRead-onlyList the extension (external system) alerts CIPP has recorded for one tenant, newest first, via GET /api/ListCheckExtAlerts.
cipp_list_custom_data_mappingsFreeRead-onlyList CIPP custom-data-mapping definitions via GET /api/ListCustomDataMappings.
cipp_list_db_cacheFreeRead-onlyList ONE tenant's CIPP database cache contents via GET /api/ListDBCache.
cipp_list_diagnostics_presetsProRead-onlyList saved diagnostic query presets via GET /api/ListDiagnosticsPresets.
cipp_list_directory_objectsFreeRead-onlyResolve Entra ID directory objects by ID for a tenant via POST /api/ListDirectoryObjects.
cipp_list_extension_cache_dataFreeRead-onlyRead CIPP extension cache data for a tenant via POST /api/ListExtensionCacheData.
cipp_list_extensions_configFreeRead-onlyList CIPP extension configurations via GET /api/ListExtensionsConfig.
cipp_list_feature_flagsFreeRead-onlyList CIPP feature flags via GET /api/ListFeatureFlags.
cipp_list_function_parametersFreeRead-onlyList the parameter schema for a CIPP function via GET /api/ListFunctionParameters.
cipp_list_function_statsFreeRead-onlyList CIPP function execution statistics via GET /api/ListFunctionStats.
cipp_list_generic_test_functionFreeRead-onlyRun the CIPP generic test function via GET /api/ListGenericTestFunction.
cipp_list_graph_explorer_presetsFreeRead-onlyList saved Graph Explorer query presets via GET /api/ListGraphExplorerPresets.
cipp_list_halo_clientsFreeRead-onlyList HaloPSA clients visible to CIPP via GET /api/ListHaloClients.
cipp_list_ip_whitelistFreeRead-onlyList CIPP's allowed IP ranges via GET /api/ListIPWhitelist.
cipp_list_known_ip_dbFreeRead-onlyList the CIPP known-IP database for a tenant via GET /api/ListKnownIPDb.
cipp_list_notification_configProRead-onlyRead the CIPP notification configuration via GET /api/ListNotificationConfig.
cipp_partner_webhookProDestructiveConfigure Microsoft Partner Center webhook delivery via POST /api/ExecPartnerWebhook.
cipp_set_cipp_auto_backupProWriteEnable or disable CIPP automatic backups via POST /api/ExecSetCIPPAutoBackup.
cipp_set_package_tagProWriteTag a CIPP package (e.g., for app deployments) via POST /api/ExecSetPackageTag.
cipp_set_user_bookmarksProWritePersist a CIPP UI user's bookmarks via POST /api/ExecUserBookmarks.
cipp_user_settingsProWriteSave a CIPP UI user's app settings via POST /api/ExecUserSettings.

[CIPP] Query Application Insights telemetry for the CIPP backend via GET /api/ExecAppInsightsQuery. Used for diagnostics and performance review. Query: 'query' (lowercase, REQUIRED) — a KQL statement such as "requests | take 10". CIPP-API master @df3738d reads the statement from either the request body or the query string and answers HTTP 400 'No query provided in request body.' when neither carries one; this tool sends it on the query string, which upstream reads the same way.

ParamTypeRequiredDefaultDescription
querystringyesREQUIRED. The KQL statement to run against the CIPP backend's Application Insights workspace (e.g., "requests | take 10"). Sent as the 'query' query parameter.

[CIPP] Start a CIPP database cache SYNC via GET /api/ExecCIPPDBCache. Despite the GET verb this is not a read: at CIPP-API master @df3738d the endpoint STARTS a cache-sync orchestration for the named cache and answers once that work is queued. Query: 'tenantFilter' (camelCase) and 'Name' (PascalCase) — BOTH REQUIRED; upstream throws 'TenantFilter parameter is required' without a tenant, and this tool sent neither value before 2026-09-03. Passing 'AllTenants' expands the managed-tenant list and FANS THE SYNC ACROSS EVERY TENANT in the CIPP installation, so name one tenant unless an estate-wide refresh is the intent. Optional 'Types' (PascalCase) narrows which cached types are synced. Use cipp_list_db_cache to inspect what a tenant already has cached.

ParamTypeRequiredDefaultDescription
namestringyesREQUIRED. The name of the cache to sync, sent as CIPP's 'Name' (PascalCase). It must match one of CIPP's cache functions: upstream resolves it to 'Set-CIPPDBCache<Name>' and an unknown name fails with "Cache function 'Set-CIPPDBCache<Name>' not found". The cache names a tenant already holds, as listed by cipp_list_db_cache, are valid values.
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Use cipp_list_tenants to discover available tenants. Passing 'AllTenants' fans the sync across every managed tenant.
typesstringnonullOptional. A COMMA-SEPARATED list that narrows which cached types are synced, sent as CIPP's 'Types' (PascalCase); upstream splits it on commas and trims each entry. Omitted entirely when not supplied.

[CIPP] Clone a CIPP template (CA, standards, alert, etc.) via POST /api/ExecCloneTemplate. CIPP body keys per spec (both PascalCase): 'GUID' (string — the source template GUID) and 'Type' (string — the template kind). Both fields also accepted as query parameters. NOT tenant-scoped.

ParamTypeRequiredDefaultDescription
fieldsJsonstringyesRequired JSON object merged into the request body. Spec-listed body keys (exact casing): 'GUID' (PascalCase string — the source template GUID), 'Type' (PascalCase string — the template kind, e.g., a CIPP template type identifier). Both fields also accepted as query parameters. Example: {"GUID":"<template-guid>","Type":"<template-type>"}. Caller is responsible for exact spec casing.

[CIPP] Refresh CSP Vendor (CPV) consent and permissions across managed tenants via GET /api/ExecCPVRefresh. Re-applies CPV permissions to tenants that have drifted. No request parameters per spec.

[CIPP] Delete a saved Graph Explorer preset via DELETE /api/ExecGraphExplorerPreset. Upstream reads the preset as a NESTED OBJECT — $Request.Body.preset.id is the row key it removes — so this tool sends {"action":"Delete","preset":{"id":...,"name":...}}; a flat 'preset' string deletes NOTHING and comes back as 'Error: You can only modify your own presets.' at HTTP 200. Two upstream traps this tool closes: (1) 'action' defaults to 'Delete' and only 'Delete' is accepted, because CIPP's DEFAULT branch — an absent or unrecognized action — sets Action='Copy' with a NEW GUID and CREATES a preset, i.e. a delete call that quietly adds one; (2) a Delete carrying no preset id is refused here, since upstream would answer HTTP 200 with the misleading ownership error instead. Deletion is owner-scoped: CIPP removes only presets whose Owner matches the calling identity and otherwise returns that same ownership error at HTTP 200 — read the Results text, a 200 is not proof of deletion. Use cipp_list_graph_explorer_presets to find the preset id.

ParamTypeRequiredDefaultDescription
actionstringnonullOptional. Only 'Delete' is accepted (case-insensitive) and it is the default. Sent as the body key 'action'. CIPP's other verbs (Copy/Save) CREATE or overwrite presets, so they are not exposed on a delete tool, and an unrecognized value is refused here rather than silently becoming CIPP's 'Copy' default.
fieldsJsonstringnonullOptional JSON object merged into the request body LAST, overriding the typed parameters above. Upstream reads its preset fields from INSIDE the nested 'preset' object — id, name, endpoint, $filter, $select, $count, $expand, $search, $orderby, $format, $top, NoPagination, ReverseTenantLookup, ReverseTenantLookupProperty, AsApp, version, IsShared — so to set any of them pass a full {"preset":} object here, which REPLACES the one built from the typed parameters. ('reportTemplate' is not read by this endpoint at all.) The ONE key that is not passed through unchecked is 'action': the MERGED body is re-validated, so an override is refused unless it is exactly 'Delete' — case-insensitive, but with NO surrounding whitespace, because the value you send is forwarded verbatim and upstream does not trim, so ' Delete ' would miss CIPP's own match and fall into the same create-a-preset default branch — and a differently-cased spelling ('Action') is refused as ambiguous because both keys would ship and CIPP resolves body keys case-insensitively. That closes the route by which a delete tool could drive CIPP's default branch, which CREATES a new preset with a fresh GUID and still answers HTTP 200.
namestringnonullOptional preset name. Sent as the NESTED body field preset.name. Upstream does NOT use it to find the preset for a delete — only preset.id selects the row — it is carried for parity with CIPP's own payload. Use cipp_list_graph_explorer_presets to find names and ids.
presetstringnonullThe preset's id (the GUID from cipp_list_graph_explorer_presets). Sent as the NESTED body field preset.id — the ONLY field that selects which preset is deleted. The call is refused when no id is resolvable.

[CIPP] Generate SAS-signed URLs to download CIPP application logs via POST /api/ExecCippLogsSas. Spec body required: (PascalCase 'Days', the lookback window). Role CIPP.AppSettings.ReadWrite — operator-class diagnostics for the CIPP instance itself, NOT tenant-scoped.

ParamTypeRequiredDefaultDescription
daysstringyesLookback window in days as a string (e.g., '7'). Sent as body key 'Days' (PascalCase, string-typed per spec).

[CIPP] Read CIPP Durable Functions state via GET /api/ExecDurableFunctions. Useful for diagnosing background job state. Spec query: 'Action' (string), 'PartitionKey' (string). The current client method takes no parameters.

[CIPP] Edit a stored CIPP template (CA, standards, alert, Intune, etc.) via POST /api/ExecEditTemplate. 'parsedRAWJson' is an OBJECT, not a string — upstream dereferences '@odata.type' on it, enumerates its .settings collection and re-serializes it. A JSON STRING yields a null '@odata.type', which is refused with "The submitted policy is missing its '@odata.type' and was not saved, because it would no longer deploy." — but ONLY when the STORED template's own RAWJson carries an '@odata.type'. A stored settings-catalog policy keeps its types under settings[].settingInstance and has NO top-level '@odata.type', so against that shape a string passes BOTH guards, is saved as a JSON-encoded string that can never deploy, and still answers 'Successfully saved the template'. The refusal is a partial safety net, not a universal one: always send a real object. WARNING — failures are reported at HTTP 200: every error path returns Results = 'Editing template failed: <reason>', so you MUST read the Results text; only 'Successfully saved the template' means it saved. Key resolution: 'id' WINS over 'GUID' as the row key being overwritten — with ONE exception. On the IntuneTemplate branch, if the stored row carries a SHA (repo/community-synced templates), CIPP mints a NEW GUID and writes a NEW template row, leaving the row you named untouched, and still answers 'Successfully saved the template': editing a SYNCED Intune template FORKS it into a duplicate rather than editing it, so re-read by id to confirm your change landed. 'Type' is read from the QUERY first and then the body (StackJack sends body only). Two branches: Type='IntuneTemplate' rebuilds RAWJson from 'parsedRAWJson' (omit it to keep the stored RAWJson) and refuses any settings entry whose settingInstance lacks '@odata.type'; every OTHER Type stores the WHOLE request body minus GUID/source/isSynced/package as the template JSON — so a partial body REPLACES the stored template with only the fields you sent. NOT tenant-scoped.

ParamTypeRequiredDefaultDescription
fieldsJsonstringyesRequired JSON object sent as the request body. Keys upstream reads: 'id' (lowercase — the template row key; WINS over 'GUID'), 'GUID' (PascalCase, all-caps — used only when 'id' is absent), 'Type' (PascalCase — the template kind, e.g. 'IntuneTemplate'), 'parsedRAWJson' (camelCase, 'RAW' all-caps — a JSON OBJECT, never a string; Intune branch only), 'displayName' (camelCase — falls back to the RAWJson displayName/name, then the stored name), 'description' (lowercase), 'name' (lowercase — used in the audit log line). Example (Intune): {"id":"<template-guid>","Type":"IntuneTemplate","displayName":"...","parsedRAWJson":{"@odata.type":"#microsoft.graph.windows10GeneralConfiguration","settings":[]}}. For any NON-Intune Type the entire object you pass here (minus GUID/source/isSynced/package) becomes the stored template, so send the full template rather than a patch. Keys are passed verbatim — caller is responsible for exact spec casing.

[CIPP] List the NinjaOne extension processing queue via GET /api/ExecExtensionNinjaOneQueue. Useful for diagnosing CIPP NinjaOne sync state. No request parameters per spec.

[CIPP] List the low-friction trial license allotments visible from the M365 admin portal for a tenant via GET /api/ListAdminPortalLicenses (camelCase 'tenantFilter' query, required). Returns only admin-portal trial allotments, so an empty result is normal for a tenant with no active trials. For full assigned-license SKU counts and renewal/term data use cipp_list_licenses.

ParamTypeRequiredDefaultDescription
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Use cipp_list_tenants to discover available tenants.

[CIPP] Run the CIPP API self-test via GET /api/ListApiTest. Returns connectivity status, permission summary, and basic configuration health for the CIPP API itself. No request parameters per spec.

[CIPP] Get ONE tenant's Entra Connect (Azure AD Connect) status via GET /api/ListAzureADConnectStatus: whether directory sync is on, the sync interval, password hash sync and pass-through authentication settings, the last sync time, and the directory objects currently in a sync error state. This is a per-tenant read — CIPP's own page is single-tenant — so the tenant is required. Loop over cipp_list_tenants for an estate-wide view. Returns raw CIPP JSON.

ParamTypeRequiredDefaultDescription
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Use cipp_list_tenants to discover available tenants. Required: without it CIPP reports on the partner tenant itself, not the customer.

[CIPP] List CIPP backup snapshots via GET /api/ExecListBackup. Useful for diagnostics and pre-restore verification. Spec query: 'BackupName' (string), 'NameOnly' (string), 'tenantFilter' (camelCase, required), 'Type' (string). The current client method takes no parameters.

[CIPP] List the extension (external system) alerts CIPP has recorded for one tenant, newest first, via GET /api/ListCheckExtAlerts. Always pass the tenant: CIPP treats a missing tenant like AllTenants and returns every alert for every tenant on the instance, which on a busy instance exceeds the result-size limit and reaches the agent as nothing at all.

ParamTypeRequiredDefaultDescription
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Use cipp_list_tenants to discover available tenants. AllTenants is accepted by CIPP but returns the whole instance's alert history.

[CIPP] List CIPP custom-data-mapping definitions via GET /api/ListCustomDataMappings. Spec query: 'directoryObject' (string), 'sourceType' (string), 'tenantFilter' (camelCase, required). The current client method takes no parameters.

[CIPP] List ONE tenant's CIPP database cache contents via GET /api/ListDBCache. Used for diagnosing data staleness. Query: 'tenantFilter' (camelCase, REQUIRED) — CIPP-API master @df3738d refuses the call outright without it and answers 'Error: tenantFilter query parameter is required'.

ParamTypeRequiredDefaultDescription
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Use cipp_list_tenants to discover available tenants.

[CIPP] List saved diagnostic query presets via GET /api/ListDiagnosticsPresets. Useful for diagnosing CIPP-side issues. No request parameters per spec.

[CIPP] Resolve Entra ID directory objects by ID for a tenant via POST /api/ListDirectoryObjects. Upstream forwards the ids straight to Microsoft Graph's beta directoryObjects/getByIds, which requires a JSON ARRAY of id strings — a comma-joined STRING serializes as a JSON string and Graph rejects it, surfacing as HTTP 400 with no hint that an array was wanted. Pass the ids to the typed 'ids' parameter (comma-separated or a JSON array) and the tool builds the array. Body keys upstream reads (all camelCase): 'ids' (JSON array, required), 'tenantFilter' (required — the tool seeds it), 'asApp' (call Graph with application permissions), 'partnerLookup' (truthy: upstream IGNORES tenantFilter and resolves against CIPP's OWN partner tenant), '$select' (appended to the Graph URI as ?$select=...). Returns the raw Graph getByIds response.

ParamTypeRequiredDefaultDescription
fieldsJsonstringnonullOptional JSON object merged into the request body LAST, overriding the typed parameters above. Keys upstream reads: 'asApp' (camelCase — truthy calls Graph as the application), 'partnerLookup' (camelCase — truthy makes upstream IGNORE tenantFilter and query CIPP's own partner tenant), '$select' (appended to the Graph URI), 'ids' (must be a JSON ARRAY — supplying it here REPLACES the array built from the typed 'ids' parameter). Keys are passed verbatim — caller is responsible for exact spec casing.
idsstringyesThe Entra object IDs to resolve. Accepts a comma-separated list ('<guid1>,<guid2>') or a JSON array ('["<guid1>","<guid2>"]'); either way the tool sends body 'ids' as a JSON ARRAY, which is what Graph's directoryObjects/getByIds requires. At least one id is required.
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Sent on the body as 'tenantFilter' (camelCase, required) per CIPP spec. The tool injects this for you. Use cipp_list_tenants to discover available tenants.

[CIPP] Read CIPP extension cache data for a tenant via POST /api/ListExtensionCacheData. dataTypes selects which cached categories come back and MUST ride the QUERY STRING — upstream resolves its selector as `$Request.Query.dataTypes -split ',' ?? $Request.Body.dataTypes ?? 'All'`, and because PowerShell binds -split tighter than ??, an absent query value leaves an array holding one EMPTY STRING rather than $null, which kills both the body fallback and the 'All' default. The resulting failure is OVER-return, not under-return: upstream then tests `if ($DataTypes -ne 'All')`, PowerShell unrolls that one-element result to the empty string, the test is FALSE, the narrowing branch is skipped, and CIPP answers with the ENTIRE unfiltered extension cache for the tenant. So a body-only dataTypes does not narrow anything and does not fail loudly — it silently returns everything. The tool therefore sends dataTypes on the query and defaults it to 'All'. Pass a comma-separated list to narrow it. Upstream reads tenantFilter from the query first, then the body; the tool sends both.

ParamTypeRequiredDefaultDescription
dataTypesstringno"All"Which cached categories to return, as a comma-separated list. Sent as the query key 'dataTypes'. Defaults to 'All', which takes upstream's no-filter path. A body 'dataTypes' key is NOT read by CIPP — use this parameter.
fieldsJsonstringnonullOptional JSON object merged verbatim into the request body. NOTE: 'dataTypes' passed here is inert — upstream's selector never falls through to the body (see the tool description); use the dataTypes parameter instead. Keys are passed verbatim — caller is responsible for exact spec casing.
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Sent both on the query string and as the 'tenantFilter' body field (camelCase, required) per CIPP spec. The tool injects this for you. Use cipp_list_tenants to discover available tenants.

[CIPP] List CIPP extension configurations via GET /api/ListExtensionsConfig. Spec marks a body required (with optional 'Description', 'Private' boolean, 'includeforks' boolean, 'orgName' object, 'repoName' string, 'searchTerm' object); use additionalFieldsJson to pass them. Returns the configured extensions and their settings.

ParamTypeRequiredDefaultDescription
additionalFieldsJsonstringnonullOptional JSON object merged into the request body. Spec-listed body keys (exact casing): 'Description' (PascalCase string), 'Private' (PascalCase boolean), 'includeforks' (lowercase boolean), 'orgName' (camelCase object), 'repoName' (camelCase string), 'searchTerm' (camelCase object). Caller is responsible for exact spec casing.

[CIPP] List CIPP feature flags via GET /api/ListFeatureFlags. Returns each flag's name and enabled state. No request parameters per spec.

[CIPP] List the parameter schema for a CIPP function via GET /api/ListFunctionParameters. Spec query: 'Compliance' (string), 'Function' (string), 'Module' (string). The current client method takes no parameters.

[CIPP] List CIPP function execution statistics via GET /api/ListFunctionStats. Spec query: 'FunctionType' (string), 'Interval' (string), 'tenantFilter' (camelCase, required), 'Time' (string). The current client method takes no parameters.

[CIPP] Run the CIPP generic test function via GET /api/ListGenericTestFunction. Returns CIPP test diagnostic output. No request parameters per spec.

[CIPP] List saved Graph Explorer query presets via GET /api/ListGraphExplorerPresets. Spec query: 'Endpoint' (string). The current client method takes no parameters.

[CIPP] List HaloPSA clients visible to CIPP via GET /api/ListHaloClients. Used by the CIPP HaloPSA extension. No request parameters per spec.

[CIPP] List CIPP's allowed IP ranges via GET /api/ListIPWhitelist. Returns IP ranges configured for trusted access to CIPP. No request parameters per spec.

[CIPP] List the CIPP known-IP database for a tenant via GET /api/ListKnownIPDb. Returns previously seen sign-in IPs and the geo/risk metadata CIPP cached for them. Spec query: 'tenantFilter' (camelCase, required). The current client method takes no parameters.

[CIPP] Read the CIPP notification configuration via GET /api/ListNotificationConfig. Returns email recipients, webhook URLs, and severity filters. No request parameters per spec.

[CIPP] Configure Microsoft Partner Center webhook delivery via POST /api/ExecPartnerWebhook. Upstream dispatches EXCLUSIVELY on the QUERY parameter 'Action', so this tool takes action as a typed parameter and refuses anything outside CIPP's vocabulary — an unrecognised value reaches upstream's default branch, which answers HTTP 200 with Results='Invalid Action', a failure that reads like a result. ListEventTypes and ListSubscription are reads; CreateSubscription writes CIPP's PartnerWebhookOnboarding config row from the body keys below; SendTest and ValidateTest exercise delivery, and ValidateTest is the only action that reads correlationId. NOT tenant-scoped — it acts on the CIPP instance's OWN Partner Center registration, using the instance tenant id.

ParamTypeRequiredDefaultDescription
actionstringyesThe operation to perform. Accepted (case-insensitive; the tool sends CIPP's exact literal as the query key 'Action'): 'ListEventTypes', 'ListSubscription', 'CreateSubscription', 'SendTest', 'ValidateTest'. Anything else is refused — CIPP would answer HTTP 200 with Results='Invalid Action'.
correlationIdstringnonullOptional correlation id, sent as the query key 'CorrelationId'. Read ONLY by the 'ValidateTest' action; ignored by every other action.
fieldsJsonstringyesJSON object merged verbatim into the request body. Read ONLY by the 'CreateSubscription' action. Keys that branch reads: 'EventType' (PascalCase — a LabelValue {label,value} is accepted and unwrapped via EventType.value, or pass the bare event-type string), 'enabled' (camelCase — cast to a boolean and stored on CIPP's PartnerWebhookOnboarding config row), 'standardsExcludeAllTenants' (camelCase — stored verbatim on the same row). Example: {"EventType":{"label":"...","value":"..."},"enabled":true,"standardsExcludeAllTenants":false}. Pass for the actions that read no body. Caller is responsible for exact spec casing.

[CIPP] Enable or disable CIPP automatic backups via POST /api/ExecSetCIPPAutoBackup. Required body key per spec: 'Enabled' (PascalCase boolean — true to enable, false to disable). NOT tenant-scoped.

ParamTypeRequiredDefaultDescription
fieldsJsonstringyesRequired JSON object merged into the request body. Spec-listed body keys (exact casing): 'Enabled' (PascalCase boolean). Example: {"Enabled":true}. Caller is responsible for exact spec casing.

[CIPP] Tag a CIPP package (e.g., for app deployments) via POST /api/ExecSetPackageTag. Required body keys per spec (all PascalCase strings): 'GUID' (the package GUID), 'Package' (package identifier), 'Remove' (string boolean — 'true' to remove the tag, 'false' to add). NOT tenant-scoped.

ParamTypeRequiredDefaultDescription
fieldsJsonstringyesRequired JSON object merged into the request body. Spec-listed body keys (exact casing, all PascalCase strings): 'GUID' (the package GUID), 'Package' (the package identifier), 'Remove' (string boolean — 'true' to remove the tag, 'false' to add). Example: {"GUID":"<pkg-guid>","Package":"<pkg-id>","Remove":"false"}. Caller is responsible for exact spec casing.

[CIPP] Persist a CIPP UI user's bookmarks via POST /api/ExecUserBookmarks. Upstream reads exactly ONE nested path — currentSettings.bookmarks — and DISCARDS every other property; when that path is missing it substitutes an EMPTY ARRAY and writes it anyway, answering HTTP 200 'Successfully added user bookmarks'. WARNING: that is a silent wipe of the user's stored bookmarks, byte-indistinguishable from a real save — so this tool REFUSES a payload whose root object has no 'bookmarks' property. Required shape: {"bookmarks":[...]}; clearing deliberately is expressible as {"bookmarks":[]}. The write REPLACES the whole stored list (RowKey = the 'user' string, PartitionKey 'UserBookmarks'). NOT tenant-scoped — per-CIPP-user state. For general UI settings use cipp_user_settings.

ParamTypeRequiredDefaultDescription
currentSettingsJsonstringyesThe bookmarks payload as JSON. MUST be a JSON OBJECT carrying a 'bookmarks' property, e.g. {"bookmarks":[{"name":"...","path":"..."}]} — upstream reads currentSettings.bookmarks and ignores every other property, and a payload without it silently overwrites the user's bookmarks with an empty list, so the tool refuses it. Pass {"bookmarks":[]} to intentionally clear. A single non-array value is wrapped into a one-element array by CIPP. Sent as the body key 'currentSettings' (camelCase).
userstringyesCIPP user identifier (typically UPN of the CIPP operator). Sent as body key 'user'; upstream stringifies it into the row's RowKey, so it must be a plain string.

[CIPP] Save a CIPP UI user's app settings via POST /api/ExecUserSettings. Upstream writes ONE table row: RowKey = the stringified 'user' body key, JSON = 'currentSettings' piped through `Select-Object * -ExcludeProperty CurrentTenant, pageSizes, sidebarShow, sidebarUnfoldable, _persist | ConvertTo-Json`. The two types are therefore the OPPOSITE of what the spec suggests: 'currentSettings' must be a JSON OBJECT (a serialized string would store that string's reflected properties — e.g. {"Length":42} — instead of the settings), and 'user' must be a plain STRING identifier (an object there produces a garbage row key). This tool takes both as typed parameters and refuses a non-object settings payload. The write REPLACES that user's settings row wholesale. NOT tenant-scoped — per-CIPP-user state. For bookmarks use cipp_set_user_bookmarks (same table, different PartitionKey).

ParamTypeRequiredDefaultDescription
currentSettingsJsonstringyesThe settings as a JSON OBJECT, e.g. {"theme":"dark","language":"en"}. Sent as the body key 'currentSettings' (camelCase). Must be an object — the tool refuses a string, array or scalar, because upstream pipes this through Select-Object | ConvertTo-Json and would silently store a string's reflected properties instead of your settings. Upstream drops CurrentTenant, pageSizes, sidebarShow, sidebarUnfoldable and _persist before storing, and REPLACES the whole row, so send the complete settings object.
fieldsJsonstringnonullOptional JSON object merged into the request body LAST, overriding the typed parameters above. Upstream reads only 'currentSettings' and 'user'. Keys are passed verbatim — caller is responsible for exact spec casing.
userstringyesCIPP user identifier — a plain string, typically the CIPP operator's UPN (the same value cipp_set_user_bookmarks takes). Sent as the body key 'user'; upstream stringifies it into the settings row's RowKey, so it must not be an object.

Analytics

ToolPlanAccessSummary
cipp_add_test_reportProDestructiveCreate OR UPDATE a CIPP test report definition via POST /api/AddTestReport — this endpoint is an UPSERT, not a create.
cipp_all_tenant_bpaProRead-onlyGet Best Practice Analyzer results across all managed tenants.
cipp_all_tenant_complianceProRead-onlyGet the device compliance summary across all managed tenants via GET /api/ListAllTenantDeviceCompliance, read from Microsoft 365 Lighthouse's managed-tenant compliance data.
cipp_all_tenant_secure_scoreProWriteWRITE — acknowledge or resolve ONE Microsoft Secure Score control for a tenant via POST /api/ExecUpdateSecureScore.
cipp_bulk_graph_requestProRead-onlyRun many Microsoft Graph READS for one tenant in a single batched call via POST /api/ListGraphBulkRequest.
cipp_delete_test_reportProDestructiveDelete a CIPP test report definition via POST /api/DeleteTestReport.
cipp_get_secure_scoreProRead-onlyRead a tenant's Microsoft Secure Score via GET /api/ListGraphRequest with Endpoint=security/secureScores.
cipp_list_available_testsFreeRead-onlyList the full catalogue of CIPP tests that can be selected for a report via GET /api/ListAvailableTests.
cipp_list_secure_score_control_profilesProRead-onlyRead the Microsoft Secure Score control profile catalog via GET /api/ListGraphRequest with Endpoint=security/secureScoreControlProfiles.
cipp_list_test_reportsFreeRead-onlyList saved CIPP test report definitions via GET /api/ListTestReports.
cipp_list_testsFreeRead-onlyList the results of a test run for a tenant via POST /api/ListTests.
cipp_offboard_tenantProDestructiveOffboard a customer tenant via PATCH /api/ExecOffboardTenant.
cipp_run_domain_analyserProWriteStart domain analysis for ONE tenant via POST /api/ExecDomainAnalyser.
cipp_run_testProWriteTrigger a tenant test run via POST /api/ExecTestRun.

[CIPP] Create OR UPDATE a CIPP test report definition via POST /api/AddTestReport — this endpoint is an UPSERT, not a create. Supplying 'ReportId' switches it to an in-place UPDATE of that existing report — every column is rebuilt from the body, so it OVERWRITES the stored report rather than merging into it (CIPP answers 'Successfully updated custom report', or HTTP 400 with Results = 'Failed to save report: Custom report not found' when the id does not exist — the thrown text is wrapped by the catch, so match on the substring, not on the whole Results value); omit 'ReportId' and CIPP mints a new GUID and answers 'Successfully created custom report'. WARNING: cipp_list_test_reports returns a report's identifier under the key 'id', NOT 'ReportId', so round-tripping a fetched report object does the OPPOSITE of an overwrite — 'ReportId' is absent, CIPP mints a new GUID, and you silently get a DUPLICATE report ('Successfully created custom report'). To UPDATE an existing report you must copy the listed 'id' value into a 'ReportId' key yourself; to clone one, round-trip the object as-is. Only 'name' is required (string, max 256 characters — a longer name fails the WHOLE call with HTTP 400). Optional: 'description' (string), and the three test-selection keys 'IdentityTests', 'DevicesTests' and 'CustomTests', each a NATIVE JSON ARRAY (of test-id strings, or of objects carrying an 'id'); each defaults to an empty selection when absent. WARNING — the silent-no-op class: CIPP serializes those arrays itself, so a PRE-SERIALIZED string is double-encoded. The report is still created (HTTP 200 with a success message) but selects ZERO tests, and the defect only surfaces later as an empty result set from cipp_list_tests. NOT tenant-scoped. Discover test ids with cipp_list_available_tests. Pass keys verbatim — caller is responsible for exact casing.

ParamTypeRequiredDefaultDescription
fieldsJsonstringyesRequired JSON object forwarded to CIPP verbatim. Keys CIPP reads: 'name' (REQUIRED string, max 256 chars — the only validated field; over 256 fails the whole call), 'description' (optional string), 'ReportId' (optional PascalCase string — PRESENCE turns this call into an UPDATE that overwrites that report; omit it to create a new one. cipp_list_test_reports names the identifier 'id', not 'ReportId', so to update a listed report you must copy its 'id' into a 'ReportId' key yourself — a raw round-trip creates a duplicate instead), 'IdentityTests' / 'DevicesTests' / 'CustomTests' (optional NATIVE JSON ARRAYS of test ids — e.g. ["ZTNA21772","CIS1113"] — or arrays of objects each carrying an 'id'; CIPP serializes them for you, so never pass a pre-serialized string: it double-encodes and the saved report selects no tests at all while still reporting success). Test ids come from cipp_list_available_tests. Example: {"name":"Q1 Compliance Review","description":"Quarterly identity + device review","IdentityTests":["ZTNA21772"],"DevicesTests":[],"CustomTests":[]}. Caller is responsible for exact casing.

[CIPP] Get Best Practice Analyzer results across all managed tenants. Returns per-tenant pass/fail summaries for security and configuration checks. This runs BPA across all tenants and may take several minutes. For a single tenant, use cipp_list_bpa instead.

[CIPP] Get the device compliance summary across all managed tenants via GET /api/ListAllTenantDeviceCompliance, read from Microsoft 365 Lighthouse's managed-tenant compliance data. REQUIRES Microsoft 365 Lighthouse: the partner must be onboarded and the customer tenants managed in Lighthouse, or there is nothing to return. A reply of 'No data found - This client might not be onboarded in Lighthouse' means exactly that Lighthouse onboarding gap, not a missing CIPP permission. This tool always asks CIPP for every tenant (it sends AllTenants) and may take 30+ seconds. For one tenant's Intune compliance policies use cipp_list_compliance_policies instead.

[CIPP] WRITE — acknowledge or resolve ONE Microsoft Secure Score control for a tenant via POST /api/ExecUpdateSecureScore. This does NOT read or refresh a Secure Score despite the legacy tool name; to READ a score use cipp_get_secure_score. CIPP translates this into a Graph PATCH of security/secureScoreControlProfiles/, so controlName is mandatory — calling this without one makes CIPP PATCH the collection and return HTTP 500 'Failed to set control to . Error: No HTTP resource was found that matches the request URI'. Body keys (case-sensitive): 'TenantFilter' (PascalCase — this endpoint is one of CIPP's PascalCase-body outliers) and 'ControlName' are injected by the tool from the typed parameters; supply 'resolutionType', 'reason' and 'vendorInformation' via fieldsJson. TWO published spec types are WRONG and will silently no-op if followed: 'resolutionType' must be a {value,label} OBJECT (CIPP reads resolutionType.value), NOT a bare string — valid values are 'ThirdParty' (mark complete, receive points), 'Ignored' (risk accepted, no points) and 'Default' (revert to Microsoft detection); and 'vendorInformation' must be the Graph securityVendorInformation OBJECT {provider, providerVersion, subProvider, vendor} — Graph documents provider and vendor as required for this update — NOT a string. Copy vendorInformation verbatim from the matching profile returned by cipp_list_secure_score_control_profiles. 'reason' is a plain string and becomes the Graph 'comment' (CIPP's own UI marks it mandatory). A ControlName beginning with 'scid_' is rejected by CIPP with HTTP 400 — Defender controls can only be changed in the Microsoft 365 Defender portal. The response is CIPP's prose result envelope {"Results":"Successfully set control ... to ..."}, not a score. Requires the Tenant.Administration.ReadWrite role.

ParamTypeRequiredDefaultDescription
controlNamestringyesREQUIRED — the Secure Score control to acknowledge, e.g. 'NonOwnerAccess' or 'MFARegistrationV2'. This is the 'id' of a profile from cipp_list_secure_score_control_profiles, equivalently a controlScores[].controlName from cipp_get_secure_score. Sent as the PascalCase 'ControlName' body field. CIPP PATCHes secureScoreControlProfiles/, so an empty value produces an HTTP 500 rather than a no-op. Values beginning with 'scid_' are Defender controls and are rejected by CIPP with HTTP 400.
fieldsJsonstringnonullOptional fields to merge into the request body as a JSON object, applied LAST (they override the injected keys). Spec body keys (case-sensitive — preserve EXACTLY): 'resolutionType' — a {value,label} OBJECT, NOT a string; value must be 'ThirdParty', 'Ignored' or 'Default'. 'reason' (lowercase string) — free-text justification, surfaced as the Graph comment. 'vendorInformation' (camelCase) — the Graph securityVendorInformation OBJECT {provider, providerVersion, subProvider, vendor}, copied verbatim from the control's profile; provider and vendor are required by Graph. Example: {"resolutionType":{"value":"Ignored","label":"Ignored / Risk Accepted"},"reason":"Compensating control in place","vendorInformation":{"provider":"SecureScore","providerVersion":null,"subProvider":null,"vendor":"Microsoft"}}. The tool injects 'TenantFilter' and 'ControlName' from the typed parameters — do NOT add them here.
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Use cipp_list_tenants to discover available tenants. Sent as the camelCase 'tenantFilter' query argument AND as the PascalCase 'TenantFilter' body field per the live CIPP spec.

[CIPP] Run many Microsoft Graph READS for one tenant in a single batched call via POST /api/ListGraphBulkRequest. READ-ONLY, AND ENFORCED HERE: EVERY element of 'requests' must carry 'method':'GET'. CIPP keeps only the entries whose 'method' is 'GET' and SILENTLY DISCARDS every other entry with no per-item error, so a mixed batch would run the reads, drop the writes and still answer HTTP 200 with nothing to show it — a batch carrying ANY non-GET entry, or an entry with no 'method' at all, is therefore REFUSED before dispatch, naming the offending element index and its method (upstream's own role for this endpoint is CIPP.Core.Read). Body keys CIPP actually reads (all camelCase): 'tenantFilter' (string — body only, there is no query fallback. ALWAYS SEND IT: upstream never validates it and has no refusal keyed on it, so an omitted or misspelled tenantFilter reaches the Graph helpers as a null tenant id, and BOTH Get-AuthorisedRequest and Get-GraphToken substitute the CIPP instance's OWN CSP/partner tenant ($env:TenantID) for a null — so the batch is tokened against the PARTNER tenant and answers HTTP 200 either way: that tenant's own data when CIPP's tenant list contains it, an empty body when the authorisation check refuses it, and in neither case anything naming the tenant or reporting an error), 'requests' (a NATIVE JSON ARRAY of objects, NOT a JSON-encoded string), 'asApp' (native JSON boolean), 'noPaginateIds' (array of request ids to skip pagination for). Each element of 'requests' must carry 'id', 'method' and 'url' — the keys 'endpoint' and 'body' are NEVER read, so an element keyed 'endpoint' sends url=null. 'url' is a Graph-relative path resolved against the Microsoft Graph BETA endpoint (CIPP's batch helper defaults to /beta/$batch), e.g. '/users?$top=5'. CIPP chunks the array into Graph $batch calls of 20, so more than 20 entries is fine. An array with no GET at all is refused here too, naming the HTTP 400 'No requests found in the body' it would otherwise draw. WARNING on 'asApp': CIPP tests it for truthiness only, so the STRING "false" is truthy in PowerShell and is FORWARDED rather than switching the flag off — send a real JSON boolean, and OMIT the key entirely to disable it. Working example: {"tenantFilter":"contoso.onmicrosoft.com","requests":[{"id":"1","method":"GET","url":"/users?$top=5"}]}. Your payload is forwarded verbatim; it is only ever INSPECTED here, never rewritten — the tool refuses shapes that could never succeed, plus any batch that is not entirely GETs.

ParamTypeRequiredDefaultDescription
fieldsJsonstringyesBulk request definition as a JSON object, forwarded to CIPP verbatim. Keys CIPP reads (all camelCase): 'tenantFilter' (string — a single tenant domain such as contoso.onmicrosoft.com; body only. Nothing upstream enforces it: omit it and the batch runs against the CIPP instance's OWN CSP/partner tenant, because the Graph helpers substitute their $env:TenantID for a null tenant id — HTTP 200 either way, carrying that tenant's data or an empty body, never an error naming the tenant you meant. Send it on every call), 'requests' (a NATIVE JSON ARRAY — each element an object with 'id' (string, correlates the response), 'method' (must be 'GET' on EVERY element — CIPP silently discards non-GET entries, so a batch carrying any other verb, or an entry with no 'method', is REFUSED here before dispatch rather than sent to be partially dropped) and 'url' (Graph-relative path against the BETA endpoint, e.g. '/users?$top=5'). The keys 'endpoint' and 'body' are NOT read by CIPP), 'asApp' (native JSON boolean — true executes as the CIPP application instead of the delegated user; OMIT the key to disable, never send the string "false", which PowerShell treats as true), 'noPaginateIds' (JSON array of request ids to exclude from auto-pagination). Every other key is ignored by CIPP. Example: {"tenantFilter":"contoso.onmicrosoft.com","requests":[{"id":"1","method":"GET","url":"/users?$top=5"},{"id":"2","method":"GET","url":"/groups?$top=5"}]}.

[CIPP] Delete a CIPP test report definition via POST /api/DeleteTestReport. Required body key per spec: 'ReportId' (PascalCase string — the report GUID to delete). NOT tenant-scoped. Use cipp_list_test_reports to find the report ID. Pass keys verbatim — caller is responsible for exact spec casing.

ParamTypeRequiredDefaultDescription
fieldsJsonstringyesRequired JSON object merged into the request body. Spec-listed body keys (exact casing): 'ReportId' (PascalCase string — the report GUID). Example: {"ReportId":"<report-guid>"}. Caller is responsible for exact spec casing.

[CIPP] Read a tenant's Microsoft Secure Score via GET /api/ListGraphRequest with Endpoint=security/secureScores. THIS is the Secure Score read — it is the exact call the CIPP UI's own Secure Score page makes. (cipp_all_tenant_secure_score is NOT a read: it is a per-control acknowledgement write against ExecUpdateSecureScore.) Returns the {Results, Metadata} ListGraphRequest envelope where Results is the Graph secureScores history, newest first: currentScore, maxScore, createdDateTime, averageComparativeScores (all-tenant and similar-size-tenant averages) and controlScores[] (per-control controlName, score, scoreInPercentage, controlCategory, description). Only the last 7 score snapshots are requested, matching the CIPP dashboard window. controlScores[].controlName is opaque — join it against cipp_list_secure_score_control_profiles to get human-readable titles and remediation guidance. Requires the CIPP.Core.Read role.

ParamTypeRequiredDefaultDescription
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Use cipp_list_tenants to discover available tenants. Sent as the camelCase 'tenantFilter' query argument. 'AllTenants' is NOT supported by this endpoint — the CIPP UI blanks its Secure Score page for AllTenants; enumerate tenants and call this tool per tenant.

[CIPP] List the full catalogue of CIPP tests that can be selected for a report via GET /api/ListAvailableTests. Returns {IdentityTests, DevicesTests, CustomTests}, where the first two are the built-in framework tests discovered on disk (CIS, CISA, Essential Eight, EIDSCA, ORCA — each with id, name, category, testFolder) and CustomTests are this instance's custom PowerShell scripts (latest version per ScriptGuid, with description, risk, enabled, alertOnFailure, version). The ids returned here are what cipp_add_test_report's IdentityTests / DevicesTests / CustomTests arrays take. NOT tenant-scoped, and takes NO input: CIPP reads neither a request body nor any query argument on this endpoint. WARNING — silent no-op: any filter you supply is IGNORED and you get HTTP 200 with the FULL unfiltered catalogue and no signal that filtering never happened. Filter the returned list yourself.

ParamTypeRequiredDefaultDescription
additionalFieldsJsonstringnonullForward-compatibility escape hatch ONLY. CIPP's ListAvailableTests reads NOTHING from the request today — no body, no query — so anything passed here is silently discarded and the response is the complete, unfiltered catalogue. There are no 'name' or 'description' filters despite what the published spec implies. Leave this unset unless a future CIPP release starts reading a body; keys would then be passed verbatim.

[CIPP] Read the Microsoft Secure Score control profile catalog via GET /api/ListGraphRequest with Endpoint=security/secureScoreControlProfiles. This is the lookup table for cipp_get_secure_score: each profile's 'id' matches a controlScores[].controlName from the score, and carries the human-readable title, remediation, remediationImpact, implementationCost, userImpact, tier, threats, complianceInformation, actionUrl and controlStateUpdates (the acknowledgement history). It also carries the 'vendorInformation' object that cipp_all_tenant_secure_score requires verbatim when acknowledging a control. Returns the {Results, Metadata} ListGraphRequest envelope; the whole catalog is fetched in one call. Requires the CIPP.Core.Read role.

ParamTypeRequiredDefaultDescription
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Use cipp_list_tenants to discover available tenants. Sent as the camelCase 'tenantFilter' query argument.

[CIPP] List saved CIPP test report definitions via GET /api/ListTestReports. Returns report names, descriptions, and configured device/identity test selections. No request parameters per spec.

[CIPP] List the results of a test run for a tenant via POST /api/ListTests. Body keys per spec (all camelCase): 'reportId' (string — the test report ID) and 'tenantFilter' (string, required). Both fields also accepted as query parameters. Use cipp_list_test_reports to find report IDs.

ParamTypeRequiredDefaultDescription
fieldsJsonstringyesRequired JSON object merged into the request body. Spec-listed body keys (exact casing): 'reportId' (camelCase string — the test report ID, also accepted as query parameter). Example: {"reportId":"<report-guid>"}. The tool injects 'tenantFilter' from the typed parameter. Caller is responsible for exact spec casing.
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Sent on the body as 'tenantFilter' (camelCase, required) per the CIPP spec. The tool injects this for you. Use cipp_list_tenants to discover available tenants.

[CIPP] Offboard a customer tenant via PATCH /api/ExecOffboardTenant. WARNING: destructive and effectively irreversible — it terminates GDAP relationships, removes CSP-side data and apps, and (with TerminateContract) ends the CSP contract itself. The tool injects the tenant as the PascalCase 'TenantFilter' body key, which is what this endpoint reads (most CIPP endpoints use camelCase 'tenantFilter'; this one does not). CIPP accepts either a bare string or a {label,value} object there — the tool sends the bare string, and no fieldsJson override is needed for it. The other keys upstream reads include: 'RemoveCSPGuestUsers', 'RemoveCSPnotificationContacts' (exact casing — 'notification' is lowercase mid-key), 'RemoveDomainAnalyserData', 'RemoveQuarantineAlert', 'RemoveMultitenantCSPApps', 'TerminateGDAP' and 'TerminateContract' — all real JSON booleans, each compared against true, so an omitted flag means that action is NOT performed. 'TerminateGDAP' does MORE than its name says: it is the only flag that triggers CIPP's ClearCache path, which DELETES the tenant's own row from CIPP's Tenants table, so the tenant disappears from cipp_list_tenants and from every tenant-scoped CIPP tool — and it fires even if every individual GDAP termination failed. 'vendorApplications' (camelCase) is an ARRAY of {label,value} objects: CIPP deletes one service principal per element, so several vendor apps go in ONE call (a single object is tolerated because the pipeline iterates a scalar once). Nothing happens for a flag you leave out — an offboard with an empty body removes nothing. Pass keys verbatim — caller is responsible for exact casing.

ParamTypeRequiredDefaultDescription
fieldsJsonstringyesOffboarding configuration as a JSON object. Keys CIPP reads (all PascalCase except 'vendorApplications'), every boolean a REAL JSON boolean compared against true — omit one and that action is simply skipped: 'RemoveCSPGuestUsers' (removes guest users created via CSP), 'RemoveCSPnotificationContacts' (exact casing, 'notification' lowercase), 'RemoveDomainAnalyserData' (purges CIPP's domain analyser data for the tenant), 'RemoveQuarantineAlert' (removes the 'CIPP User requested to release a quarantined message' protection alert policy — leave it out and that policy stays behind in the customer's tenant), 'RemoveMultitenantCSPApps' (removes CSP-deployed multi-tenant apps), 'TerminateGDAP' (terminates every active GDAP relationship AND — it is the ONLY flag that triggers CIPP's ClearCache path — DELETES the tenant's own row from CIPP's Tenants table, removing it from cipp_list_tenants and from every tenant-scoped CIPP tool. The deletion happens even if the individual GDAP terminations all fail, because the flag is set before that try block, and it is reported only as a line inside the 200 response's Results array), 'TerminateContract' (ends the CSP contract; effectively irreversible), 'vendorApplications' (camelCase ARRAY of {label,value} objects — one service principal is deleted per element, so list every vendor app you want removed in a single call; 'value' is the service principal id, 'label' is only used in the result text). The tool injects 'TenantFilter'; you do not need to add it. Example: {"TerminateGDAP":true,"RemoveCSPGuestUsers":true,"RemoveQuarantineAlert":true,"vendorApplications":[{"label":"Vendor A","value":"00000000-0000-0000-0000-000000000001"}]}. Keys are passed verbatim — caller is responsible for exact casing.
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). This endpoint reads the PascalCase 'TenantFilter' body key — NOT the camelCase 'tenantFilter' most CIPP endpoints use — and the tool injects it for you as a bare string, which CIPP accepts alongside the {label,value} object form. No fieldsJson override is required. Use cipp_list_tenants to discover available tenants.

[CIPP] Start domain analysis for ONE tenant via POST /api/ExecDomainAnalyser. The analyser runs asynchronously through a CIPP orchestrator, so the response is a start acknowledgement — {"Results":"Domain Analyser started"} or {"Results":"Domain Analyser error: check logs"} — never the findings themselves; read the stored findings afterwards with cipp_list_domain_analyser, the per-tenant scorecard covering DNS health, email authentication (SPF/DKIM/DMARC) and domain security posture. cipp_list_domain_health is NOT a substitute for that read — it is a per-domain, per-check probe that requires an 'action' and a 'domain', and reaches one domain's stored row only via action='ListDomainInfo'. CIPP reads a single body key, 'tenantFilter' (camelCase; a bare string or a object are both accepted), which the tool injects from the typed parameter. Upstream treats that key as OPTIONAL and sweeps EVERY tenant when it is absent, but this tool always sends the one tenant you name — it cannot start an all-tenant sweep, by design. If the connected CIPP identity lacks AllTenants access and the named tenant is not in its allowed list, CIPP answers HTTP 403 {"Results":"Access to this tenant is not allowed"}.

ParamTypeRequiredDefaultDescription
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Use cipp_list_tenants to discover available tenants. Sent as the camelCase 'tenantFilter' body field — CIPP reads it from the body only, there is no query fallback on this endpoint. A tenant outside a restricted CIPP identity's allowed list is refused with HTTP 403.

[CIPP] Trigger a tenant test run via POST /api/ExecTestRun. CIPP reads TWO fields, not one: 'tenantFilter' (camelCase, required — resolved from the QUERY FIRST and only then from the body, and this tool sends it on both lanes, so the typed parameter always wins) and 'mode', which selects what the run actually does. 'mode' accepts 'both' (default — collect fresh cached data AND run the tests), 'cache' (data collection only, no tests) or 'tests' (run the tests against already-cached data, forced). CIPP lowercases the value, so 'BOTH'/'Cache' are fine. WARNING: an unrecognized value is NOT an error — CIPP silently falls back to 'both', so a typo quietly runs the heaviest variant instead of the one asked for. Pass 'mode' through additionalFieldsJson. The call is asynchronous: it returns a start acknowledgement such as {"Results":"Successfully started test run for contoso.onmicrosoft.com"}, not results — read those with cipp_list_tests once the run completes.

ParamTypeRequiredDefaultDescription
additionalFieldsJsonstringnonullOptional JSON object merged into the request body LAST. It CANNOT retarget the run: upstream resolves the tenant as Query.tenantFilter ?? Body.tenantFilter — query FIRST — and this tool always puts the typed tenant on the query string, so a 'tenantFilter' supplied here is overridden and has no effect. Use the typed parameter. The one field worth passing today is 'mode' (lowercase key), which upstream does read from the body (Query.mode ?? Body.mode, and this tool sets no mode query key): 'both' (default) collects data then runs the tests, 'cache' collects data only, 'tests' runs the tests only against cached data. Any other value silently degrades to 'both' rather than erroring. Example: {"mode":"tests"}. Keys are passed verbatim — caller is responsible for exact casing.
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Sent both on the query string and as the 'tenantFilter' body field (camelCase, required) per the CIPP spec. The tool injects this for you. Use cipp_list_tenants to discover available tenants.

Application Approvals

ToolPlanAccessSummary
cipp_add_multi_tenant_appProWriteQueue a multi-tenant enterprise-app deployment via POST /api/ExecAddMultiTenantApp.
cipp_create_app_templateProWriteCapture an existing app in a source tenant as a reusable app-approval template via POST /api/ExecCreateAppTemplate.
cipp_delete_app_approval_templateProDestructiveDelete an application approval template via DELETE /api/ExecAppApprovalTemplate, body-carried.
cipp_delete_app_permission_templateProDestructiveDelete an application permission-set template via DELETE /api/ExecAppPermissionTemplate, body-carried.
cipp_exec_app_approvalFreeRead-onlyBuild the per-tenant Microsoft admin-consent links for an application, via GET /api/ExecAppApproval.
cipp_exec_applicationProDestructiveEdit or delete an application object / service principal via PATCH /api/ExecApplication.
cipp_exec_service_principalsProDestructiveList, get, or create service principals in the PARTNER tenant via GET /api/ExecServicePrincipals (the parameters travel in the query string; there is no request body and no tenantFilter — the endpoint…
cipp_list_app_approval_templatesFreeRead-onlyList all application approval templates in CIPP.

[CIPP] Queue a multi-tenant enterprise-app deployment via POST /api/ExecAddMultiTenantApp. WARNING — verified against CIPP-API master: this endpoint validates NOTHING about the shapes you send; every wrong-shaped request still answers HTTP 200 with a 'Deploying to , see the logbook for details' sentence while deploying to nobody. (The manual branch does carry one HTTP 400, but only for an infrastructure failure — a Get-Tenants or queue-write error — never for a body you got wrong; the template branch hard-sets 200 and has no non-200 path at all.) Read the CIPP logbook, never the status code — a 200 means QUEUED (work is handed to an async orchestrator), not deployed. 'configMode' is the SOLE gate on the entire function and is a CLOSED vocabulary — 'manual' or 'template'; with anything else, or absent, NEITHER branch runs and nothing is queued, so this tool refuses such a call before sending it. Body shapes upstream actually reads: 'tenantFilter' is an OBJECT {"value":["contoso.onmicrosoft.com"],"label":["Contoso"]} — never a scalar string (CIPP reads .value/.label; a string yields zero tenants) — built for you from the typed tenantFilter parameter. configMode='manual' reads 'AppId' (PascalCase) plus 'permissions', an ARRAY of objects [{"origin":"Delegated"|"Application","id":"<permission-guid>"}] filtered on 'origin'; a string or a missing key yields EMPTY resourceAccess and the app is queued with NO permissions. configMode='template' instead reads 'selectedTemplate', an OBJECT {"value":"<templateId>","label":"<name>","addedFields":{"AppId":"<app-guid>"}}; a string yields TemplateId=null and AppId=null in every queued item. 'CopyPermissions' (PascalCase) is compared against a real boolean — use the typed copyPermissions parameter.

ParamTypeRequiredDefaultDescription
configModestringnonullWhich branch of the endpoint runs: the literal 'manual' (register the app from the 'permissions' array) or 'template' (deploy the template named by 'selectedTemplate'). Matched case-insensitively here and seeded into body key 'configMode'. EFFECTIVELY MANDATORY — upstream's whole body is an if/elseif on this key with no else, so any other value (or none) executes nothing, assigns no status and returns an empty HTTP 200; this tool refuses the call instead of sending it. A 'configMode' supplied in fieldsJson wins the merge and is checked against the same closed vocabulary, but it is NOT trimmed — a padded value there is refused rather than sent, because the caller's exact bytes are what CIPP receives. The refusal names the value it rejected.
copyPermissionsbooleannonullmanual mode only. true routes the deployment through CIPP's 'ExecApplicationCopy' command instead of 'ExecAddMultiTenantApp'. Sent as a REAL JSON boolean in body key 'CopyPermissions' (PascalCase) because upstream tests `-eq $true`; a JSON string other than exactly "true" silently selects the non-copy path — a wrong-but-successful deployment. Omit to leave the key off entirely.
fieldsJsonstringyesThe rest of the CIPP body as a JSON object, merged LAST (a key here overrides the tool-injected 'tenantFilter'/'configMode'/'CopyPermissions'). Keys are passed verbatim — the tool never rewrites them. Required set depends on configMode. manual: 'AppId' (PascalCase, the app registration's appId GUID) and 'permissions' — an ARRAY of objects, each with 'origin' ('Delegated' → Graph Scope, or 'Application' → Graph Role) and 'id' (the permission GUID). Omitting 'permissions', or passing a string, silently queues the app with NO permissions at HTTP 200. template: 'selectedTemplate' — an OBJECT {"value":"<templateId>","label":"<template name>","addedFields":{"AppId":"<app-guid>"}}; a string silently queues TemplateId=null/AppId=null items at HTTP 200. Example (manual): {"AppId":"<app-guid>","permissions":[{"origin":"Application","id":"df021288-bdef-4463-88db-98f22de89214"}]}. Example (template): {"selectedTemplate":{"value":"<template-guid>","label":"Baseline","addedFields":{"AppId":"<app-guid>"}}}.
tenantFilterstringyesTarget tenant(s). One domain (contoso.onmicrosoft.com), a comma-separated list of domains, or the exact literal 'allTenants'. The tool converts it into the OBJECT shape CIPP actually reads — {"tenantFilter":{"value":["contoso.onmicrosoft.com"],"label":["contoso.onmicrosoft.com"]}} — because upstream reads $Request.Body.tenantFilter.value and .label and never the scalar. WARNING: 'allTenants' expands SERVER-SIDE to every tenant CIPP manages ((Get-Tenants).defaultDomainName) and deploys the app across the whole estate. Use cipp_list_tenants to discover tenants.

[CIPP] Capture an existing app in a source tenant as a reusable app-approval template via POST /api/ExecCreateAppTemplate. Body keys are ALL PascalCase, the tenant included: upstream reads $Request.Body.TenantFilter, and the tool injects that PascalCase key from the typed parameter. (PowerShell's body lookup is case-insensitive, so a camelCase 'tenantFilter' would be found too — but adding one in fieldsJson simply puts two case-differing properties on the wire and makes which one wins undefined. Do not 'correct' it.) Required: 'AppId' and 'DisplayName' — either missing throws and CIPP answers HTTP 400. 'Type' is BINARY, not a vocabulary: exactly 'servicePrincipal' takes the READ-ONLY service-principal branch. ANY other value — including 'application' and omission — takes the app-registration branch, which is NOT read-only: it 400s when the registration is unreadable, but when the registration IS readable and the source tenant is not your own partner tenant, CIPP looks for an app of the same DisplayName in the PARTNER tenant and, finding none, COPIES the whole app registration there (POST /v1.0/applications against the partner tenant, with signInAudience rewritten AzureADMyOrg -> AzureADMultipleOrgs) and creates a service principal for it. The saved template then carries that NEW partner-tenant AppId, not the source one. Use Type='servicePrincipal' unless you intend that copy. Only genuinely multi-tenant apps are accepted: a signInAudience outside AzureADMultipleOrgs / AzureADandPersonalMicrosoftAccount is rejected at HTTP 400 with 'create a Manifest (single-tenant) template for this app instead'. 'Overwrite' is evaluated as a real boolean — use the typed overwrite parameter. The saved template is always named '<DisplayName> (Auto-created)'. Unlike most tools here this endpoint does report failure: HTTP 400 with Results.state='error'.

ParamTypeRequiredDefaultDescription
fieldsJsonstringyesThe rest of the CIPP body as a JSON object, merged LAST (a key here overrides the tool-injected 'TenantFilter' and 'Overwrite'). All PascalCase. 'AppId' — REQUIRED, the application's appId GUID in the source tenant; missing ⇒ HTTP 400 'AppId is required'. 'DisplayName' — REQUIRED; missing ⇒ HTTP 400 'DisplayName is required'; it also names the saved template, as '<DisplayName> (Auto-created)'. 'Type' — exactly 'servicePrincipal' selects the service-principal branch (permissions read from the app registration, else rebuilt from oauth2PermissionGrants + appRoleAssignments); ANY other value or omission selects the app-registration branch, which throws 'App registration not found for AppId' at HTTP 400 when the registration is not readable — and, when it IS readable and the source tenant is not your partner tenant, WRITES a copy of the app registration plus a service principal INTO the partner tenant and stamps the new AppId on the template (see the tool description). Example: {"AppId":"<app-guid>","DisplayName":"My App","Type":"servicePrincipal"}. Keys are passed verbatim — the tool never rewrites them.
overwritebooleannonullUpdate the matching template in place instead of adding a duplicate. Sent as a REAL JSON boolean in body key 'Overwrite' because upstream evaluates `$Request.Body.Overwrite -eq $true`; a string flag — even "True" or "1" — leaves it false. WARNING: when false or omitted CIPP skips the existing-template lookup entirely and writes a NEW row under a fresh template GUID, so repeated calls accumulate duplicate templates at HTTP 200. The overwrite match key depends on 'Type': AppId for 'servicePrincipal', otherwise the template name '<DisplayName> (Auto-created)'.
tenantFilterstringyesSource tenant that already holds the app (e.g., contoso.onmicrosoft.com). Injected as body key 'TenantFilter' (PascalCase 'T') — the tenant key this endpoint reads; do not add a camelCase 'tenantFilter' in fieldsJson, because PowerShell's body lookup is case-insensitive and a second case-differing property just makes which one wins undefined (the connector also sends a tenantFilter query argument, which upstream never reads). It is also stamped into the saved template as SourceTenant. Use cipp_list_tenants to discover available tenants.

[CIPP] Delete an application approval template via DELETE /api/ExecAppApprovalTemplate, body-carried. The endpoint is a switch on body key 'Action' whose default branch — taken whenever Action is absent or unrecognized — LISTS every template at HTTP 200 and deletes nothing, so this tool seeds 'Action':'Delete' unless you name a branch yourself. The Delete branch reads exactly ONE key: 'TemplateId' (the stored RowKey GUID); 'TemplateName' is NOT read from the request there — the deleted template's name is read back off the stored row. Every outcome of the Delete, Get and default branches is HTTP 200 — only the Save branch can answer 400, for an ApplicationManifest carrying keyCredentials/passwordCredentials — so read the response body and never the status code. NOTE THE SHAPE: this endpoint serializes its body as a JSON ARRAY. A delete answers [{"Results":"Successfully deleted template '<name>'"}] and a non-matching id [{"Results":"No template found with the provided ID"}], so Results lives on element [0], NOT at the top level; the default (list) branch returns the template array the same way. Use cipp_list_app_approval_templates to find the ID.

ParamTypeRequiredDefaultDescription
actionstringnonullWhich switch branch runs, sent as body key 'Action' (PascalCase). Defaults to 'Delete' — the tool seeds it because an absent or unrecognized Action falls into the default branch, which lists all templates at HTTP 200 and deletes nothing (a silent no-op from a tool named delete). 'Get' also only reads. WARNING: 'Save' WRITES a template row built from the rest of the body — do not pass it here.
templateIdstringnonullTemplate ID to delete — the stored RowKey/GUID, and the ONLY key the Delete branch matches on. Sent as body key 'TemplateId' (PascalCase). Omit it and CIPP matches nothing and answers 'No template found with the provided ID' at HTTP 200. Use cipp_list_app_approval_templates to find valid template IDs.
templateNamestringnonullTemplate display name. Sent as body key 'TemplateName' (PascalCase), but the Delete branch NEVER reads it — it is read only by the endpoint's Save branch. It is NOT an alternative to templateId: supplying it alone deletes nothing and returns 'No template found with the provided ID' at HTTP 200. Leave unset for deletions.

[CIPP] Delete an application permission-set template via DELETE /api/ExecAppPermissionTemplate, body-carried. The endpoint is a switch on body key 'Action' whose default branch — Action absent or unrecognized — LISTS the permission templates at HTTP 200 and deletes nothing, so this tool seeds 'Action':'Delete' unless you name a branch yourself. The Delete branch reads exactly ONE key: 'TemplateId' (the RowKey under PartitionKey 'Templates'). 'TemplateName' and 'Permissions' are read only by the Save branch and are INERT for a delete. The endpoint ALWAYS returns HTTP 200 (the status is hard-coded), so read the response body and never the status code — and note the body is a JSON ARRAY: with no TemplateId it answers [{"Results":"No Template ID provided for deletion"}], a successful delete [{"Results":"Successfully deleted template '<name>'"}]. Results lives on element [0], NOT at the top level.

ParamTypeRequiredDefaultDescription
actionstringnonullWhich switch branch runs, sent as body key 'Action' (PascalCase). Defaults to 'Delete' — the tool seeds it because an absent or unrecognized Action falls into the default branch, which lists the permission templates at HTTP 200 and deletes nothing. WARNING: 'Save' WRITES a permission-template row from 'TemplateName' + 'Permissions' — do not pass it here.
permissionsJsonstringnonullPermissions payload as a JSON object/array, merged verbatim into body key 'Permissions' (PascalCase). INERT for a deletion — upstream reads 'Permissions' only in its Save branch. Leave unset unless you are deliberately driving Save.
templateIdstringnonullTemplate ID to delete — the stored RowKey/GUID, and the only key the Delete branch matches on. Sent as body key 'TemplateId' (PascalCase). Omit it and CIPP deletes nothing and answers 'No Template ID provided for deletion' at HTTP 200.
templateNamestringnonullTemplate display name. Sent as body key 'TemplateName' (PascalCase), but the Delete branch NEVER reads it (the deleted name is read back off the stored row) — it is read only by the endpoint's Save branch. Inert for a deletion; leave unset.

[CIPP] Build the per-tenant Microsoft admin-consent links for an application, via GET /api/ExecAppApproval. Returns one entry per CIPP-managed tenant: {defaultDomainName, link}, where link is https://login.microsoftonline.com/<tenantGuid>/v2.0/adminconsent?client_id=<appId>&scope=<appId>/.default — <appId> being the applicationId you pass, or CIPP's own SAM application when omitted. This tool GRANTS NOTHING: an admin in each tenant must open the link and consent — and no API can approve or deny an admin-consent request (Microsoft does not expose one). NOTE the scope: <appId>/.default covers EVERY permission statically registered on that application, which can be more than a specific pending consent request asked for — say so when handing a link to a tenant admin.

ParamTypeRequiredDefaultDescription
applicationIdstringnonullOptional application (client) ID to build consent links for. Omit to use CIPP's own SAM application.

[CIPP] Edit or delete an application object / service principal via PATCH /api/ExecApplication. CLOSED vocabularies (CIPP 400s an invalid Action): 'Action' is one of Update | Upsert | Delete | RemoveKey | RemovePassword ('Upsert' = Update plus create-if-missing); 'Type' is one of applications | servicePrincipals — Type is only VALIDATED when supplied, but omitting it builds a malformed Graph URL that fails downstream, so ALWAYS supply it. At least one of 'Id' (object ID) or 'AppId' is required; when both are present, Id wins. 'Payload' (the Graph PATCH body as a NESTED JSON OBJECT — CIPP serializes it itself, so passing a JSON string gets double-encoded and Graph rejects it) and 'KeyIds' (an ARRAY of key credential IDs, or {value: [...]} — a comma-joined string counts as ONE id and removes nothing; required by RemoveKey/RemovePassword) are body-only; 'Action'/'AppId'/'Id'/'Type'/'tenantFilter' are accepted in body or query. This tool CANNOT grant consent — the endpoint edits application objects and removes credentials only; consent is granted by a tenant admin via the links from cipp_exec_app_approval. Body keys are mixed-case per CIPP's contract ('tenantFilter' camelCase, the rest PascalCase); pass keys verbatim.

ParamTypeRequiredDefaultDescription
fieldsJsonstringyesApplication action parameters as JSON object. Body keys per spec (all PascalCase except tenantFilter): 'Action' (the operation verb — also forwardable as query parameter), 'AppId' (the application/app-registration GUID — also query-forwardable), 'Id' (the service principal or object ID — also query-forwardable), 'KeyIds' (ARRAY of key credential IDs — a comma-joined string counts as one id and removes nothing), 'Payload' (the Graph PATCH body as a NESTED JSON OBJECT — never a string; CIPP serializes it itself), 'Type' (the application type identifier — also query-forwardable). Example: {"Action":"<verb>","AppId":"<app-guid>","Id":"<sp-guid>","Type":"...","KeyIds":"...","Payload":"..."}. The tool also injects 'tenantFilter' from the typed parameter. Keys are passed verbatim — caller is responsible for exact spec casing.
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Sent as the 'tenantFilter' body field (camelCase, required) per the CIPP spec — and also accepted as a 'tenantFilter' query parameter. The tool injects this for you. Use cipp_list_tenants to discover available tenants.

[CIPP] List, get, or create service principals in the PARTNER tenant via GET /api/ExecServicePrincipals (the parameters travel in the query string; there is no request body and no tenantFilter — the endpoint always acts on the partner tenant). Behavior: action='Create' instantiates the service principal for appId (CIPP blocklists six AppIds — eM Client, PerfectData Software, Newsletter Software Supermailer, rclone, CloudSponge, SigParser — and answers those with HTTP 200 and Metadata.Success=false); ANY other action value, or no action, READS — by appId, else by id, else lists all (select, a Graph $select projection, applies to the list only). The endpoint always returns HTTP 200: check Metadata.Success in the response, never the status code.

ParamTypeRequiredDefaultDescription
actionstringnonullOperation. Only 'Create' writes (creates the service principal for appId); omit or any other value reads.
appIdstringnonullApplication (client) ID — for Create, or to get a single service principal by app id.
idstringnonullService principal object ID — to get a single service principal when appId is not used.
selectstringnonullGraph $select projection, applied to the list-all read only.

[CIPP] List all application approval templates in CIPP. Returns template names, app permissions, and approval configurations.

Applications

ToolPlanAccessSummary
cipp_add_choco_appProWriteQueue a Chocolatey package for Intune deployment via POST /api/AddChocoApp — a CROSS-TENANT fan-out that writes one row per target tenant into CIPP's 'apps' queue table.
cipp_add_msp_appProWriteQueue an RMM/MSP agent installer for Intune deployment via POST /api/AddMSPApp — a CROSS-TENANT fan-out that writes one 'Not Deployed yet' row per target tenant into CIPP's 'apps' queue table.
cipp_add_office_appProDestructiveDeploy Microsoft 365 Apps (Office) to Intune via POST /api/AddOfficeApp — a CROSS-TENANT fan-out.
cipp_add_store_appProWriteQueue a Microsoft Store (winget-source) application for Intune deployment via POST /api/AddStoreApp — a CROSS-TENANT fan-out that writes one 'Not Deployed yet' row per target tenant into CIPP's 'apps'…
cipp_add_win32_script_appProWriteQueue a Win32 script-based application for Intune deployment via POST /api/AddWin32ScriptApp — a CROSS-TENANT fan-out that writes one 'Not Deployed yet' row per target tenant into CIPP's 'apps' queue…
cipp_assign_appProDestructiveAssign an existing Intune application to users, groups, or devices in a tenant via POST /api/ExecAssignApp.
cipp_exec_app_uploadProWriteUpload an application package to Intune.
cipp_list_application_queueFreeRead-onlyList queued application deployments across tenants.
cipp_list_apps_repositoryFreeRead-onlySearch a Chocolatey-style (NuGet v2) package feed for deployable application definitions via POST /api/ListAppsRepository.
cipp_list_potential_appsFreeRead-onlySearch a public package source for deployable applications via POST /api/ListPotentialApps.
cipp_remove_appProDestructivePermanently remove an Intune-managed application from a tenant via POST /api/RemoveApp.
cipp_remove_queued_appProDestructiveCancel a pending deployment in the CIPP application queue via POST /api/RemoveQueuedApp before it processes.
cipp_sync_vppProWriteTrigger a sync of Apple Volume Purchase Program (VPP) tokens for a tenant via POST /api/ExecSyncVPP.

[CIPP] Queue a Chocolatey package for Intune deployment via POST /api/AddChocoApp — a CROSS-TENANT fan-out that writes one row per target tenant into CIPP's 'apps' queue table. Nothing installs synchronously; CIPP's own deployment processor drains the queue. Targets come ONLY from the body key 'selectedTenants' (never 'tenantFilter'), and that key must be an ARRAY OF OBJECTS — this tool builds it from the selectedTenants parameter. Upstream reads: PackageName (REQUIRED — HTTP 400 if missing or if it fails the regex ^[A-Za-z0-9][A-Za-z0-9._-]*$), ApplicationName, description, AssignTo, CustomGroup, excludeGroup, InstallationIntent, CustomRepo, customArguments, InstallAsSystem, DisableRestart, selectedTenants. WARNING: if no supplied tenant survives the allowed-tenant filter, the deploy loop runs zero times and CIPP still answers HTTP 200 with a null Results member ({"Results":null} — the deploy loop produced no output, so it is null, not an empty array) — a silent no-op that reads as success. Unlike cipp_add_office_app, this endpoint does NOT expand a literal 'AllTenants' domain; it would queue a row against a tenant named 'AllTenants'.

ParamTypeRequiredDefaultDescription
disableRestartbooleannonullSuppress the device restart. Sent as the body key 'DisableRestart' as a REAL JSON boolean. true -> installExperience.deviceRestartBehavior='suppress'; false or omitted -> 'allow'. Never send this as the string "false" — it would evaluate TRUE upstream and suppress restarts.
fieldsJsonstringyesChocolatey deployment configuration as a JSON object, merged into the body AFTER this tool's typed parameters (a key here overrides the same key seeded above). Keys upstream actually reads, with the casing shown: 'PackageName' (string, REQUIRED — Chocolatey package id such as '7zip'; HTTP 400 unless it matches ^[A-Za-z0-9][A-Za-z0-9._-]*$), 'ApplicationName' (string — Intune display name), 'description' (string), 'AssignTo' (string — assignment target; the literal 'customGroup' makes CIPP substitute the 'CustomGroup' value), 'CustomGroup' (string), 'excludeGroup' (string — group excluded from the assignment), 'InstallationIntent' (string — stored verbatim on the queue row, not validated here), 'CustomRepo' (string — alternate Chocolatey feed, appended to the install command as -CustomRepo), 'customArguments' (string — appended as -CustomArguments), 'InstallAsSystem' (JSON boolean — prefer the typed parameter), 'DisableRestart' (JSON boolean — prefer the typed parameter), 'selectedTenants' (the array of {customerId, defaultDomainName} objects; normally supplied by the typed parameter — if you set it here it must be that array, and a plain string is refused). Every other key is ignored upstream. Booleans must be REAL JSON booleans: CIPP truthiness-tests them in PowerShell, where the string "false" is TRUE, so a quoted value silently means the opposite; this tool refuses quoted values on those keys rather than let them invert.
installAsSystembooleannonullRun the install as SYSTEM. Sent as the body key 'InstallAsSystem' as a REAL JSON boolean. true -> installExperience.runAsAccount='system'; false -> 'user'. OMITTED IS 'user' — CIPP's default here is the opposite of cipp_add_store_app's. Never send this as the string "false": PowerShell truthiness makes every non-empty string TRUE, which would silently install as SYSTEM.
selectedTenantsstringyesTarget tenants for this CROSS-TENANT deploy — REQUIRED, comma-separated. Each entry is a tenant's defaultDomainName (e.g. 'contoso.onmicrosoft.com'), optionally prefixed with that tenant's customerId GUID and a pipe: '<customerId>|contoso.onmicrosoft.com'. This tool builds CIPP's real body shape from it — the ARRAY OF OBJECTS [{"customerId":"...","defaultDomainName":"..."}] — because upstream filters each element on .customerId against the API client's allowed-tenant list and then deploys to .defaultDomainName. When the CIPP API client is scoped to all tenants the customerId is not consulted, so a bare domain is enough; when it is scoped to specific tenants the customerId GUID must match or that tenant is dropped without comment. WARNING: this key is NOT 'tenantFilter', and it is NOT a string — a string (or a non-matching tenant) makes the deploy loop run zero times while CIPP still returns HTTP 200 with a null Results member ({"Results":null}, not an empty array), so the call reads as success and nothing is deployed. Use cipp_list_tenants to get defaultDomainName and customerId values.

[CIPP] Queue an RMM/MSP agent installer for Intune deployment via POST /api/AddMSPApp — a CROSS-TENANT fan-out that writes one 'Not Deployed yet' row per target tenant into CIPP's 'apps' queue table. Targets come ONLY from the body key 'selectedTenants' (never 'tenantFilter'), and that key must be an ARRAY OF OBJECTS — unlike the other AddApp endpoints this one keeps the whole object and reads each element's .defaultDomainName; this tool builds it from the selectedTenants parameter. The RMM vendor is a CLOSED vocabulary read from the NESTED key 'RMMName'.'value', which this tool builds from the rmmName parameter; an unsupported value is refused upstream with HTTP 400 listing the accepted set. Upstream reads: RMMName.value (REQUIRED), DisplayName, PackageName, params, AssignTo, CustomGroup, excludeGroup, selectedTenants. Status codes differ from the other AddApp endpoints: 200 when at least one tenant queued cleanly, 207 MultiStatus on partial failure, 500 when every tenant failed — but ZERO matched tenants is still a plain 200 with a null Results member ({"Results":null}, not an empty array), a silent no-op.

ParamTypeRequiredDefaultDescription
fieldsJsonstringyesMSP deployment configuration as a JSON object, merged into the body AFTER this tool's typed parameters (a key here overrides the same key seeded above). Keys upstream actually reads, with the casing shown: 'DisplayName' (string — Intune display name), 'PackageName' (string — passed to Get-CIPPMSPAppInstallCommand), 'params' (object/string — NOT a free-form blob: it is consumed by Get-CIPPMSPAppInstallCommand to build the install/uninstall command lines and the detection script for the chosen vendor, so its shape is vendor-specific), 'AssignTo' (string — the literal 'customGroup' makes CIPP substitute the 'CustomGroup' value), 'CustomGroup' (string), 'excludeGroup' (string — group excluded from the assignment), 'RMMName' (object {"value": "<vendor>"}; normally supplied by the typed parameter), 'selectedTenants' (the array of {customerId, defaultDomainName} objects; normally supplied by the typed parameter — a plain string is refused). Every other key is ignored upstream.
rmmNamestringyesRMM vendor — REQUIRED. Sent as the NESTED body shape 'RMMName': {"value": "<vendor>"} (upstream reads $Request.Body.RMMName.value; a flat string is never read). Accepted values (upstream's closed vocabulary): datto, ninja, Huntress, syncro, NCentral, automate, cwcommand. Matched case-insensitively here and normalized to that exact casing before sending. Anything else is refused, matching upstream's own HTTP 400. WARNING — the vocabulary and the SHIPPED TEMPLATE FILES are out of sync at this pin: having passed the 400 gate, CIPP immediately loads AddMSPApp&lt;value>.app.json, and at @df3738d there is no ninja.app.json and no NCentral.app.json (the Ninja template is named ninjarmm.app.json), while 'Huntress' differs in case from the on-disk huntress.app.json and the CIPP container runs Linux, where -LiteralPath is case-sensitive. That Get-Content sits OUTSIDE the endpoint's per-tenant try/catch and AFTER the 400 gate, so a value with no template file fails as neither a readable 400 nor a per-tenant error entry in Results — do not send one. Values that have a matching template today: datto, syncro, automate, cwcommand (and Huntress only on a case-insensitive filesystem).
selectedTenantsstringyesTarget tenants for this CROSS-TENANT deploy — REQUIRED, comma-separated. Each entry is a tenant's defaultDomainName (e.g. 'contoso.onmicrosoft.com'), optionally prefixed with that tenant's customerId GUID and a pipe: '<customerId>|contoso.onmicrosoft.com'. This tool builds CIPP's real body shape from it — the ARRAY OF OBJECTS [{"customerId":"...","defaultDomainName":"..."}] — because upstream filters each element on .customerId against the API client's allowed-tenant list and then deploys to .defaultDomainName. When the CIPP API client is scoped to all tenants the customerId is not consulted, so a bare domain is enough; when it is scoped to specific tenants the customerId GUID must match or that tenant is dropped without comment. WARNING: this key is NOT 'tenantFilter', and it is NOT a string — a string (or a non-matching tenant) makes the deploy loop run zero times while CIPP still returns HTTP 200 with a null Results member ({"Results":null}, not an empty array), so the call reads as success and nothing is deployed. Use cipp_list_tenants to get defaultDomainName and customerId values.

[CIPP] Deploy Microsoft 365 Apps (Office) to Intune via POST /api/AddOfficeApp — a CROSS-TENANT fan-out. Unlike the other AddApp endpoints this one does NOT queue: it calls Graph beta/deviceAppManagement/mobileApps SYNCHRONOUSLY per tenant. Office is a singleton per tenant: if an app with @odata.type '#microsoft.graph.officeSuiteApp' already exists, that tenant is skipped with 'Office deployment already exists for <tenant>' and nothing is changed. Targets come ONLY from the body key 'selectedTenants' (never 'tenantFilter'), and that key must be an ARRAY OF OBJECTS — this tool builds it from the selectedTenants parameter. WARNING — ALL-TENANTS SWEEP: this endpoint alone expands a target whose defaultDomainName is the literal 'AllTenants' into EVERY tenant CIPP knows (Get-Tenants), so that one value deploys Office org-wide. WARNING — SILENT NO-OP: with no matching tenant the loop runs zero times and CIPP still answers HTTP 200 with a null Results member ({"Results":null}, not an empty array). Assignment: when AssignTo is set and is not the literal 'On', CIPP assigns the new app with Intent='Required' to that group; 'On' means 'do not assign'. MARKED DESTRUCTIVE for four reasons, unlike the queue-based AddApp siblings: the Graph create is SYNCHRONOUS rather than queued, so there is no queue row to review or drain; the literal 'AllTenants' expands the target set to every tenant CIPP knows; a non-'On' AssignTo assigns the new app as Intent='Required' in the SAME call; and removeOtherVersions (body key 'RemoveVersions') UNINSTALLS the tenant's existing Office installs.

ParamTypeRequiredDefaultDescription
acceptLicensebooleannonullAuto-accept the Office EULA. Sent as the body key 'AcceptLicense' as a REAL JSON boolean, cast upstream with [bool] into autoAcceptEula. Omitted is false. Never send the string "false" — it evaluates TRUE.
excludedAppsstringnonullOffice apps to EXCLUDE from the install, as a comma-separated list of names — sent as the body key 'excludedApps' built into a JSON ARRAY, because upstream ITERATES this key (a comma-separated string arrives as one element, matches no property, and is silently ignored with only a server-side Write-Warning). Valid names, matched case-insensitively and normalized: infoPath, sharePointDesigner, excel, groove, lync, oneNote, outlook, powerPoint, publisher, teams, visio, word, access, bing. Any other name is refused here rather than silently dropped. NOTE the baseline: infoPath and sharePointDesigner are excluded by DEFAULT; everything else defaults to included. Naming an app here sets it to excluded; this list cannot re-INCLUDE infoPath or sharePointDesigner.
fieldsJsonstringyesOffice deployment configuration as a JSON object, merged into the body AFTER this tool's typed parameters (a key here overrides the same key seeded above). The body is handed to Get-CIPPOfficeAppBody, which supports THREE shapes, checked in this order: (1) 'IntuneBody' — a complete pre-built officeSuiteApp body (from a saved template); if present it is used verbatim minus Graph read-only properties and EVERY other key below is ignored. (2) 'useCustomXml' (JSON boolean) + 'customXml' (string) — when both are truthy the raw Office Configuration XML is base64-encoded into officeConfigurationXml and every field-level key below is ignored. (3) the individual fields: 'AcceptLicense' (JSON boolean -> autoAcceptEula), 'RemoveVersions' (JSON boolean -> shouldUninstallOlderVersionsOfOffice), 'SharedComputerActivation' (JSON boolean -> useSharedComputerActivation), 'arch' (JSON boolean -> officePlatformArchitecture; prefer the typed parameter), 'excludedApps' (ARRAY, prefer the typed parameter), 'languages' (ARRAY -> localesToInstall, prefer the typed parameter), 'updateChannel' (string, or a {label,value} object whose .value is used — e.g. 'current', 'monthlyEnterprise', 'firstReleaseCurrent', 'currentPreview', 'deferred'; sent to Graph verbatim and NOT validated by CIPP). Assignment/targeting keys read by the endpoint itself: 'AssignTo' (string; the literal 'customGroup' substitutes 'CustomGroup', and the literal 'On' means do-not-assign), 'CustomGroup' (string), 'excludeGroup' (string), 'selectedTenants' (the array of {customerId, defaultDomainName} objects; a plain string is refused). Every other key is ignored. Booleans must be REAL JSON booleans — CIPP casts them with PowerShell [bool], where the string "false" is TRUE — so this tool refuses quoted values on the boolean keys rather than let them invert.
languagesstringnonullOffice UI/proofing languages to install, as a comma-separated list of locale ids (e.g. 'en-us,fr-fr') — sent as the body key 'languages' built into a JSON ARRAY and mapped upstream to localesToInstall. Upstream ITERATES this key, so a comma-separated STRING would arrive as a single bogus locale. Values are passed through verbatim and are not validated by CIPP.
removeOtherVersionsbooleannonullUninstall other Office editions during install. Sent as the body key 'RemoveVersions' as a REAL JSON boolean, cast upstream with [bool] into shouldUninstallOlderVersionsOfOffice. Omitted is false. Never send the string "false" — it evaluates TRUE and would remove existing Office installs.
selectedTenantsstringyesTarget tenants for this CROSS-TENANT deploy — REQUIRED, comma-separated. Each entry is a tenant's defaultDomainName (e.g. 'contoso.onmicrosoft.com'), optionally prefixed with that tenant's customerId GUID and a pipe: '<customerId>|contoso.onmicrosoft.com'. This tool builds CIPP's real body shape from it — the ARRAY OF OBJECTS [{"customerId":"...","defaultDomainName":"..."}] — because upstream filters each element on .customerId against the API client's allowed-tenant list and then deploys to .defaultDomainName. When the CIPP API client is scoped to all tenants the customerId is not consulted, so a bare domain is enough; when it is scoped to specific tenants the customerId GUID must match or that tenant is dropped without comment. WARNING: this key is NOT 'tenantFilter', and it is NOT a string — a string (or a non-matching tenant) makes the deploy loop run zero times while CIPP still returns HTTP 200 with a null Results member ({"Results":null}, not an empty array), so the call reads as success and nothing is deployed. Use cipp_list_tenants to get defaultDomainName and customerId values. EXTRA WARNING for this tool only: the literal domain 'AllTenants' is expanded server-side into EVERY tenant.
sharedComputerActivationbooleannonullEnable shared computer activation. Sent as the body key 'SharedComputerActivation' as a REAL JSON boolean, cast upstream with [bool] into useSharedComputerActivation. Omitted is false. Never send the string "false" — it evaluates TRUE.
use64BitArchitecturebooleannonullInstall 64-bit Office. Sent as the body key 'arch' as a REAL JSON boolean. true -> officePlatformArchitecture='x64'; false or omitted -> 'x86'. THIS IS NOT '32'/'64': upstream treats 'arch' as a plain truthiness toggle, so the strings '32' AND '64' are both TRUE and both deploy x64 — asking for 32-bit with '32' silently deploys 64-bit. Quoted values are refused by this tool.

[CIPP] Queue a Microsoft Store (winget-source) application for Intune deployment via POST /api/AddStoreApp — a CROSS-TENANT fan-out that writes one 'Not Deployed yet' row per target tenant into CIPP's 'apps' queue table. Nothing installs synchronously. Targets come ONLY from the body key 'selectedTenants' (never 'tenantFilter'), and that key must be an ARRAY OF OBJECTS — this tool builds it from the selectedTenants parameter. Upstream reads: PackageName, ApplicationName, description, AssignTo, CustomGroup, InstallationIntent, InstallAsSystem, selectedTenants. Note the Graph winGetAppInstallExperience carries ONLY runAsAccount — there is no restart-behavior key on this endpoint — and that unlike the other Add*App endpoints this one does NOT read 'excludeGroup'. WARNING: with no matching tenant the loop runs zero times and CIPP still answers HTTP 200 with a null Results member ({"Results":null} — the deploy loop produced no output, so it is null, not an empty array) — a silent no-op that reads as success.

ParamTypeRequiredDefaultDescription
fieldsJsonstringyesStore app deployment configuration as a JSON object, merged into the body AFTER this tool's typed parameters (a key here overrides the same key seeded above). Keys upstream actually reads, with the casing shown: 'PackageName' (string — becomes Graph packageIdentifier; the winget/Store package id from cipp_list_potential_apps with type=WinGet), 'ApplicationName' (string — Intune display name), 'description' (string), 'AssignTo' (string — the literal 'customGroup' makes CIPP substitute the 'CustomGroup' value), 'CustomGroup' (string), 'InstallationIntent' (string — stored verbatim on the queue row, not validated here), 'InstallAsSystem' (JSON boolean — prefer the typed parameter), 'selectedTenants' (the array of {customerId, defaultDomainName} objects; normally supplied by the typed parameter — a plain string is refused). Every other key is ignored upstream, including 'excludeGroup'. Booleans must be REAL JSON booleans — CIPP casts with PowerShell [bool], where the string "false" is TRUE — so this tool refuses quoted values on that key.
installAsSystembooleannonullRun the install as SYSTEM. Sent as the body key 'InstallAsSystem' as a REAL JSON boolean. OMITTED IS 'system' here — upstream's rule is: user ONLY when the key is present AND casts to false; otherwise system. That makes false the only value that changes anything, and it is exactly the value a quoted "false" destroys: PowerShell [bool]"false" is TRUE, so the string "false" silently yields SYSTEM. Quoted values are refused by this tool.
selectedTenantsstringyesTarget tenants for this CROSS-TENANT deploy — REQUIRED, comma-separated. Each entry is a tenant's defaultDomainName (e.g. 'contoso.onmicrosoft.com'), optionally prefixed with that tenant's customerId GUID and a pipe: '<customerId>|contoso.onmicrosoft.com'. This tool builds CIPP's real body shape from it — the ARRAY OF OBJECTS [{"customerId":"...","defaultDomainName":"..."}] — because upstream filters each element on .customerId against the API client's allowed-tenant list and then deploys to .defaultDomainName. When the CIPP API client is scoped to all tenants the customerId is not consulted, so a bare domain is enough; when it is scoped to specific tenants the customerId GUID must match or that tenant is dropped without comment. WARNING: this key is NOT 'tenantFilter', and it is NOT a string — a string (or a non-matching tenant) makes the deploy loop run zero times while CIPP still returns HTTP 200 with a null Results member ({"Results":null}, not an empty array), so the call reads as success and nothing is deployed. Use cipp_list_tenants to get defaultDomainName and customerId values.

[CIPP] Queue a Win32 script-based application for Intune deployment via POST /api/AddWin32ScriptApp — a CROSS-TENANT fan-out that writes one 'Not Deployed yet' row per target tenant into CIPP's 'apps' queue table. Nothing installs synchronously. Targets come ONLY from the body key 'selectedTenants' (never 'tenantFilter'), and that key must be an ARRAY OF OBJECTS — this tool builds it from the selectedTenants parameter. Two body keys are hard-required and fail LOUDLY with HTTP 400: an application name (upstream accepts EITHER 'ApplicationName' or 'applicationName') and 'installScript'. Upstream also reads description, publisher, uninstallScript, detectionPath, detectionFile, detectionScript, AssignTo, CustomGroup, InstallationIntent, InstallAsSystem, DisableRestart, runAs32Bit, enforceSignatureCheck. It does NOT read 'excludeGroup'. WARNING: with no matching tenant the loop runs zero times and CIPP still answers HTTP 200 with a null Results member ({"Results":null} — the deploy loop produced no output, so it is null, not an empty array) — a silent no-op that reads as success.

ParamTypeRequiredDefaultDescription
disableRestartbooleannonullSuppress the device restart. Sent as the body key 'DisableRestart' as a REAL JSON boolean. true -> deviceRestartBehavior='suppress'; false or omitted -> 'allow'. Never send the string "false" — it evaluates TRUE and suppresses restarts.
enforceSignatureCheckbooleannonullRequire a valid signature on the package. Sent as the body key 'enforceSignatureCheck' as a REAL JSON boolean, cast upstream with [bool]. Omitted is false. Never send the string "false" — it evaluates TRUE.
fieldsJsonstringyesWin32 script app configuration as a JSON object, merged into the body AFTER this tool's typed parameters (a key here overrides the same key seeded above). Keys upstream actually reads, with the casing shown: 'applicationName' (string, REQUIRED — 'ApplicationName' works equally well; HTTP 400 with 'Application name is required' when both are missing), 'installScript' (string, REQUIRED — PowerShell install script body; HTTP 400 with 'Install script is required' when missing), 'uninstallScript' (string), 'description' (string), 'publisher' (string), 'detectionFile' (string — file or folder name used for presence detection), 'detectionPath' (string — directory for the detection rule), 'detectionScript' (string — script-based detection rule, stored on the queue row), 'AssignTo' (string — the literal 'customGroup' makes CIPP substitute the 'CustomGroup' value), 'CustomGroup' (string), 'InstallationIntent' (string — stored verbatim on the queue row, not validated here), 'InstallAsSystem' / 'DisableRestart' / 'runAs32Bit' / 'enforceSignatureCheck' (JSON booleans — prefer the typed parameters). Every other key is ignored upstream, including 'excludeGroup'. Booleans must be REAL JSON booleans — CIPP truthiness-tests or [bool]-casts them in PowerShell, where the string "false" is TRUE — so this tool refuses quoted values on those keys rather than let them invert.
installAsSystembooleannonullRun the install as SYSTEM. Sent as the body key 'InstallAsSystem' as a REAL JSON boolean. true -> runAsAccount='system'; false or omitted -> 'user'. Never send the string "false": PowerShell truthiness makes every non-empty string TRUE, which would silently install as SYSTEM.
runAs32BitbooleannonullRun the installer as a 32-bit process. Sent as the body key 'runAs32Bit' as a REAL JSON boolean, cast upstream with [bool]. Omitted is false. Never send the string "false" — it evaluates TRUE.
selectedTenantsstringyesTarget tenants for this CROSS-TENANT deploy — REQUIRED, comma-separated. Each entry is a tenant's defaultDomainName (e.g. 'contoso.onmicrosoft.com'), optionally prefixed with that tenant's customerId GUID and a pipe: '<customerId>|contoso.onmicrosoft.com'. This tool builds CIPP's real body shape from it — the ARRAY OF OBJECTS [{"customerId":"...","defaultDomainName":"..."}] — because upstream filters each element on .customerId against the API client's allowed-tenant list and then deploys to .defaultDomainName. When the CIPP API client is scoped to all tenants the customerId is not consulted, so a bare domain is enough; when it is scoped to specific tenants the customerId GUID must match or that tenant is dropped without comment. WARNING: this key is NOT 'tenantFilter', and it is NOT a string — a string (or a non-matching tenant) makes the deploy loop run zero times while CIPP still returns HTTP 200 with a null Results member ({"Results":null}, not an empty array), so the call reads as success and nothing is deployed. Use cipp_list_tenants to get defaultDomainName and customerId values.

[CIPP] Assign an existing Intune application to users, groups, or devices in a tenant via POST /api/ExecAssignApp. Use cipp_list_apps to find the app's 'ID'. tenantFilter is sent on BOTH the query string AND inside the body; the same fields ('AppType', 'AssignTo', 'GroupIds', 'GroupNames', 'ID', 'Intent') are also accepted as query parameters — every OTHER key this endpoint reads is BODY-ONLY. Three defaults and gates decide what this call actually does. (1) 'assignmentMode' is a CLOSED two-value vocabulary that decides whether existing assignments survive: absent or whitespace means 'append' (the new target is ADDED, existing assignments preserved), while 'replace' WIPES every existing assignment for the app. (2) 'Intent' DEFAULTS to 'Required' when absent or whitespace, so omitting it INSTALLS the app rather than merely offering it. (3) At least one target is mandatory — with no AssignTo, GroupIds, GroupNames or exclude group, upstream throws. Both that throw and the one for an unsupported 'assignmentMode' happen BEFORE the endpoint's only try/catch, so CIPP's request dispatcher catches them instead and answers HTTP 500 whose body is the RAW exception text — NOT the {"Results":...} envelope that the success path and the endpoint's own catch (a Graph-side failure) both use. StackJack forwards that text verbatim, and it names the exact fix: "Unsupported AssignmentMode value '<x>'. Valid options are 'replace' or 'append'." or "No assignment target provided. Supply AssignTo, GroupNames, GroupIds, or an exclude group." On a 500 from this tool, READ the upstream text before treating it as a transient fault to retry.

ParamTypeRequiredDefaultDescription
fieldsJsonstringyesAssignment configuration as a JSON object. CIPP body keys (exact spec casing): 'ID' (string — Intune app id), 'AppType' (string — e.g. 'WinGet', 'Win32', 'Office365'; when omitted CIPP tries to resolve it from Graph and continues without assignment settings if that lookup fails), 'AssignTo' (string — assignment target group, e.g. 'AllDevices', 'AllUsers', or a custom group name), 'Intent' (string — typically 'available' | 'required' | 'uninstall'; ABSENT OR WHITESPACE DEFAULTS TO 'Required', so omitting it installs the app rather than offering it), 'GroupIds' (an ARRAY of AzureAD group object IDs, or a comma-separated string — upstream accepts either), 'GroupNames' (an ARRAY of AzureAD group display names, or a comma-separated string), 'AssignmentFilterName' (string — Intune assignment filter name; BODY ONLY, not query-forwardable), 'AssignmentFilterType' (string — 'include' or 'exclude'; BODY ONLY), 'assignmentMode' (string, camelCase, BODY ONLY — a CLOSED vocabulary of 'append' | 'replace', lowercased before the check so any casing works. DEFAULT when absent or whitespace is 'append': the new target is ADDED and existing assignments survive. 'replace' WIPES every existing assignment for the app. Any other value throws OUTSIDE the endpoint's try/catch and comes back from CIPP's dispatcher as HTTP 500 whose body is the raw exception text "Unsupported AssignmentMode value '<x>'. Valid options are 'replace' or 'append'." instead of the usual {"Results":...} envelope — read that text, it names the fix), 'excludeGroup' (string — a single group excluded from the assignment; BODY ONLY), 'ExcludeGroupIds' (an ARRAY of group object IDs, or a comma-separated string, to exclude; BODY ONLY), 'ExcludeGroupNames' (an ARRAY of group display names, or a comma-separated string, to exclude; BODY ONLY), 'assignmentDirection' (string 'include' | 'exclude'; BODY ONLY — any other value is silently treated as absent). At least one of AssignTo / GroupIds / GroupNames / an exclude group is MANDATORY: with none of them upstream throws before its try/catch and the call comes back as HTTP 500 carrying the raw text 'No assignment target provided. Supply AssignTo, GroupNames, GroupIds, or an exclude group.' rather than the usual envelope. One override to know: an exclude-only request (no AssignTo, GroupIds or GroupNames) sent with 'replace' and no 'assignmentDirection' is silently forced back to 'append' so it cannot wipe the includes.
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Sent on the query string as tenantFilter and also in the body as 'tenantFilter' (camelCase). Use cipp_list_tenants to discover available tenants.

[CIPP] Upload an application package to Intune. Triggers the upload and processing of a queued application.

[CIPP] List queued application deployments across tenants. Returns pending app installations, their status, and target tenants.

[CIPP] Search a Chocolatey-style (NuGet v2) package feed for deployable application definitions via POST /api/ListAppsRepository. Despite being a list/read endpoint this is a POST with a body. Upstream reads EXACTLY TWO body keys — 'Search' and 'Repository' — and ignores every other key. Returns at most the 30 latest-version packages matching the search term, each projected to {packagename, author, applicationName, version, description, customRepo, created}; feed the 'packagename' into cipp_add_choco_app. WARNING: the HTTP status is ALWAYS 200, even on failure — the outcome lives in the response body's 'IsError' (bool) and 'Message' fields. There are THREE IsError causes, not two: an empty search term short-circuits to IsError=true / Message='No search terms specified', an unreachable feed gives IsError=true / Message='Repository error: ...', and a REACHABLE feed that simply matched nothing gives IsError=true / Message='No results found' — so an IsError body is not automatically a transport failure worth retrying; read Message. A status-only caller sees success in all three cases. The envelope is {Search, Results, Message, IsError}; Results here really is an array (upstream wraps it in @()), empty when nothing matched.

ParamTypeRequiredDefaultDescription
fieldsJsonstringnonullReserved escape hatch for forward compatibility, merged into the body AFTER the typed parameters above (so a key here overrides one seeded above). Upstream at this pin reads ONLY 'Search' and 'Repository' — every other key you put here is silently ignored, including the app-deployment keys (AcceptLicense, AssignTo, DisableRestart, InstallAsSystem, packagename, publisher, rmmname, excludedApps, …) that earlier versions of this description wrongly advertised. Pass '' or omit.
repositorystringnonullOptional package feed base URL, sent as the body key 'Repository'. Defaults upstream to 'https://chocolatey.org/api/v2' when absent or empty. Must be a NuGet v2 OData feed root — upstream appends "/Search()?$filter=IsLatestVersion&$skip=0&$top=30&searchTerm='<search>'&targetFramework=''&includePrerelease=false". A bad URL surfaces as IsError=true with Message='Repository error: ...' inside an HTTP 200.
searchstringyesPackage search term — REQUIRED, sent as the body key 'Search'. Upstream builds a NuGet v2 Search() query with $filter=IsLatestVersion, $top=30 and searchTerm='<this value>'. An empty or whitespace term is not an HTTP error: CIPP returns 200 with IsError=true and Message='No search terms specified' and zero results.

[CIPP] Search a public package source for deployable applications via POST /api/ListPotentialApps. Despite being a list/read endpoint this is a POST with a body. Upstream reads EXACTLY TWO body keys — 'type' and 'SearchString' — and ignores every other key (there is no 'searchQuery' key; earlier descriptions invented it). 'type' selects the source and is effectively REQUIRED: WinGet queries Microsoft's storeedgefd manifestSearch (max 50 results, Market=US, Substring match) and Choco queries community.chocolatey.org (top 999, latest version only). Each result is projected to {applicationName, packagename}, sorted by applicationName; feed 'packagename' into cipp_add_store_app (WinGet) or cipp_add_choco_app (Choco). WARNING: upstream has NO else branch and NO validation — a type outside those two values, or an omitted type, matches neither branch and the endpoint returns HTTP 200 with an EMPTY array, indistinguishable from 'no matches'. This tool refuses such a call up front rather than let it look like an empty search.

ParamTypeRequiredDefaultDescription
fieldsJsonstringnonullReserved escape hatch for forward compatibility, merged into the body AFTER the typed parameters above (so a key here overrides one seeded above). Upstream at this pin reads ONLY 'type' and 'SearchString'; every other key — including the ~30 app-deployment keys earlier versions of this description listed — is silently ignored. Pass '' or omit.
searchStringstringyesSearch term — REQUIRED, sent as the body key 'SearchString' (that exact casing). For WinGet it becomes the manifestSearch Query KeyWord with MatchType=Substring; for Choco it is interpolated into the feed's searchTerm. Note the key is 'SearchString' — a body key named 'searchQuery' is never read by upstream.
typestringyesPackage source — REQUIRED, sent as the body key 'type'. Accepted values: 'WinGet' or 'Choco' (matched case-insensitively and normalized to that exact casing before sending). Any other value is refused here, because upstream would silently answer HTTP 200 with an empty array instead of an error.

[CIPP] Permanently remove an Intune-managed application from a tenant via POST /api/RemoveApp. Destructive: the app object is DELETED from Intune, so its assignments to users and devices go with it — CIPP issues one Graph DELETE against the mobileApps object and does NOT send a separate unassign call. Use cipp_list_apps to find the 'ID'. The same fields ('ID', 'tenantFilter') are accepted as both query parameters AND body fields.

ParamTypeRequiredDefaultDescription
fieldsJsonstringyesApp identification as a JSON object. CIPP body keys (exact spec casing): 'ID' (string, REQUIRED — Intune app id from cipp_list_apps. Missing on BOTH the query string and the body, upstream hits an unconditional early exit and returns no response object at all. That is an `exit`, not a throw, so CIPP's dispatcher never composes an error body for it either — whatever the Functions host makes of a response-less invocation, you will NOT get the structured error the other tools here promise. Always supply ID), 'tenantFilter' (string — already added by the tool, but listed here for completeness).
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Sent both on the query string as tenantFilter and in the body as 'tenantFilter' (camelCase). Use cipp_list_tenants to discover available tenants.

[CIPP] Cancel a pending deployment in the CIPP application queue via POST /api/RemoveQueuedApp before it processes. Use cipp_list_application_queue to find the queue item id. Note: this is a CIPP-internal queue item, not an Intune app — for already-deployed apps use cipp_remove_app instead.

ParamTypeRequiredDefaultDescription
fieldsJsonstringyesQueue item identification as a JSON object. CIPP body keys (exact spec casing): 'ID' (string, REQUIRED — CIPP queue item id from cipp_list_application_queue). The spec defines no other body fields.

[CIPP] Trigger a sync of Apple Volume Purchase Program (VPP) tokens for a tenant via POST /api/ExecSyncVPP. Refreshes Intune's view of VPP-purchased app licenses and reapplies pending assignments. The body has only ONE field: 'tenantFilter*' (camelCase, required). No other body fields are defined by the spec.

ParamTypeRequiredDefaultDescription
fieldsJsonstringyesReserved for forward compatibility. The spec defines no extra body fields beyond 'tenantFilter'. Pass an empty object '' or use this only if a future CIPP version adds VPP sync options. Keys are passed verbatim — caller is responsible for exact spec casing.
tenantFilterstringyesTarget tenant domain (e.g., contoso.onmicrosoft.com). Sent both on the query string as tenantFilter and in the body as 'tenantFilter' (camelCase). Use cipp_list_tenants to discover available tenants.