The VCF Operations API uses different payloads for finding, canceling, and deleting alerts. Treating those operations as interchangeable can either fail validation or broaden a cleanup beyond the alerts you intended to remove.
This follow-up corrects the API section in Bulk Delete Alerts in VCF Operations. The safe sequence is:
- Query candidate alerts without changing them.
- Review every returned alert ID and account for pagination.
- Cancel only the approved IDs with a
uuid-valuesbody. - Verify those IDs are now
CANCELED. - Delete the same IDs with a direct
AlertQuerybody. - Query again to confirm that they are gone.
Before You Begin
- Fix or tune the alert definition first. Deleting alert history does not stop the condition from generating new alerts.
- Use a VCF Operations account whose application role and object scope permit the required alert operations.
- Take any database backup required by your change policy before a large cleanup.
- Start with one small batch. The API documents a default query page size of 1,000, so a single response is not proof that you found every match.
- Use a trusted certificate. The examples intentionally do not bypass TLS verification.
Set the appliance name, create a private working directory, and read an existing session token without placing it in shell history. Store the authorization header in a mode-600 file so the token is not expanded into each curl process argument, then remove it as soon as the cleanup is verified:
export OPS_FQDN='ops.example.com'
umask 077
OPS_WORK_DIR="$(mktemp -d /tmp/vcf-ops-alert-cleanup.XXXXXX)"
read -r -s -p 'VCF Operations token: ' OPS_TOKEN
printf '\n'
printf 'Authorization: OpsToken %s\n' "$OPS_TOKEN" \
> "${OPS_WORK_DIR}/authorization.header"
chmod 600 "${OPS_WORK_DIR}/authorization.header"
unset OPS_TOKEN
Current VCF Operations API requests use this header:
Authorization: OpsToken TOKEN_VALUE
You can obtain the session token from POST /suite-api/api/auth/token/acquire. Do not save the token, account password, or response in a shared script or repository.
Step 1: Query a Narrow Candidate Set
Use POST /suite-api/api/alerts/query with an AlertQuery. Prefer an exact alertDefinitionId, resource ID, or an already reviewed list of alert IDs. The alertName field uses contains logic and is therefore less precise.
Create a query for one exact alert definition:
{
"compositeOperator": "AND",
"activeOnly": true,
"alertDefinitionId": [
"11111111-2222-3333-4444-555555555555"
]
}
Save that JSON as ${OPS_WORK_DIR}/alert-candidates.json, replace the example UUID, and query the first page:
curl --fail-with-body \
--request POST \
"https://${OPS_FQDN}/suite-api/api/alerts/query?page=0&pageSize=1000" \
--header @"${OPS_WORK_DIR}/authorization.header" \
--header 'Content-Type: application/json' \
--data-binary @"${OPS_WORK_DIR}/alert-candidates.json" \
--output "${OPS_WORK_DIR}/alert-page-0.json"
Review the count and identifying fields before doing anything destructive:
jq '.pageInfo, [.alerts[] | {alertId, alertDefinitionId, alertDefinitionName, resourceId, status}]' \
"${OPS_WORK_DIR}/alert-page-0.json"
If pageInfo.totalCount exceeds the number returned, query every remaining page and review it. Keep batches small enough to audit. Never replace the query with {} merely to avoid pagination.
Step 2: Build and Review the Exact Cancel List
The modify-alerts endpoint does not accept an AlertQuery. It requires a uuid-values body whose only required field is the uuids array.
After reviewing the first batch, build the body from its returned IDs:
jq '{uuids: [.alerts[].alertId]}' \
"${OPS_WORK_DIR}/alert-page-0.json" \
> "${OPS_WORK_DIR}/alerts-to-cancel.json"
jq . "${OPS_WORK_DIR}/alerts-to-cancel.json"
Record the approved count and retain the reviewed ID list with the change record.
Step 3: Cancel Only Those IDs
Cancel the exact list with POST /suite-api/api/alerts?action=cancel:
curl --fail-with-body \
--request POST \
"https://${OPS_FQDN}/suite-api/api/alerts?action=cancel" \
--header @"${OPS_WORK_DIR}/authorization.header" \
--header 'Content-Type: application/json' \
--data-binary @"${OPS_WORK_DIR}/alerts-to-cancel.json" \
--output "${OPS_WORK_DIR}/alert-cancel-response.json"
jq '[.alerts[] | {alertId, status, cancelTimeUTC}]' \
"${OPS_WORK_DIR}/alert-cancel-response.json"
Canceling changes alert state; it does not delete the alert record.
Step 4: Build the Targeted Delete Query
The delete endpoint accepts a direct AlertQuery, not an alert-query wrapper. Reuse the reviewed IDs and require CANCELED status:
jq '{
compositeOperator: "AND",
alertStatus: ["CANCELED"],
alertId: .uuids
}' \
"${OPS_WORK_DIR}/alerts-to-cancel.json" \
> "${OPS_WORK_DIR}/alerts-to-delete.json"
jq . "${OPS_WORK_DIR}/alerts-to-delete.json"
Run the same body through the read-only query endpoint first:
curl --fail-with-body \
--request POST \
"https://${OPS_FQDN}/suite-api/api/alerts/query?page=0&pageSize=1000" \
--header @"${OPS_WORK_DIR}/authorization.header" \
--header 'Content-Type: application/json' \
--data-binary @"${OPS_WORK_DIR}/alerts-to-delete.json" \
--output "${OPS_WORK_DIR}/alert-delete-preview.json"
jq '.pageInfo, [.alerts[] | {alertId, alertDefinitionName, resourceId, status}]' \
"${OPS_WORK_DIR}/alert-delete-preview.json"
Stop if the preview contains an unexpected ID, definition, resource, or status. The preview count should match the reviewed cancel list for that batch.
Step 5: Delete and Verify
Delete the targeted canceled alerts with DELETE /suite-api/api/alerts/bulk:
curl --fail-with-body \
--request DELETE \
"https://${OPS_FQDN}/suite-api/api/alerts/bulk" \
--header @"${OPS_WORK_DIR}/authorization.header" \
--header 'Content-Type: application/json' \
--data-binary @"${OPS_WORK_DIR}/alerts-to-delete.json"
The documented success response is HTTP 204 and indicates that the deletion was started. It does not return a deleted-object list. Query the exact IDs again until the API returns no matching alerts, and confirm the expected result in the UI and audit history before closing the change.
Delete the temporary authorization header immediately. Retain or securely remove the reviewed JSON files according to the change-record policy, then remove the directory when it is empty:
rm -f -- "${OPS_WORK_DIR}/authorization.header"
unset OPS_TOKEN
# After retaining or deleting the reviewed JSON files:
rmdir -- "$OPS_WORK_DIR"
unset OPS_WORK_DIR
Requests to Avoid
- Do not send an empty
{}query to the bulk-delete endpoint. - Do not use only
alertStatus: ["CANCELED"]unless the approved scope is explicitly every canceled alert in the environment. - Do not put an
AlertQueryinside analert-querywrapper; the endpoint expects the query fields at the JSON root. - Do not send alert IDs directly to
DELETE /suite-api/api/alerts; the documented bulk deletion route is/suite-api/api/alerts/bulk. - Do not assume the first 1,000 results are the complete set.
- Do not schedule destructive cleanup until the query, count limits, audit logging, and failure handling have been tested with a small batch.