---
type: api
title: Create rules in bulk
description: **Spaces method and path for this operation:**
url: https://www.elastic.co/elastic/docs-builder/docs/4371/api/doc/kibana/operation/operation-post-alerting-v2-rules-bulk-create
resource: https://www.elastic.co/elastic/docs-builder/docs/4371/api/doc/kibana/operation/operation-post-alerting-v2-rules-bulk-create
products:
  - Kibana
---

# Create rules in bulk

Availability: 9.5+

- Server: `https://{kibana_url}`

## Paths

- `POST` `/api/alerting/v2/rules/_bulk_create`
- `POST` `/s/{space_id}/api/alerting/v2/rules/_bulk_create`

- [Api key auth](/elastic/docs-builder/docs/4371/api/doc/kibana/authentication#apikeyauth)
- [Basic auth](/elastic/docs-builder/docs/4371/api/doc/kibana/authentication#basicauth)

## Description

Refer to [Spaces](https://www.elastic.co/docs/deploy-manage/manage-spaces) for more information.

Creates up to 100 rules in a single request. Each item can include a client-supplied `id`. Kibana generates an identifier when `id` is omitted. The request succeeds with HTTP 200 even when some rules fail. Created rules are returned in `items`. Failures are returned in `errors`. Check `errors` before treating the request as fully successful. To create a single rule, use POST /api/alerting/v2/rules.<br/><br/>[Required authorization] Route required privileges: manage_alerting-v2-rules.

## Request Body

`application/json`

- `items` ([] object · min: 1 · max: 100) — required
  The rules to create. Must contain between 1 and 100 rules.
  - `artifacts` ([] object · min: 1 · max: 100) — optional
    Optional objects attached to the rule, such as a runbook or a dashboard. Each item has `id`, `type`, and `data`. The shape of `data` depends on `type`. For example, a `runbook` uses `content` and a `dashboard` uses `dashboard_id`. Known types are validated against that shape. Unknown types are stored when `id`, `type`, and `data` are present. An empty array is rejected: omit `artifacts` on create, or send `null` on PATCH to clear.
    - `data` (map string to object) — required
      Structured artifact data.
    - `id` (string · min: 1 · max: 150) — required
      Artifact identifier.
    - `type` (string · min: 1 · max: 128) — required
      Artifact type.
  - `enabled` (boolean · default: true) — optional
    If `true` (default), the rule runs on its schedule after creation. If `false`, the rule is saved but does not run until you enable it.
  - `grouping` ({}) — optional
    Grouping configuration.
    - `fields` ([] string · min: 1 · max: 16) — required
      Fields to group alerts by, e.g. ["host.name", "service.name"]. Should match ES|QL GROUP BY fields.
  - `id` (string · min: 1 · max: 150 · pattern: ^[a-zA-Z0-9_-]+$) — optional
    Optional rule ID. If omitted, Kibana generates one. IDs in the request must be unique.
  - `kind` (enum) — required
    Whether the rule creates alerts (`alert`) or only stores matching events (`signal`).
    Values: `alert`, `signal`
  - `metadata` ({}) — required
    Rule metadata.
    - `builder` (object) — optional
      Identifies the rule builder that authored this rule (e.g. "threshold"). Absent for rules authored directly in ES|QL; send `null` on PATCH to clear it.
      - `type` (string · min: 1 · max: 64) — required
        Rule builder type.
    - `description` (string · min: 1 · max: 1024) — optional
      Human-readable description of the rule. Omit to leave it unset; send `null` on PATCH to clear it. An empty string is rejected.
    - `name` (string · min: 1 · max: 256) — required
      Rule name (must be unique within the space).
    - `routing_tags` ([] string · min: 1 · max: 20) — optional
      Routing tags that link alerts from this rule to action policies. An action policy applies when its `matcher.tags` contains at least one of these tags. Only allowed when kind is "alert".
    - `tags` ([] string · min: 1 · max: 20) — optional
      Tags for categorization, e.g. ["production", "infra"].
  - `no_data` (union) — optional
    What the rule does when a group has no data. Required when `kind` is `alert`. Not allowed when `kind` is `signal`. Any strategy other than `ignore` requires either `query.breach` or `no_data.query`, so that a group with no data can be told apart from one that stopped breaching.
    - **Kibana_HTTP_APIs_alerting_rule_no_data_ignore** (`strategy: ignore`)
      Does not check whether a group still has data. Missing groups do not produce `no_data` events.
      - `strategy` (enum) — required
        Values: `ignore`
    - **Kibana_HTTP_APIs_alerting_rule_no_data_keep_last** (`strategy: keep_last`)
      Holds the alert's current status when the rule finds no data.
      - `query` (string · min: 1 · max: 10000) — optional
        Optional ES|QL query that checks whether a group has data. If omitted, `query.base` is used, which then has to be a presence query in its own right — so `query.breach` is required.
      - `strategy` (enum) — required
        Values: `keep_last`
    - **Kibana_HTTP_APIs_alerting_rule_no_data_resolve** (`strategy: resolve`)
      Closes the alert the first time the rule finds no data for a group.
      - `query` (string · min: 1 · max: 10000) — optional
        Optional ES|QL query that checks whether a group has data. If omitted, `query.base` is used, which then has to be a presence query in its own right — so `query.breach` is required.
      - `strategy` (enum) — required
        Values: `resolve`
    - **Kibana_HTTP_APIs_alerting_rule_no_data_alert** (`strategy: alert`)
      Marks an existing alert `active` when the rule finds no data. It never opens an alert for a group that has not breached. Not accepted when creating or updating rules.
      - `query` (string · min: 1 · max: 10000) — optional
        Optional ES|QL query that checks whether a group has data. If omitted, `query.base` is used, which then has to be a presence query in its own right — so `query.breach` is required.
      - `strategy` (enum) — required
        Values: `alert`
  - `query` ({}) — required
    ES|QL query the rule evaluates. `base` is required. `breach` is an optional clause appended to it.
    - `base` (string · min: 1 · max: 10000) — required
      ES|QL query that specifies the data to evaluate. Must include a `FROM` clause. Kibana applies the time filter from `schedule.lookback` using `time_field`.
    - `breach` ({}) — optional
      Optional ES|QL clause appended to `query.base`. If omitted, every row from `query.base` is a match, and a `no_data` strategy other than `ignore` then requires `no_data.query`.
      - `segment` (string · min: 1 · max: 10000) — required
        ES|QL clause appended to `query.base`, for example `WHERE avg_cpu > 0.85`. Don't include a `FROM` clause.
  - `recovery` (union) — optional
    When an alert recovers. Required when `kind` is `alert`. Not allowed when `kind` is `signal`.
    - **Kibana_HTTP_APIs_alerting_rule_recovery_no_breach** (`strategy: no_breach`)
      Recovers the alert when its group no longer appears in the breach results.
      - `strategy` (enum) — required
        Values: `no_breach`
    - **Kibana_HTTP_APIs_alerting_rule_recovery_condition** (`strategy: condition`)
      Recovers the alert when `query.base` plus `segment` returns the group. Requires `query.breach`.
      - `segment` (string · min: 1 · max: 10000) — required
        ES|QL clause appended to `query.base`, for example `WHERE avg_cpu < 0.60`. Don't include a `FROM` clause.
      - `strategy` (enum) — required
        Values: `condition`
    - **Kibana_HTTP_APIs_alerting_rule_recovery_query** (`strategy: query`)
      Recovers the alert when this separate query returns the group.
      - `query` (string · min: 1 · max: 10000) — required
        Independent ES|QL query, including its own `FROM` clause. A matching group recovers the alert.
      - `strategy` (enum) — required
        Values: `query`
    - **Kibana_HTTP_APIs_alerting_rule_recovery_manual** (`strategy: manual`)
      Does not recover automatically. Close the alert with a user action. `state_transition.recovering` has no effect.
      - `strategy` (enum) — required
        Values: `manual`
  - `schedule` ({}) — required
    Execution schedule configuration.
    - `every` (string · max: 32) — required
      Execution interval, e.g. 1m, 5m, 1h.
    - `lookback` (string · max: 32) — optional
      Lookback window for the query, e.g. 5m, 1h. Can also be expressed in ES|QL.
  - `state_transition` ({}) — optional
    Specifies how many consecutive matches, or how long a condition must hold, before an alert becomes `active` or `inactive`. Allowed only when `kind` is `alert`.
    - `pending` ({}) — optional
      - `count` (integer · min: 0 · max: 1000) — optional
        Consecutive matches the alert spends in `pending` before it becomes `active` on the next match. For example, `2` opens it on the third consecutive match. Set to `0` to open it on the first match.
      - `operator` (enum) — optional
        When both `count` and `timeframe` are set, `and` requires both and `or` requires either. Allowed only when both fields are present.
        Values: `and`, `or`
      - `timeframe` (string · max: 32) — optional
        Duration the condition must hold, for example `5m`. Combine with `count` using `operator`.
    - `recovering` ({}) — optional
      - `count` (integer · min: 0 · max: 1000) — optional
        Consecutive recoveries the alert spends in `recovering` before it becomes `inactive` on the next recovery. For example, `2` closes it on the third consecutive recovery. Set to `0` to close it on the first recovery.
      - `operator` (enum) — optional
        When both `count` and `timeframe` are set, `and` requires both and `or` requires either. Allowed only when both fields are present.
        Values: `and`, `or`
      - `timeframe` (string · max: 32) — optional
        Duration the condition must hold, for example `5m`. Combine with `count` using `operator`.
  - `time_field` (string · min: 1 · max: 256 · default: @timestamp) — optional
    Document field Kibana uses with `schedule.lookback` to time-filter `query.base`.

## Responses

#### `200`

Created rules are returned in `items`. Failures are returned in `errors`.

`application/json`

- `errors` ([] object)
  Errors for rules that could not be created. Each entry includes the rule `id` and the error. Empty when every requested rule was created.
  - `error` (object)
    - `code` (string)
      Stable error code you can branch on, for example `INVALID_SCHEDULE` or `RULE_ALREADY_EXISTS`.
    - `details` (map string to object)
      Optional extra information about the error, for example field validation issues or the `rule_id` when that ID already exists.
    - `message` (string)
      A readable explanation of the error. The wording can change without notice. Do not parse this field.
  - `id` (string)
    The identifier of the resource that failed.
- `items` ([] object)
  Rules that were created. Rules listed in `errors` are not included.
  - `artifacts` ([] object · min: 1 · max: 100)
    Optional objects attached to the rule, such as a runbook or a dashboard. Each item has `id`, `type`, and `data`. The shape of `data` depends on `type`. For example, a `runbook` uses `content` and a `dashboard` uses `dashboard_id`. Known types are validated against that shape. Unknown types are stored when `id`, `type`, and `data` are present. An empty array is rejected: omit `artifacts` on create, or send `null` on PATCH to clear.
    - `data` (map string to object)
      Structured artifact data.
    - `id` (string · min: 1 · max: 150)
      Artifact identifier.
    - `type` (string · min: 1 · max: 128)
      Artifact type.
  - `created_at` (string · pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z))$)
    ISO timestamp when the rule was created.
  - `created_by` ({})
    Actor who created the rule.
    - `profile_uid` (string) — nullable
      User profile ID of the actor, or `null` when it cannot be resolved.
  - `enabled` (boolean)
    Whether the rule is enabled.
  - `grouping` ({})
    Grouping configuration.
    - `fields` ([] string · min: 1 · max: 16)
      Fields to group alerts by, e.g. ["host.name", "service.name"]. Should match ES|QL GROUP BY fields.
  - `id` (string)
    Unique rule identifier.
  - `kind` (enum)
    Whether the rule creates alerts (`alert`) or only stores matching events (`signal`).
    Values: `alert`, `signal`
  - `metadata` ({})
    Rule metadata.
    - `builder` (object)
      Identifies the rule builder that authored this rule (e.g. "threshold"). Absent for rules authored directly in ES|QL; send `null` on PATCH to clear it.
      - `type` (string · min: 1 · max: 64)
        Rule builder type.
    - `description` (string · min: 1 · max: 1024)
      Human-readable description of the rule. Omit to leave it unset; send `null` on PATCH to clear it. An empty string is rejected.
    - `name` (string · min: 1 · max: 256)
      Rule name (must be unique within the space).
    - `routing_tags` ([] string · min: 1 · max: 20)
      Routing tags that link alerts from this rule to action policies. An action policy applies when its `matcher.tags` contains at least one of these tags. Only allowed when kind is "alert".
    - `tags` ([] string · min: 1 · max: 20)
      Tags for categorization, e.g. ["production", "infra"].
  - `no_data` (union)
    What the rule does when a group has no data. Required when `kind` is `alert`. Not allowed when `kind` is `signal`. Any strategy other than `ignore` requires either `query.breach` or `no_data.query`, so that a group with no data can be told apart from one that stopped breaching.
    - **Kibana_HTTP_APIs_alerting_rule_no_data_ignore** (`strategy: ignore`)
      Does not check whether a group still has data. Missing groups do not produce `no_data` events.
      - `strategy` (enum)
        Values: `ignore`
    - **Kibana_HTTP_APIs_alerting_rule_no_data_keep_last** (`strategy: keep_last`)
      Holds the alert's current status when the rule finds no data.
      - `query` (string · min: 1 · max: 10000)
        Optional ES|QL query that checks whether a group has data. If omitted, `query.base` is used, which then has to be a presence query in its own right — so `query.breach` is required.
      - `strategy` (enum)
        Values: `keep_last`
    - **Kibana_HTTP_APIs_alerting_rule_no_data_resolve** (`strategy: resolve`)
      Closes the alert the first time the rule finds no data for a group.
      - `query` (string · min: 1 · max: 10000)
        Optional ES|QL query that checks whether a group has data. If omitted, `query.base` is used, which then has to be a presence query in its own right — so `query.breach` is required.
      - `strategy` (enum)
        Values: `resolve`
    - **Kibana_HTTP_APIs_alerting_rule_no_data_alert** (`strategy: alert`)
      Marks an existing alert `active` when the rule finds no data. It never opens an alert for a group that has not breached. Not accepted when creating or updating rules.
      - `query` (string · min: 1 · max: 10000)
        Optional ES|QL query that checks whether a group has data. If omitted, `query.base` is used, which then has to be a presence query in its own right — so `query.breach` is required.
      - `strategy` (enum)
        Values: `alert`
  - `query` ({})
    ES|QL query the rule evaluates. `base` is required. `breach` is an optional clause appended to it.
    - `base` (string · min: 1 · max: 10000)
      ES|QL query that specifies the data to evaluate. Must include a `FROM` clause. Kibana applies the time filter from `schedule.lookback` using `time_field`.
    - `breach` ({})
      Optional ES|QL clause appended to `query.base`. If omitted, every row from `query.base` is a match, and a `no_data` strategy other than `ignore` then requires `no_data.query`.
      - `segment` (string · min: 1 · max: 10000)
        ES|QL clause appended to `query.base`, for example `WHERE avg_cpu > 0.85`. Don't include a `FROM` clause.
  - `recovery` (union)
    When an alert recovers. Required when `kind` is `alert`. Not allowed when `kind` is `signal`.
    - **Kibana_HTTP_APIs_alerting_rule_recovery_no_breach** (`strategy: no_breach`)
      Recovers the alert when its group no longer appears in the breach results.
      - `strategy` (enum)
        Values: `no_breach`
    - **Kibana_HTTP_APIs_alerting_rule_recovery_condition** (`strategy: condition`)
      Recovers the alert when `query.base` plus `segment` returns the group. Requires `query.breach`.
      - `segment` (string · min: 1 · max: 10000)
        ES|QL clause appended to `query.base`, for example `WHERE avg_cpu < 0.60`. Don't include a `FROM` clause.
      - `strategy` (enum)
        Values: `condition`
    - **Kibana_HTTP_APIs_alerting_rule_recovery_query** (`strategy: query`)
      Recovers the alert when this separate query returns the group.
      - `query` (string · min: 1 · max: 10000)
        Independent ES|QL query, including its own `FROM` clause. A matching group recovers the alert.
      - `strategy` (enum)
        Values: `query`
    - **Kibana_HTTP_APIs_alerting_rule_recovery_manual** (`strategy: manual`)
      Does not recover automatically. Close the alert with a user action. `state_transition.recovering` has no effect.
      - `strategy` (enum)
        Values: `manual`
  - `schedule` ({})
    Execution schedule configuration.
    - `every` (string · max: 32)
      Execution interval, e.g. 1m, 5m, 1h.
    - `lookback` (string · max: 32)
      Lookback window for the query, e.g. 5m, 1h. Can also be expressed in ES|QL.
  - `state_transition` ({})
    Specifies how many consecutive matches, or how long a condition must hold, before an alert becomes `active` or `inactive`. Allowed only when `kind` is `alert`.
    - `pending` ({})
      - `count` (integer · min: 0 · max: 1000)
        Consecutive matches the alert spends in `pending` before it becomes `active` on the next match. For example, `2` opens it on the third consecutive match. Set to `0` to open it on the first match.
      - `operator` (enum)
        When both `count` and `timeframe` are set, `and` requires both and `or` requires either. Allowed only when both fields are present.
        Values: `and`, `or`
      - `timeframe` (string · max: 32)
        Duration the condition must hold, for example `5m`. Combine with `count` using `operator`.
    - `recovering` ({})
      - `count` (integer · min: 0 · max: 1000)
        Consecutive recoveries the alert spends in `recovering` before it becomes `inactive` on the next recovery. For example, `2` closes it on the third consecutive recovery. Set to `0` to close it on the first recovery.
      - `operator` (enum)
        When both `count` and `timeframe` are set, `and` requires both and `or` requires either. Allowed only when both fields are present.
        Values: `and`, `or`
      - `timeframe` (string · max: 32)
        Duration the condition must hold, for example `5m`. Combine with `count` using `operator`.
  - `time_field` (string · min: 1 · max: 256 · default: @timestamp)
    Document field Kibana uses with `schedule.lookback` to time-filter `query.base`.
  - `updated_at` (string · pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z))$)
    ISO timestamp when the rule was last updated.
  - `updated_by` ({})
    Actor who last updated the rule.
    - `profile_uid` (string) — nullable
      User profile ID of the actor, or `null` when it cannot be resolved.
  - `version` (integer · min: 1 · max: 9007199254740991)
    Monotonically increasing integer number representing a rule configuration version, incremented on every change. Used on generated rule events as `rule.version`.
