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 — 25 tools
- Users — 14 tools
- User Management — 42 tools
- Groups — 12 tools
- Mailboxes — 18 tools
- Mailbox Management — 30 tools
- Mailbox Retention — 3 tools
- Contacts & Resources — 18 tools
- Transport & Spam — 39 tools
- Devices — 20 tools
- Device Management — 34 tools
- Security — 15 tools
- Conditional Access — 9 tools
- Safe Links — 11 tools
- Teams & SharePoint — 17 tools
- Standards — 19 tools
- Audit — 12 tools
- GDAP — 14 tools
- Scheduler — 5 tools
- Utility — 13 tools
- Diagnostics — 34 tools
- Analytics — 14 tools
- Application Approvals — 8 tools
- Applications — 13 tools
Tenants
cipp_add_domain details
cipp_add_domain details
[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.
cipp_add_spn details
cipp_add_spn details
[CIPP] Add CIPP service principal permissions to partner tenant. Required for CIPP API access.
cipp_add_tenant details
cipp_add_tenant details
[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.
cipp_clear_tenant_cache details
cipp_clear_tenant_cache details
[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.
cipp_delete_domain_action details
cipp_delete_domain_action details
[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.
cipp_edit_tenant details
cipp_edit_tenant details
[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.
cipp_edit_tenant_offboarding_defaults details
cipp_edit_tenant_offboarding_defaults details
[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.
cipp_exec_exclude_licenses details
cipp_exec_exclude_licenses details
[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.
cipp_get_organization details
cipp_get_organization details
cipp_get_tenant_details details
cipp_get_tenant_details details
cipp_list_app_consent_requests details
cipp_list_app_consent_requests details
cipp_list_csp_licenses details
cipp_list_csp_licenses details
cipp_list_domains details
cipp_list_domains details
cipp_list_excluded_licenses details
cipp_list_excluded_licenses details
[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_list_external_tenant_info details
cipp_list_external_tenant_info details
[CIPP] Look up external tenant information by domain or tenant ID. Returns organization name, tenant ID, and federation status.
cipp_list_licenses details
cipp_list_licenses details
[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.
cipp_list_oauth_apps details
cipp_list_oauth_apps details
[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.
cipp_list_service_health details
cipp_list_service_health details
[CIPP] List current M365 service health status for a tenant. Returns service name, status, and any active incidents or advisories.
cipp_list_tenant_alignment details
cipp_list_tenant_alignment details
[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.
cipp_list_tenant_onboarding details
cipp_list_tenant_onboarding details
[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.
cipp_list_tenants details
cipp_list_tenants details
[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.
cipp_onboard_tenant details
cipp_onboard_tenant details
[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.
cipp_remove_tenant_capabilities_cache details
cipp_remove_tenant_capabilities_cache details
[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.
cipp_send_org_message details
cipp_send_org_message details
[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.
cipp_set_auth_method details
cipp_set_auth_method details
[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.
Users
cipp_bec_check details
cipp_bec_check details
[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.
cipp_get_user_ca_policies details
cipp_get_user_ca_policies details
[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.
cipp_get_user_devices details
cipp_get_user_devices details
cipp_get_user_groups details
cipp_get_user_groups details
cipp_get_user_mailbox details
cipp_get_user_mailbox details
cipp_get_user_mfa details
cipp_get_user_mfa details
cipp_get_user_photo details
cipp_get_user_photo details
[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.
cipp_get_user_signin_logs details
cipp_get_user_signin_logs details
cipp_list_basic_auth_users details
cipp_list_basic_auth_users details
[CIPP] List users who have basic authentication (legacy auth) enabled. These accounts are security risks and should be migrated to modern auth.
cipp_list_deleted_users details
cipp_list_deleted_users details
[CIPP] List soft-deleted users in the tenant recycle bin. These users can be restored within 30 days using cipp_restore_deleted_user.
cipp_list_inactive_accounts details
cipp_list_inactive_accounts details
[CIPP] List user accounts that have not signed in recently. Returns last sign-in date and account details for identifying stale accounts.
cipp_list_mfa_users details
cipp_list_mfa_users details
[CIPP] List all users with their MFA registration status and methods. Useful for identifying users without MFA configured.
cipp_list_user_counts details
cipp_list_user_counts details
cipp_list_users details
cipp_list_users details
[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.
User Management
cipp_add_guest details
cipp_add_guest details
[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`.
cipp_add_jit_admin_template details
cipp_add_jit_admin_template details
[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.
cipp_add_user details
cipp_add_user details
[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.
cipp_add_user_bulk details
cipp_add_user_bulk details
[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.
cipp_add_user_defaults details
cipp_add_user_defaults details
[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}.
cipp_bec_remediate details
cipp_bec_remediate details
[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.
cipp_bulk_license details
cipp_bulk_license details
[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.
cipp_clear_immutable_id details
cipp_clear_immutable_id details
[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.
cipp_create_tap details
cipp_create_tap details
[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.
cipp_device_delete_identity details
cipp_device_delete_identity details
[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.
cipp_disable_user details
cipp_disable_user details
[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.
cipp_dismiss_risky_user details
cipp_dismiss_risky_user details
[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).
cipp_edit_jit_admin_template details
cipp_edit_jit_admin_template details
[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.
cipp_edit_user details
cipp_edit_user details
[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).
cipp_edit_user_aliases details
cipp_edit_user_aliases details
[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.
cipp_jit_admin details
cipp_jit_admin details
[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.
cipp_license_search details
cipp_license_search details
[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).
cipp_list_jit_admin details
cipp_list_jit_admin details
[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.
cipp_list_jit_admin_templates details
cipp_list_jit_admin_templates details
[CIPP] List available Just-In-Time admin templates. Returns template names, roles, and default durations.
cipp_list_new_user_defaults details
cipp_list_new_user_defaults details
[CIPP] List saved new user creation default templates. Returns template names, assigned licenses, groups, and profile settings.
cipp_list_user_trusted_blocked_senders details
cipp_list_user_trusted_blocked_senders details
[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.
cipp_offboard_user details
cipp_offboard_user details
[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.
cipp_offboarding_job_status details
cipp_offboarding_job_status details
[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_onedrive_provision details
cipp_onedrive_provision details
[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.
cipp_onedrive_shortcut details
cipp_onedrive_shortcut details
[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.
cipp_password_never_expires details
cipp_password_never_expires details
[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.
cipp_patch_user details
cipp_patch_user details
[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.
cipp_per_user_mfa details
cipp_per_user_mfa details
[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.
cipp_remove_deleted_object details
cipp_remove_deleted_object details
[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.
cipp_remove_jit_admin_template details
cipp_remove_jit_admin_template details
[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.
cipp_remove_trusted_blocked_sender details
cipp_remove_trusted_blocked_sender details
[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.
cipp_remove_user details
cipp_remove_user details
[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.
cipp_remove_user_default_template details
cipp_remove_user_default_template details
[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.
cipp_reprocess_user_licenses details
cipp_reprocess_user_licenses details
[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.
cipp_reset_mfa details
cipp_reset_mfa details
[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).
cipp_reset_password details
cipp_reset_password details
[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.
cipp_restore_deleted_user details
cipp_restore_deleted_user details
[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.
cipp_revoke_sessions details
cipp_revoke_sessions details
[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.
cipp_send_push details
cipp_send_push details
[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.
cipp_set_cloud_managed details
cipp_set_cloud_managed details
[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.
cipp_set_user_photo details
cipp_set_user_photo details
[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.
Groups
cipp_add_group details
cipp_add_group details
[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.
cipp_add_group_template details
cipp_add_group_template details
[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.
cipp_convert_group_to_team details
cipp_convert_group_to_team details
[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.
cipp_delete_group details
cipp_delete_group details
[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).
cipp_edit_group details
cipp_edit_group details
[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.
cipp_group_delivery_management details
cipp_group_delivery_management details
[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).
cipp_group_hide_from_gal details
cipp_group_hide_from_gal details
[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).
cipp_list_group_sender_auth details
cipp_list_group_sender_auth details
[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.
cipp_list_group_templates details
cipp_list_group_templates details
[CIPP] List saved group templates. Returns template names, group types, and configured settings.
cipp_list_groups details
cipp_list_groups details
[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.
cipp_list_roles details
cipp_list_roles details
[CIPP] List all directory roles in a tenant including role name, description, and assigned members. Useful for auditing admin access.
cipp_remove_group_template details
cipp_remove_group_template details
[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.
Mailboxes
cipp_exec_mail_test details
cipp_exec_mail_test details
[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.
cipp_get_calendar_permissions details
cipp_get_calendar_permissions details
cipp_get_contact_permissions details
cipp_get_contact_permissions details
cipp_get_mailbox_cas details
cipp_get_mailbox_cas details
cipp_get_mailbox_mobile_devices details
cipp_get_mailbox_mobile_devices details
[CIPP] Get mobile devices connected to a specific mailbox via ActiveSync or Outlook Mobile. Returns device name, OS, last sync time, and access state.
cipp_get_mailbox_permissions details
cipp_get_mailbox_permissions details
cipp_get_mailbox_rules details
cipp_get_mailbox_rules details
[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.
cipp_get_ooo details
cipp_get_ooo details
cipp_list_exo_request details
cipp_list_exo_request details
[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.
cipp_list_global_address_list details
cipp_list_global_address_list details
[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.
cipp_list_mailbox_forwarding details
cipp_list_mailbox_forwarding details
[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).
cipp_list_mailbox_restores details
cipp_list_mailbox_restores details
[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.
cipp_list_mailboxes details
cipp_list_mailboxes details
[CIPP] List all mailboxes in a tenant including user, shared, and resource mailboxes. Returns display name, primary SMTP address, mailbox type, and size.
cipp_list_quarantine_message details
cipp_list_quarantine_message details
[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.
cipp_list_restricted_users details
cipp_list_restricted_users details
[CIPP] List users who have been restricted from sending email due to suspected spam or compromise. Use cipp_remove_restricted_user to unblock.
cipp_list_shared_mailbox_account_enabled details
cipp_list_shared_mailbox_account_enabled details
cipp_list_shared_mailbox_stats details
cipp_list_shared_mailbox_stats details
cipp_list_user_mailbox_rules details
cipp_list_user_mailbox_rules details
[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.
Mailbox Management
cipp_add_shared_mailbox details
cipp_add_shared_mailbox details
cipp_convert_mailbox details
cipp_convert_mailbox details
[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'.
cipp_copy_for_sent details
cipp_copy_for_sent details
[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.
cipp_edit_calendar_permissions details
cipp_edit_calendar_permissions details
[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.
cipp_edit_mailbox_permissions details
cipp_edit_mailbox_permissions details
[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.
cipp_enable_archive details
cipp_enable_archive details
[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.
cipp_enable_auto_expanding_archive details
cipp_enable_auto_expanding_archive details
[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).
cipp_exec_mailbox_mobile_devices details
cipp_exec_mailbox_mobile_devices details
[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.
cipp_hide_from_gal details
cipp_hide_from_gal details
[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.
cipp_hve_user details
cipp_hve_user details
[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.
cipp_mailbox_restore details
cipp_mailbox_restore details
[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.
cipp_message_trace details
cipp_message_trace details
[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').
cipp_modify_calendar_perms details
cipp_modify_calendar_perms details
[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).
cipp_modify_contact_perms details
cipp_modify_contact_perms details
[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).
cipp_modify_mailbox_perms details
cipp_modify_mailbox_perms details
[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.
cipp_remove_mailbox_rule details
cipp_remove_mailbox_rule details
[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.
cipp_remove_restricted_user details
cipp_remove_restricted_user details
[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.
cipp_schedule_mailbox_vacation details
cipp_schedule_mailbox_vacation details
[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.
cipp_schedule_ooo_vacation details
cipp_schedule_ooo_vacation details
[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.
cipp_set_calendar_processing details
cipp_set_calendar_processing details
[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.
cipp_set_email_forward details
cipp_set_email_forward details
[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.
cipp_set_litigation_hold details
cipp_set_litigation_hold details
[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.
cipp_set_mailbox_email_size details
cipp_set_mailbox_email_size details
[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').
cipp_set_mailbox_locale details
cipp_set_mailbox_locale details
cipp_set_mailbox_quota details
cipp_set_mailbox_quota details
[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.
cipp_set_mailbox_rule details
cipp_set_mailbox_rule details
[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.
cipp_set_ooo details
cipp_set_ooo details
[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.
cipp_set_recipient_limits details
cipp_set_recipient_limits details
[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.
cipp_set_retention_hold details
cipp_set_retention_hold details
[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.
cipp_start_managed_folder_assistant details
cipp_start_managed_folder_assistant details
[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.
Mailbox Retention
cipp_delete_retention_policies details
cipp_delete_retention_policies details
[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.
cipp_delete_retention_tags details
cipp_delete_retention_tags details
[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.
cipp_set_mailbox_retention_policies details
cipp_set_mailbox_retention_policies details
[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.
Contacts & Resources
cipp_add_contact details
cipp_add_contact details
[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.
cipp_add_contact_template details
cipp_add_contact_template details
[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.
cipp_add_equipment_mailbox details
cipp_add_equipment_mailbox details
[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.
cipp_add_room_list details
cipp_add_room_list details
[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.
cipp_add_room_mailbox details
cipp_add_room_mailbox details
[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.
cipp_deploy_contact_templates details
cipp_deploy_contact_templates details
[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.
cipp_edit_contact details
cipp_edit_contact details
[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.
cipp_edit_contact_template details
cipp_edit_contact_template details
[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.
cipp_edit_equipment_mailbox details
cipp_edit_equipment_mailbox details
[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.
cipp_edit_room_list details
cipp_edit_room_list details
[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.
cipp_edit_room_mailbox details
cipp_edit_room_mailbox details
[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.
cipp_list_contact_templates details
cipp_list_contact_templates details
[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_contacts details
cipp_list_contacts details
[CIPP] List all mail contacts in a tenant. Returns display name, email address, and contact type for external contacts in the directory.
cipp_list_equipment details
cipp_list_equipment details
[CIPP] List all equipment mailboxes in a tenant. Equipment mailboxes represent bookable resources like projectors, vehicles, or conference phones.
cipp_list_room_lists details
cipp_list_room_lists details
[CIPP] List room lists (groups of rooms) in a tenant. Room lists organize meeting rooms by floor, building, or location.
cipp_list_rooms details
cipp_list_rooms details
[CIPP] List all room mailboxes in a tenant. Returns room name, email address, capacity, and booking settings.
cipp_remove_contact details
cipp_remove_contact details
[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.
cipp_remove_contact_template details
cipp_remove_contact_template details
[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.
Transport & Spam
cipp_add_connection_filter details
cipp_add_connection_filter details
[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.
cipp_add_connection_filter_template details
cipp_add_connection_filter_template details
[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.
cipp_add_edit_transport_rule details
cipp_add_edit_transport_rule details
[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.
cipp_add_ex_connector_template details
cipp_add_ex_connector_template details
[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.
cipp_add_exchange_connector details
cipp_add_exchange_connector details
[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.
cipp_add_quarantine_policy details
cipp_add_quarantine_policy details
[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.
cipp_add_spam_filter details
cipp_add_spam_filter details
[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.
cipp_add_spam_filter_template details
cipp_add_spam_filter_template details
[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.
cipp_add_tenant_allow_block details
cipp_add_tenant_allow_block details
[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.
cipp_add_transport_rule details
cipp_add_transport_rule details
[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.
cipp_add_transport_rule_template details
cipp_add_transport_rule_template details
[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.
cipp_edit_anti_phishing_filter details
cipp_edit_anti_phishing_filter details
[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.
cipp_edit_exchange_connector details
cipp_edit_exchange_connector details
[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.
cipp_edit_malware_filter details
cipp_edit_malware_filter details
[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.
cipp_edit_quarantine_policy details
cipp_edit_quarantine_policy details
[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.
cipp_edit_safe_attachments_filter details
cipp_edit_safe_attachments_filter details
[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.
cipp_edit_spam_filter details
cipp_edit_spam_filter details
[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.
cipp_edit_transport_rule details
cipp_edit_transport_rule details
[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.
cipp_list_connection_filter_templates details
cipp_list_connection_filter_templates details
[CIPP] List saved connection filter policy templates. Returns template names and configured IP allow/block lists for reuse.
cipp_list_connection_filters details
cipp_list_connection_filters details
[CIPP] List connection filter policies for a tenant. Connection filters control which IP addresses are allowed or blocked from sending email to the tenant.
cipp_list_ex_connector_templates details
cipp_list_ex_connector_templates details
[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.
cipp_list_exchange_connectors details
cipp_list_exchange_connectors details
cipp_list_quarantine details
cipp_list_quarantine details
[CIPP] List quarantined email messages for a tenant. Returns message subject, sender, recipient, quarantine reason, and received date.
cipp_list_quarantine_policy details
cipp_list_quarantine_policy details
[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.
cipp_list_spam_filter_templates details
cipp_list_spam_filter_templates details
[CIPP] List saved spam filter policy templates. Returns template names and configured settings for reuse across tenants.
cipp_list_spam_filters details
cipp_list_spam_filters details
cipp_list_tenant_allow_block details
cipp_list_tenant_allow_block details
[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.
cipp_list_transport_rules details
cipp_list_transport_rules details
[CIPP] List all Exchange transport rules (mail flow rules) for a tenant. Returns rule name, priority, state, conditions, and actions.
cipp_list_transport_rules_templates details
cipp_list_transport_rules_templates details
[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_quarantine details
cipp_manage_quarantine details
[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.
cipp_remove_connection_filter_template details
cipp_remove_connection_filter_template details
[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.
cipp_remove_ex_connector_template details
cipp_remove_ex_connector_template details
[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.
cipp_remove_exchange_connector details
cipp_remove_exchange_connector details
[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.
cipp_remove_quarantine_policy details
cipp_remove_quarantine_policy details
[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.
cipp_remove_spam_filter details
cipp_remove_spam_filter details
[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.
cipp_remove_spam_filter_template details
cipp_remove_spam_filter_template details
[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.
cipp_remove_tenant_allow_block details
cipp_remove_tenant_allow_block details
[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.
cipp_remove_transport_rule details
cipp_remove_transport_rule details
[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.
cipp_remove_transport_rule_template details
cipp_remove_transport_rule_template details
[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.
Devices
cipp_get_device_details details
cipp_get_device_details details
[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.
cipp_list_app_protection details
cipp_list_app_protection details
[CIPP] List Intune app protection policies (MAM) for a tenant. Returns policy names, platform, settings, and targeted apps.
cipp_list_app_status details
cipp_list_app_status details
[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.
cipp_list_apps details
cipp_list_apps details
[CIPP] List Intune-managed applications for a tenant. Returns app names, types, assignment status, and install counts.
cipp_list_assignment_filter_templates details
cipp_list_assignment_filter_templates details
[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_assignment_filters details
cipp_list_assignment_filters details
[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.
cipp_list_autopilot_configs details
cipp_list_autopilot_configs details
cipp_list_autopilot_devices details
cipp_list_autopilot_devices details
[CIPP] List all Windows Autopilot registered devices for a tenant. Returns serial number, model, group tag, and enrollment profile.
cipp_list_compliance_policies details
cipp_list_compliance_policies details
[CIPP] List Intune device compliance policies for a tenant. Returns policy names, settings, and assignment targets.
cipp_list_defender_state details
cipp_list_defender_state details
[CIPP] List Microsoft Defender for Endpoint device status for a tenant. Returns protection state, engine version, and last scan time.
cipp_list_defender_tvm details
cipp_list_defender_tvm details
[CIPP] List Microsoft Defender Threat & Vulnerability Management data for a tenant. Returns vulnerability scores, recommendations, and exposed devices.
cipp_list_detected_app_devices details
cipp_list_detected_app_devices details
[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.
cipp_list_detected_apps details
cipp_list_detected_apps details
[CIPP] List applications detected on Intune-managed devices for a tenant. Returns app names, versions, and device counts.
cipp_list_devices details
cipp_list_devices details
[CIPP] List all Intune-managed devices for a tenant. Returns device ID, name, OS, compliance state, and last check-in time.
cipp_list_intune_intents details
cipp_list_intune_intents details
[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.
cipp_list_intune_policies details
cipp_list_intune_policies details
[CIPP] List Intune device configuration policies for a tenant. Returns policy types, settings, and assignments.
cipp_list_intune_reusable_setting_templates details
cipp_list_intune_reusable_setting_templates details
[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_settings details
cipp_list_intune_reusable_settings details
[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.
cipp_list_intune_scripts details
cipp_list_intune_scripts details
cipp_list_intune_templates details
cipp_list_intune_templates details
[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
cipp_add_assignment_filter details
cipp_add_assignment_filter details
[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.
cipp_add_assignment_filter_template details
cipp_add_assignment_filter_template details
[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.
cipp_add_autopilot_config details
cipp_add_autopilot_config details
[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.
cipp_add_autopilot_device details
cipp_add_autopilot_device details
[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.
cipp_add_defender_deployment details
cipp_add_defender_deployment details
[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.
cipp_add_enrollment details
cipp_add_enrollment details
[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.
cipp_add_intune_reusable_setting details
cipp_add_intune_reusable_setting details
[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.
cipp_add_intune_reusable_setting_template details
cipp_add_intune_reusable_setting_template details
[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.
cipp_add_intune_template details
cipp_add_intune_template details
[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.
cipp_add_policy details
cipp_add_policy details
[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.
cipp_assign_autopilot_device details
cipp_assign_autopilot_device details
[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.
cipp_assign_policy details
cipp_assign_policy details
[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.
cipp_delete_assignment_filter details
cipp_delete_assignment_filter details
[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.
cipp_device_action details
cipp_device_action details
[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.
cipp_device_passcode_action details
cipp_device_passcode_action details
[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.
cipp_edit_assignment_filter details
cipp_edit_assignment_filter details
[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.
cipp_edit_intune_policy details
cipp_edit_intune_policy details
[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.
cipp_edit_intune_script details
cipp_edit_intune_script details
[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.
cipp_edit_policy details
cipp_edit_policy details
[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'.
cipp_exec_bitlocker_search details
cipp_exec_bitlocker_search details
[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.
cipp_get_local_admin_password details
cipp_get_local_admin_password details
[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.
cipp_get_recovery_key details
cipp_get_recovery_key details
[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.
cipp_remove_assignment_filter_template details
cipp_remove_assignment_filter_template details
[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.
cipp_remove_autopilot_config details
cipp_remove_autopilot_config details
[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.
cipp_remove_autopilot_device details
cipp_remove_autopilot_device details
[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.
cipp_remove_intune_reusable_setting details
cipp_remove_intune_reusable_setting details
[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.
cipp_remove_intune_reusable_setting_template details
cipp_remove_intune_reusable_setting_template details
[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.
cipp_remove_intune_script details
cipp_remove_intune_script details
[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.
cipp_remove_intune_template details
cipp_remove_intune_template details
[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.
cipp_remove_policy details
cipp_remove_policy details
[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.
cipp_rename_autopilot_device details
cipp_rename_autopilot_device details
[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.
cipp_set_autopilot_group_tag details
cipp_set_autopilot_group_tag details
[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.
cipp_sync_autopilot details
cipp_sync_autopilot details
[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) }.
cipp_sync_dep details
cipp_sync_dep details
[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.
Security
cipp_add_ca_policy details
cipp_add_ca_policy details
[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.
cipp_edit_ca_policy details
cipp_edit_ca_policy details
[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.
cipp_list_anti_phishing details
cipp_list_anti_phishing details
[CIPP] List anti-phishing filter policies for a tenant. Returns impersonation protection, spoof settings, and action configurations.
cipp_list_ca_changes details
cipp_list_ca_changes details
[CIPP] List recent changes to Conditional Access policies for a tenant. Returns modification history with timestamps and actors.
cipp_list_ca_policies details
cipp_list_ca_policies details
[CIPP] List all Conditional Access policies for a tenant. Returns policy names, state, conditions, and grant controls.
cipp_list_malware_filters details
cipp_list_malware_filters details
[CIPP] List malware filter policies for a tenant. Returns file type blocking, zero-hour purge, and notification settings.
cipp_list_mdo_alerts details
cipp_list_mdo_alerts details
[CIPP] List Microsoft Defender for Office 365 alerts for a tenant. Returns threat detection alerts for email and collaboration workloads.
cipp_list_named_locations details
cipp_list_named_locations details
cipp_list_safe_attachments details
cipp_list_safe_attachments details
[CIPP] List Safe Attachments policies for a tenant. Returns detonation settings, action on detection, and redirect configuration.
cipp_list_safe_links details
cipp_list_safe_links details
[CIPP] List Safe Links policies for a tenant. Returns policy names, URL scanning settings, and protection scope.
cipp_list_security_alerts details
cipp_list_security_alerts details
[CIPP] List Microsoft 365 security alerts for a tenant. Returns alert severity, status, category, and affected resources.
cipp_list_security_incidents details
cipp_list_security_incidents details
[CIPP] List Microsoft 365 security incidents for a tenant. Returns incident severity, status, classification, and linked alerts.
cipp_set_mdo_alert details
cipp_set_mdo_alert details
[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.
cipp_set_security_alert details
cipp_set_security_alert details
[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.
cipp_set_security_incident details
cipp_set_security_incident details
[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.
Conditional Access
cipp_add_ca_template details
cipp_add_ca_template details
[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.
cipp_add_named_location details
cipp_add_named_location details
[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.
cipp_exec_ca_check details
cipp_exec_ca_check details
[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.
cipp_exec_ca_exclusion details
cipp_exec_ca_exclusion details
[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.
cipp_exec_ca_service_exclusion details
cipp_exec_ca_service_exclusion details
[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.
cipp_exec_named_location details
cipp_exec_named_location details
[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.
cipp_list_ca_templates details
cipp_list_ca_templates details
[CIPP] List all Conditional Access policy templates available in CIPP. Returns template names, descriptions, and policy definitions.
cipp_remove_ca_policy details
cipp_remove_ca_policy details
[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.
cipp_remove_ca_template details
cipp_remove_ca_template details
[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.
Safe Links
cipp_add_safe_links_from_template details
cipp_add_safe_links_from_template details
[CIPP] Deploy one or more Safe Links policy templates to one or more tenants in bulk via POST /api/AddSafeLinksPolicyFromTemplate. CROSS-TENANT: the targets come from the body key 'selectedTenants', never 'tenantFilter' — pass them through the selectedTenants parameter, which builds the [{"value":"contoso.onmicrosoft.com"}] object shape CIPP actually projects .value off. WARNING: CIPP never validates the target list, so a wrong-shaped selectedTenants returns HTTP 200 with a null Results member ({"Results":null}, not an empty array) and deploys NOTHING — indistinguishable from success. StackJack therefore refuses that shape before dispatch. Templates are sent INLINE: this endpoint performs no template-store lookup, so a template ID does not work — read a template with cipp_list_safe_links_template_details and pass its full object as TemplateList[].value.
cipp_add_safe_links_template details
cipp_add_safe_links_template details
[CIPP] Create a new Safe Links policy template in the CIPP template store via POST /api/AddSafeLinksPolicyTemplate. CIPP-internal template store, not a tenant action — the endpoint reads no tenantFilter and captures nothing from a live tenant. REQUIRED body keys are 'Name' and 'PolicyName': CIPP hard-throws "Template name is required but was not provided" / "Policy name is required but was not provided" without them. WARNING: every failure on this endpoint answers HTTP 403 Forbidden, NOT a 400 — the endpoint's sole catch hard-sets Forbidden, and the missing-field message survives only in the body's Results string. A 403 here is far more likely a missing 'Name'/'PolicyName' than a permissions problem; read Results before touching credentials. It accepts the SAME full policy+rule property set as cipp_create_safe_links_template (the two kept-property lists are identical), so send the complete Safe Links settings here — a body carrying only naming fields stores a near-empty template that later deploys an empty policy. The row is written under a freshly generated GUID.
cipp_create_safe_links_policy details
cipp_create_safe_links_policy details
[CIPP] Create a new Defender for Office Safe Links policy + rule pair in a tenant via POST /api/ExecNewSafeLinksPolicy. Safe Links rewrites and time-of-click scans URLs in Outlook, Teams, and Office. tenantFilter is sent on BOTH the query string AND inside the body. CIPP creates both the policy and its associated rule from a single body.
cipp_create_safe_links_template details
cipp_create_safe_links_template details
[CIPP] Create a NEW Safe Links policy template in the CIPP template store from the fields in this request via POST /api/CreateSafeLinksPolicyTemplate. It does NOT capture anything from a live tenant: the endpoint is declared AnyTenant, reads no tenantFilter, and makes no Exchange or Graph call — CIPP's own description is "creates a new Safe Links policy template from scratch". Everything the template will contain must be supplied in the body; it is stored under a freshly generated GUID. WARNING: the endpoint validates nothing, so a body carrying only naming fields silently stores a near-empty template that later deploys an empty policy via cipp_add_safe_links_from_template. On the rare failure (a table-storage error — nothing in the body is validated, so nothing else can fail) CIPP answers HTTP 403 Forbidden with the reason in Results, not a 4xx validation code; a 403 from this endpoint is not necessarily a permissions problem. To base a template on a live tenant's policy, read it first with cipp_list_safe_links_details and pass those settings here yourself.
cipp_delete_safe_links_policy details
cipp_delete_safe_links_policy details
[CIPP] Permanently delete a Safe Links policy + rule pair from a tenant via POST /api/ExecDeleteSafeLinksPolicy. Destructive: removes both the rule and the underlying policy. Use cipp_list_safe_links to confirm the names before running. The same fields ('PolicyName', 'RuleName', 'tenantFilter') are accepted as BOTH query parameters and body fields.
cipp_edit_safe_links_policy details
cipp_edit_safe_links_policy details
[CIPP] Modify an existing Safe Links policy + rule pair via POST /api/EditSafeLinksPolicy. Use cipp_list_safe_links to find the existing policy name. tenantFilter, PolicyName, and RuleName are sent on BOTH the query string AND inside the body.
cipp_edit_safe_links_template details
cipp_edit_safe_links_template details
[CIPP] Replace the stored contents of an existing Safe Links policy template in CIPP via POST /api/EditSafeLinksPolicyTemplate. WARNING — this is a FULL REPLACE, not a partial patch: CIPP rebuilds the stored row from scratch out of THIS request body and re-saves it under the same RowKey with Force, so every property you omit is PERMANENTLY DROPPED from the template. 'TemplateName' and 'TemplateDescription' are assigned unconditionally, so omitting either nulls it out. Read the current template with cipp_list_safe_links_template_details first and resend every field you want to keep — sending only the one field you want to change destroys the rest. The existing row is read solely to verify the ID exists; nothing from it is merged. WARNING: a missing or non-matching ID does NOT come back as a 400 — every failure on this endpoint answers HTTP 403 Forbidden (the sole catch hard-sets it), with the reason ('Template ID is required' / "Template with ID '<id>' not found") only in the body's Results string. On an edit tool a wrong ID is the commonest failure there is, so read Results before assuming a permissions problem or rotating a key. This edits a CIPP-stored template, not a tenant policy — use cipp_edit_safe_links_policy for live tenant policies.
cipp_list_safe_links_details details
cipp_list_safe_links_details details
[CIPP] Get the full configuration of a specific Safe Links policy in a tenant via POST /api/ListSafeLinksPolicyDetails. Despite being a list/read endpoint, this is a POST that takes a body filter. The same fields ('PolicyName', 'RuleName', 'tenantFilter') are accepted as BOTH query parameters and body fields.
cipp_list_safe_links_template_details details
cipp_list_safe_links_template_details details
[CIPP] Get the full configuration of a specific Safe Links policy template stored in CIPP via POST /api/ListSafeLinksPolicyTemplateDetails. Despite being a list/read endpoint, this is a POST that takes a body. Use cipp_list_safe_links_templates to discover template IDs. Not tenant-scoped.
cipp_list_safe_links_templates details
cipp_list_safe_links_templates details
[CIPP] List all Safe Links policy templates available in CIPP. Returns template names, descriptions, and policy definitions.
cipp_remove_safe_links_template details
cipp_remove_safe_links_template details
[CIPP] Permanently delete a Safe Links policy template from the CIPP template store via POST /api/RemoveSafeLinksPolicyTemplate. Destructive: removes the template only — tenant policies created from it remain intact. Use cipp_list_safe_links_templates to find the template id. Not tenant-scoped.
Teams & SharePoint
cipp_add_site details
cipp_add_site details
[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.
cipp_add_site_bulk details
cipp_add_site_bulk details
[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.
cipp_add_team details
cipp_add_team details
[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.
cipp_assign_teams_voice_number details
cipp_assign_teams_voice_number details
[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.
cipp_delete_sharepoint_site details
cipp_delete_sharepoint_site details
cipp_get_sharepoint_quota details
cipp_get_sharepoint_quota details
cipp_get_sharepoint_settings details
cipp_get_sharepoint_settings details
cipp_list_sharepoint_admin_url details
cipp_list_sharepoint_admin_url details
cipp_list_site_members details
cipp_list_site_members details
[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.
cipp_list_sites details
cipp_list_sites details
[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.
cipp_list_teams details
cipp_list_teams details
[CIPP] List all Microsoft Teams for a tenant. Returns team display name, visibility, member count, and archive status.
cipp_list_teams_activity details
cipp_list_teams_activity details
[CIPP] List Microsoft Teams activity reports for a tenant. Returns usage metrics including messages, calls, and meetings per team.
cipp_list_teams_lis_location details
cipp_list_teams_lis_location details
[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.
cipp_list_teams_voice details
cipp_list_teams_voice details
[CIPP] List Microsoft Teams voice and telephony configuration for a tenant. Returns calling plans, phone numbers, and voice policies.
cipp_remove_teams_voice_number details
cipp_remove_teams_voice_number details
[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).
cipp_set_sharepoint_member details
cipp_set_sharepoint_member details
cipp_set_sharepoint_permissions details
cipp_set_sharepoint_permissions details
Standards
cipp_add_bpa_template details
cipp_add_bpa_template details
[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.
cipp_add_standards_template details
cipp_add_standards_template details
[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.
cipp_deploy_standards details
cipp_deploy_standards details
[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.
cipp_drift_clone details
cipp_drift_clone details
[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'.
cipp_list_bpa details
cipp_list_bpa details
[CIPP] List Best Practice Analyzer results for a tenant. Returns pass/fail status for security, identity, and configuration checks.
cipp_list_bpa_templates details
cipp_list_bpa_templates details
[CIPP] List saved Best Practice Analyzer templates. Returns template names and configured checks.
cipp_list_domain_analyser details
cipp_list_domain_analyser details
[CIPP] Run detailed domain analysis for a tenant. Returns comprehensive DNS configuration, email authentication, and security posture.
cipp_list_domain_health details
cipp_list_domain_health details
[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.
cipp_list_standard_templates details
cipp_list_standard_templates details
[CIPP] List saved CIPP standard templates. Returns template names and configured security/configuration baselines for reuse across tenants.
cipp_list_standards details
cipp_list_standards details
[CIPP] List deployed CIPP standards for a tenant. Returns standard names, applied settings, and compliance state.
cipp_list_standards_compare details
cipp_list_standards_compare details
[CIPP] Compare a tenant's current configuration against the CIPP standards template. Returns differences and compliance gaps.
cipp_list_tenant_drift details
cipp_list_tenant_drift details
[CIPP] Detect configuration drift for a tenant. Returns settings that have changed from the deployed standard baseline.
cipp_remove_bpa_template details
cipp_remove_bpa_template details
[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.
cipp_remove_standard details
cipp_remove_standard details
[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'.
cipp_remove_standard_template details
cipp_remove_standard_template details
[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.
cipp_run_bpa details
cipp_run_bpa details
[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.
cipp_standard_convert details
cipp_standard_convert details
[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_standards_run details
cipp_standards_run details
[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.
cipp_update_drift_deviation details
cipp_update_drift_deviation details
[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.
Audit
cipp_add_alert details
cipp_add_alert details
[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.
cipp_exec_add_alert details
cipp_exec_add_alert details
[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'.
cipp_list_alerts details
cipp_list_alerts details
[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.
cipp_list_audit_log_searches details
cipp_list_audit_log_searches details
[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'.
cipp_list_audit_log_test details
cipp_list_audit_log_test details
[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_audit_logs details
cipp_list_audit_logs details
[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.
cipp_list_logs details
cipp_list_logs details
[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').
cipp_list_pending_webhooks details
cipp_list_pending_webhooks details
[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_signin_logs details
cipp_list_signin_logs details
[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.
cipp_list_webhook_alerts details
cipp_list_webhook_alerts details
[CIPP] List configured webhook alert subscriptions. Returns webhook URLs, event types, and enabled status.
cipp_remove_queued_alert details
cipp_remove_queued_alert details
[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.
cipp_search_audit_logs details
cipp_search_audit_logs details
[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).
GDAP
cipp_add_gdap_role details
cipp_add_gdap_role details
[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.
cipp_approve_gdap_invite details
cipp_approve_gdap_invite details
[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_gdap details
cipp_auto_extend_gdap details
[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.
cipp_delete_gdap_invite details
cipp_delete_gdap_invite details
[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.
cipp_delete_gdap_relationship details
cipp_delete_gdap_relationship details
[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.
cipp_delete_gdap_role_mapping details
cipp_delete_gdap_role_mapping details
[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.
cipp_delete_gdap_role_template details
cipp_delete_gdap_role_template details
[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>').
cipp_list_gdap_access details
cipp_list_gdap_access details
[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.
cipp_list_gdap_invites details
cipp_list_gdap_invites details
[CIPP] List pending GDAP relationship invitations across your partner tenant. Returns invite status, customer details, and requested roles.
cipp_list_gdap_relationships details
cipp_list_gdap_relationships details
[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.
cipp_list_gdap_roles details
cipp_list_gdap_roles details
[CIPP] List all GDAP (Granular Delegated Admin Privileges) roles available in your partner tenant. Returns role names, IDs, and descriptions.
cipp_list_partner_relationships details
cipp_list_partner_relationships details
[CIPP] List partner relationships (DAP/GDAP) for a specific tenant. Returns relationship type, status, and delegated permissions.
cipp_patch_gdap_access_assignment details
cipp_patch_gdap_access_assignment details
[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.
cipp_remove_gdap_ga_role details
cipp_remove_gdap_ga_role details
[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.
Scheduler
cipp_add_scheduled_item details
cipp_add_scheduled_item details
[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.
cipp_list_scheduled_item_details details
cipp_list_scheduled_item_details details
[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.
cipp_list_scheduled_items details
cipp_list_scheduled_items details
[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_scheduled_item details
cipp_remove_scheduled_item details
[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.
cipp_run_scheduler_billing details
cipp_run_scheduler_billing details
[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
cipp_breach_search details
cipp_breach_search details
[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.
cipp_geoip_lookup details
cipp_geoip_lookup details
[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.
cipp_get_alerts details
cipp_get_alerts details
[CIPP] Get current CIPP system alerts and notifications. Returns alert messages, severity, and recommended actions.
cipp_get_queue_status details
cipp_get_queue_status details
[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.
cipp_graph_request details
cipp_graph_request details
[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.
cipp_list_breaches_account details
cipp_list_breaches_account details
[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.
cipp_list_breaches_tenant details
cipp_list_breaches_tenant details
[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.
cipp_list_csp_sku details
cipp_list_csp_sku details
[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'.
cipp_list_users_and_groups details
cipp_list_users_and_groups details
[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.
cipp_manage_csp_license details
cipp_manage_csp_license details
[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.
cipp_universal_search details
cipp_universal_search details
[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.
cipp_universal_search_v2 details
cipp_universal_search_v2 details
[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.
Diagnostics
cipp_app_insights_query details
cipp_app_insights_query details
[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.
cipp_cipp_db_cache details
cipp_cipp_db_cache details
[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.
cipp_clone_template details
cipp_clone_template details
[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.
cipp_cpv_refresh details
cipp_cpv_refresh details
[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_graph_explorer_preset details
cipp_delete_graph_explorer_preset details
[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.
cipp_download_cipp_logs details
cipp_download_cipp_logs details
[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.
cipp_durable_functions details
cipp_durable_functions details
[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_template details
cipp_edit_template details
[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.
cipp_extension_ninja_one_queue details
cipp_extension_ninja_one_queue details
[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_admin_portal_licenses details
cipp_list_admin_portal_licenses details
[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.
cipp_list_api_test details
cipp_list_api_test details
[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_list_azure_ad_connect_status details
cipp_list_azure_ad_connect_status details
[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.
cipp_list_backup details
cipp_list_backup details
[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_check_ext_alerts details
cipp_list_check_ext_alerts details
[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.
cipp_list_custom_data_mappings details
cipp_list_custom_data_mappings details
[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_db_cache details
cipp_list_db_cache details
[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'.
cipp_list_diagnostics_presets details
cipp_list_diagnostics_presets details
[CIPP] List saved diagnostic query presets via GET /api/ListDiagnosticsPresets. Useful for diagnosing CIPP-side issues. No request parameters per spec.
cipp_list_directory_objects details
cipp_list_directory_objects details
[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.
cipp_list_extension_cache_data details
cipp_list_extension_cache_data details
[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.
cipp_list_extensions_config details
cipp_list_extensions_config details
[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.
cipp_list_feature_flags details
cipp_list_feature_flags details
[CIPP] List CIPP feature flags via GET /api/ListFeatureFlags. Returns each flag's name and enabled state. No request parameters per spec.
cipp_list_function_parameters details
cipp_list_function_parameters details
[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_function_stats details
cipp_list_function_stats details
[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_list_generic_test_function details
cipp_list_generic_test_function details
[CIPP] Run the CIPP generic test function via GET /api/ListGenericTestFunction. Returns CIPP test diagnostic output. No request parameters per spec.
cipp_list_graph_explorer_presets details
cipp_list_graph_explorer_presets details
[CIPP] List saved Graph Explorer query presets via GET /api/ListGraphExplorerPresets. Spec query: 'Endpoint' (string). The current client method takes no parameters.
cipp_list_halo_clients details
cipp_list_halo_clients details
[CIPP] List HaloPSA clients visible to CIPP via GET /api/ListHaloClients. Used by the CIPP HaloPSA extension. No request parameters per spec.
cipp_list_ip_whitelist details
cipp_list_ip_whitelist details
[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_known_ip_db details
cipp_list_known_ip_db details
[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_list_notification_config details
cipp_list_notification_config details
[CIPP] Read the CIPP notification configuration via GET /api/ListNotificationConfig. Returns email recipients, webhook URLs, and severity filters. No request parameters per spec.
cipp_partner_webhook details
cipp_partner_webhook details
[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.
cipp_set_cipp_auto_backup details
cipp_set_cipp_auto_backup details
[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.
cipp_set_package_tag details
cipp_set_package_tag details
[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.
cipp_set_user_bookmarks details
cipp_set_user_bookmarks details
[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.
cipp_user_settings details
cipp_user_settings details
[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).
Analytics
cipp_add_test_report details
cipp_add_test_report details
[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.
cipp_all_tenant_bpa details
cipp_all_tenant_bpa details
[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_all_tenant_compliance details
cipp_all_tenant_compliance details
[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_all_tenant_secure_score details
cipp_all_tenant_secure_score details
[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.
cipp_bulk_graph_request details
cipp_bulk_graph_request details
[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.
cipp_delete_test_report details
cipp_delete_test_report details
[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.
cipp_get_secure_score details
cipp_get_secure_score details
[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.
cipp_list_available_tests details
cipp_list_available_tests details
[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.
cipp_list_secure_score_control_profiles details
cipp_list_secure_score_control_profiles details
[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.
cipp_list_test_reports details
cipp_list_test_reports details
[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_tests details
cipp_list_tests details
[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.
cipp_offboard_tenant details
cipp_offboard_tenant details
[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.
cipp_run_domain_analyser details
cipp_run_domain_analyser details
[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"}.
cipp_run_test details
cipp_run_test details
[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.
Application Approvals
cipp_add_multi_tenant_app details
cipp_add_multi_tenant_app details
[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.
cipp_create_app_template details
cipp_create_app_template details
[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'.
cipp_delete_app_approval_template details
cipp_delete_app_approval_template details
[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.
cipp_delete_app_permission_template details
cipp_delete_app_permission_template details
[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.
cipp_exec_app_approval details
cipp_exec_app_approval details
[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.
cipp_exec_application details
cipp_exec_application details
[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.
cipp_exec_service_principals details
cipp_exec_service_principals details
[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.
cipp_list_app_approval_templates details
cipp_list_app_approval_templates details
[CIPP] List all application approval templates in CIPP. Returns template names, app permissions, and approval configurations.
Applications
cipp_add_choco_app details
cipp_add_choco_app details
[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'.
cipp_add_msp_app details
cipp_add_msp_app details
[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.
cipp_add_office_app details
cipp_add_office_app details
[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.
cipp_add_store_app details
cipp_add_store_app details
[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.
cipp_add_win32_script_app details
cipp_add_win32_script_app details
[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.
cipp_assign_app details
cipp_assign_app details
[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.
cipp_exec_app_upload details
cipp_exec_app_upload details
[CIPP] Upload an application package to Intune. Triggers the upload and processing of a queued application.
cipp_list_application_queue details
cipp_list_application_queue details
[CIPP] List queued application deployments across tenants. Returns pending app installations, their status, and target tenants.
cipp_list_apps_repository details
cipp_list_apps_repository details
[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.
cipp_list_potential_apps details
cipp_list_potential_apps details
[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.
cipp_remove_app details
cipp_remove_app details
[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.
cipp_remove_queued_app details
cipp_remove_queued_app details
[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.
cipp_sync_vpp details
cipp_sync_vpp details
[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.
More in Tools Reference
Atera ToolsAuvik ToolsAvanan (Check Point Harmony Email) ToolsConnectWise Sell ToolsStill need help? Ask the team