save_alert_rule
Create or update an alert rule for the calling tenant in one call. WRITE: available to any authenticated user. Omit ruleId to create a new rule; supply ruleId to REPLACE an existing one.
UPDATE IS A WHOLE-OBJECT REPLACE, NOT A MERGE. Every field you leave out is cleared — omitting description sets it to null. To change one thing, fetch the rule with get_alert_rules and re-send its full spec with that one field altered. Two things are carved out and survive omission: status — ACTIVE/DISABLED is preserved; change it with set_alert_rule_status delivery — notifyOnResolve is preserved. Where a rule's alerts go is not held on the rule at all: routing lives in the notification gateway, so set it with set_notification_destinations, passing source "alerting" and the rule id as subject.
Re-validates exactly like preview_alert_rule: if the spec is invalid, nothing is persisted and problems[] is populated instead of rule — preview_alert_rule first to calibrate the threshold, then save once problems[] is empty there.
The rule still saves even when warnings[] is non-empty — warnings are advisory, never a reason to withhold saving, unlike problems[]. warnings[] currently carries one code, FIELD_NEVER_OBSERVED: a filter/groupBy field querysql couldn't resolve to a known column (so it silently falls back to reading it from the JSON catch-all) and that has never appeared in this customer's recent telemetry — almost always a typo'd field name, especially when preview_alert_rule also reported dataCoverage.status = NO_MATCHING_DATA. Fix the spelling and re-preview rather than treat it as a calibration problem.
Authors a single metric or anomaly rule (one measure over a rolling window). Compound multi-condition rules can't be created here — build those in the web editor.
METRIC RULE (structured) — watches one measure over a rolling time window: source: telemetry source (required): LOGS, SPANS, METRICS filter: optional QuerySQL boolean filter, e.g. service = 'my-svc' fn: catalog measure function, e.g. count, error_rate, p95, error_burn_rate arg: optional field the measure operates on, e.g. duration_ms for p95 params: optional named measure params, e.g. {"budget":"0.001"} (error_burn_rate) expression: optional free-form aggregate (used instead of fn) — a ratio/calculation, e.g. countIf(status_code = 'ERROR') * 100.0 / count() (this is exactly fn: error_rate; use fn instead unless you need a custom ratio — both already return 0-100, don't divide by 100 again) metricName/metricType: required only when source is METRICS unit: optional explicit display unit for the measure, e.g. BYTES or DURATION_MS — set it when the metric name doesn't self-describe its unit (OTel names like jvm.memory.used or http.server.request.duration carry no unit suffix); omit it to let the server infer the unit from the metric name or measure function windowMinutes: rolling window length in minutes (required) groupBy: optional list of fields to group the series by comparator: threshold comparator: GT, GTE, LT, LTE (required for static) warningThreshold: the warning-tier threshold the measure is compared against (required for static) warningConsecutiveWindows: consecutive breaching windows for the warning tier (default 1) criticalThreshold / criticalConsecutiveWindows: optional escalation tier
ANOMALY METRIC (structured) — flags a measure that deviates from its own historical baseline instead of a fixed threshold. Supply zScoreThreshold + direction instead of comparator/warningThreshold; groupBy must be empty. zScoreThreshold: robust z-score magnitude that counts as anomalous (> 0) direction: HIGH (spikes above baseline) or LOW (drops below baseline) anomalyConsecutiveWindows: consecutive anomalous windows required (>= 1)
Common fields: name: human-readable rule name (required, non-blank) description: optional free text notifyOnResolve: whether to notify when the alert resolves (default true) active: create-only — whether the rule starts ACTIVE (default true) or DISABLED
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| fn | No | Catalog measure function: count, error_rate, p95, error_burn_rate, ... | |
| arg | No | Optional field the measure operates on, e.g. duration_ms | |
| name | Yes | Human-readable rule name (non-blank) | |
| unit | No | Explicit display unit for the measure, e.g. BYTES or DURATION_MS — set it when the metric name doesn't self-describe its unit (OTel names like jvm.memory.used or http.server.request.duration carry no unit suffix); omit to let the server infer the unit from the metric name or measure function | |
| check | No | Compound AND/OR check tree as JSON, in the same shape get_alert_rules returns in checkDetails.check. Supply instead of the structured measurement and condition fields, which are ignored when this is set. | |
| active | No | Create-only: start the rule ACTIVE (default true) or DISABLED | |
| filter | No | Optional QuerySQL boolean filter, e.g. service = 'my-svc' | |
| params | No | Optional named measure params, e.g. {"budget":"0.001"} | |
| ruleId | No | Rule id (UUID) to update; omit to create a new rule | |
| source | No | Telemetry source: LOGS, SPANS, METRICS | |
| groupBy | No | Optional fields to group the series by | |
| direction | No | Anomaly direction: HIGH or LOW | |
| comparator | No | Threshold comparator: GT, GTE, LT, LTE (static rules) | |
| expression | No | Optional free-form aggregate expression measuring the source, used instead of fn (takes precedence when set). QuerySQL over the source's fields, e.g. a ratio 'countIf(status_code = ''ERROR'') * 100.0 / count()' (this is exactly fn: error_rate, which already returns 0-100 — don't divide by 100 again) or a metric ratio 'avg(if(metric_name = ''a'', value, null)) / avg(if(metric_name = ''b'', value, null))'. | |
| metricName | No | Metric name (required only when source is METRICS) | |
| metricType | No | Metric type: GAUGE, SUM, HISTOGRAM, ... (only when source is METRICS) | |
| description | No | Optional free-text description | |
| windowMinutes | No | Rolling window length in minutes | |
| notifyOnResolve | No | Notify when the alert resolves (default true) | |
| zScoreThreshold | No | Anomaly z-score threshold (> 0) — supply instead of comparator/warningThreshold | |
| warningThreshold | No | Warning-tier threshold (static rules) | |
| criticalThreshold | No | Optional critical-tier threshold (escalation) | |
| anomalyConsecutiveWindows | No | Consecutive anomalous windows required (>= 1) | |
| warningConsecutiveWindows | No | Consecutive breaching windows for the warning tier (default 1) | |
| criticalConsecutiveWindows | No | Consecutive breaching windows for the critical tier (default 1) |