#### `400`

Indicates an invalid schema or parameters.

`application/json`

- `code` (string)
  Stable error code you can branch on, for example `INVALID_SCHEDULE` or `RULE_ALREADY_EXISTS`.
- `details` (map string to object)
  Optional extra information about the error, for example field validation issues or the `rule_id` when that ID already exists.
- `error` (string)
  A short human-readable summary of the error category (e.g., "Not Found", "Bad Request"). Subject to change without notice. Do not parse or rely on its content.
- `message` (string)
  A readable explanation of the error. The wording can change without notice. Do not parse this field.
#### `401`

Indicates the request was not authenticated.

`application/json`

- `code` (string)
  Stable error code you can branch on, for example `INVALID_SCHEDULE` or `RULE_ALREADY_EXISTS`.
- `details` (map string to object)
  Optional extra information about the error, for example field validation issues or the `rule_id` when that ID already exists.
- `error` (string)
  A short human-readable summary of the error category (e.g., "Not Found", "Bad Request"). Subject to change without notice. Do not parse or rely on its content.
- `message` (string)
  A readable explanation of the error. The wording can change without notice. Do not parse this field.
#### `403`

Indicates the user does not have the required privileges to perform the request.

`application/json`

- `code` (string)
  Stable error code you can branch on, for example `INVALID_SCHEDULE` or `RULE_ALREADY_EXISTS`.
- `details` (map string to object)
  Optional extra information about the error, for example field validation issues or the `rule_id` when that ID already exists.
- `error` (string)
  A short human-readable summary of the error category (e.g., "Not Found", "Bad Request"). Subject to change without notice. Do not parse or rely on its content.
- `message` (string)
  A readable explanation of the error. The wording can change without notice. Do not parse this field.
#### `500`

Indicates an unexpected server-side error.

`application/json`

- `code` (string)
  Stable error code you can branch on, for example `INVALID_SCHEDULE` or `RULE_ALREADY_EXISTS`.
- `details` (map string to object)
  Optional extra information about the error, for example field validation issues or the `rule_id` when that ID already exists.
- `error` (string)
  A short human-readable summary of the error category (e.g., "Not Found", "Bad Request"). Subject to change without notice. Do not parse or rely on its content.
- `message` (string)
  A readable explanation of the error. The wording can change without notice. Do not parse this field.
#### `503`

Indicates the alerting engine is disabled by the `alerting:v2:enabled` advanced setting.

`application/json`

- `code` (string)
  Stable error code you can branch on, for example `INVALID_SCHEDULE` or `RULE_ALREADY_EXISTS`.
- `details` (map string to object)
  Optional extra information about the error, for example field validation issues or the `rule_id` when that ID already exists.
- `error` (string)
  A short human-readable summary of the error category (e.g., "Not Found", "Bad Request"). Subject to change without notice. Do not parse or rely on its content.
- `message` (string)
  A readable explanation of the error. The wording can change without notice. Do not parse this field.
## Code Examples

#### Console

```console
POST /api/alerting/v2/rules/_bulk_create
{
  "items": [
    {
      "grouping": {
        "fields": [
          "host.name"
        ]
      },
      "kind": "alert",
      "metadata": {
        "description": "Alerts when average CPU usage exceeds a threshold.",
        "name": "Host CPU high",
        "routing_tags": [
          "sre-oncall"
        ],
        "tags": [
          "production",
          "infra"
        ]
      },
      "no_data": {
        "strategy": "keep_last"
      },
      "query": {
        "base": "FROM metrics-* | STATS avg_cpu = AVG(host.cpu.usage) BY host.name",
        "breach": {
          "segment": "WHERE avg_cpu \u003E 0.9"
        }
      },
      "recovery": {
        "strategy": "no_breach"
      },
      "schedule": {
        "every": "1m",
        "lookback": "5m"
      },
      "state_transition": {
        "pending": {
          "count": 1
        },
        "recovering": {
          "count": 1
        }
      },
      "time_field": "@timestamp"
    },
    {
      "enabled": false,
      "grouping": {
        "fields": [
          "host.name"
        ]
      },
      "id": "rule-disabled",
      "kind": "alert",
      "metadata": {
        "description": "Alerts when average CPU usage exceeds a threshold.",
        "name": "Host CPU high (disabled)",
        "routing_tags": [
          "sre-oncall"
        ],
        "tags": [
          "production",
          "infra"
        ]
      },
      "no_data": {
        "strategy": "keep_last"
      },
      "query": {
        "base": "FROM metrics-* | STATS avg_cpu = AVG(host.cpu.usage) BY host.name",
        "breach": {
          "segment": "WHERE avg_cpu \u003E 0.9"
        }
      },
      "recovery": {
        "strategy": "no_breach"
      },
      "schedule": {
        "every": "1m",
        "lookback": "5m"
      },
      "state_transition": {
        "pending": {
          "count": 1
        },
        "recovering": {
          "count": 1
        }
      },
      "time_field": "@timestamp"
    }
  ]
}
```

#### curl

```curl
curl -X POST "https://{kibana_url}/api/alerting/v2/rules/_bulk_create" \
  -H "kbn-xsrf: true" \
  -H "Content-Type: application/json" \
  -d '{
  "items": [
    {
      "grouping": {
        "fields": [
          "host.name"
        ]
      },
      "kind": "alert",
      "metadata": {
        "description": "Alerts when average CPU usage exceeds a threshold.",
        "name": "Host CPU high",
        "routing_tags": [
          "sre-oncall"
        ],
        "tags": [
          "production",
          "infra"
        ]
      },
      "no_data": {
        "strategy": "keep_last"
      },
      "query": {
        "base": "FROM metrics-* | STATS avg_cpu = AVG(host.cpu.usage) BY host.name",
        "breach": {
          "segment": "WHERE avg_cpu \u003E 0.9"
        }
      },
      "recovery": {
        "strategy": "no_breach"
      },
      "schedule": {
        "every": "1m",
        "lookback": "5m"
      },
      "state_transition": {
        "pending": {
          "count": 1
        },
        "recovering": {
          "count": 1
        }
      },
      "time_field": "@timestamp"
    },
    {
      "enabled": false,
      "grouping": {
        "fields": [
          "host.name"
        ]
      },
      "id": "rule-disabled",
      "kind": "alert",
      "metadata": {
        "description": "Alerts when average CPU usage exceeds a threshold.",
        "name": "Host CPU high (disabled)",
        "routing_tags": [
          "sre-oncall"
        ],
        "tags": [
          "production",
          "infra"
        ]
      },
      "no_data": {
        "strategy": "keep_last"
      },
      "query": {
        "base": "FROM metrics-* | STATS avg_cpu = AVG(host.cpu.usage) BY host.name",
        "breach": {
          "segment": "WHERE avg_cpu \u003E 0.9"
        }
      },
      "recovery": {
        "strategy": "no_breach"
      },
      "schedule": {
        "every": "1m",
        "lookback": "5m"
      },
      "state_transition": {
        "pending": {
          "count": 1
        },
        "recovering": {
          "count": 1
        }
      },
      "time_field": "@timestamp"
    }
  ]
}'
```

## Response Examples

#### Created both requested rules

```json
{
  "errors": [],
  "items": [
    {
      "created_at": "2026-01-15T12:00:00.000Z",
      "created_by": {
        "profile_uid": "u_elastic_0"
      },
      "enabled": true,
      "grouping": {
        "fields": [
          "host.name"
        ]
      },
      "id": "rule-1",
      "kind": "alert",
      "metadata": {
        "description": "Alerts when average CPU usage exceeds a threshold.",
        "name": "Host CPU high",
        "routing_tags": [
          "sre-oncall"
        ],
        "tags": [
          "production",
          "infra"
        ]
      },
      "no_data": {
        "strategy": "keep_last"
      },
      "query": {
        "base": "FROM metrics-* | STATS avg_cpu = AVG(host.cpu.usage) BY host.name",
        "breach": {
          "segment": "WHERE avg_cpu \u003E 0.9"
        }
      },
      "recovery": {
        "strategy": "no_breach"
      },
      "schedule": {
        "every": "1m",
        "lookback": "5m"
      },
      "state_transition": {
        "pending": {
          "count": 1
        },
        "recovering": {
          "count": 1
        }
      },
      "time_field": "@timestamp",
      "updated_at": "2026-01-15T12:00:00.000Z",
      "updated_by": {
        "profile_uid": "u_elastic_0"
      },
      "version": 1
    },
    {
      "created_at": "2026-01-15T12:00:00.000Z",
      "created_by": {
        "profile_uid": "u_elastic_0"
      },
      "enabled": false,
      "grouping": {
        "fields": [
          "host.name"
        ]
      },
      "id": "rule-disabled",
      "kind": "alert",
      "metadata": {
        "description": "Alerts when average CPU usage exceeds a threshold.",
        "name": "Host CPU high (disabled)",
        "routing_tags": [
          "sre-oncall"
        ],
        "tags": [
          "production",
          "infra"
        ]
      },
      "no_data": {
        "strategy": "keep_last"
      },
      "query": {
        "base": "FROM metrics-* | STATS avg_cpu = AVG(host.cpu.usage) BY host.name",
        "breach": {
          "segment": "WHERE avg_cpu \u003E 0.9"
        }
      },
      "recovery": {
        "strategy": "no_breach"
      },
      "schedule": {
        "every": "1m",
        "lookback": "5m"
      },
      "state_transition": {
        "pending": {
          "count": 1
        },
        "recovering": {
          "count": 1
        }
      },
      "time_field": "@timestamp",
      "updated_at": "2026-01-15T12:00:00.000Z",
      "updated_by": {
        "profile_uid": "u_elastic_0"
      },
      "version": 1
    }
  ]
}
```

#### Request body is missing required items

```json
{
  "code": "BAD_REQUEST",
  "details": {
    "errors": {
      "items": [
        "Required"
      ]
    }
  },
  "error": "Bad Request",
  "message": "items: Required"
}
```

#### Request was not authenticated

```json
{
  "code": "UNAUTHORIZED",
  "error": "Unauthorized",
  "message": "Authentication required to access this API."
}
```

#### Caller lacks required privileges

```json
{
  "code": "FORBIDDEN",
  "error": "Forbidden",
  "message": "The current user does not have the required privileges for this request."
}
```

#### Unexpected server-side error

```json
{
  "code": "INTERNAL_SERVER_ERROR",
  "error": "Internal Server Error",
  "message": "An unexpected error occurred."
}
```

#### Alerting V2 is disabled

```json
{
  "code": "ALERTING_DISABLED",
  "error": "Service Unavailable",
  "message": "Alerting is disabled."
}
```

