Cases action steps
Cases action steps let workflows create, query, update, and manage cases. The cases.* namespace provides full coverage of the Cases API through a single, consistent set of step types so you can automate case workflows without leaving the Elastic platform.
Use Cases steps for patterns like:
- Create a case when a detection rule fires, then attach the alert and relevant observables.
- Open a case, assign it to an on-call user, and post a notification to a team channel.
- Find similar cases for deduplication before creating a new one.
- Close a case automatically when a condition is met, and append an analyst note first.
Every cases.* step shares the same conventions, so once you learn one step the others are predictable.
Parameter casing. Case-level parameters use snake_case: case_id, not caseId. The editor rejects camelCase for these fields. Individual payloads, such as the alert object attached by cases.addAlerts, use the casing of the originating API (for example, alertId, index) because they match external API shapes.
Optional push-case config. Most cases.* steps accept an optional top-level push-case boolean (defaults to false). When true, the step pushes the updated case to a connected external system (for example, Jira or ServiceNow) after the workflow operation succeeds.
push-case applies only to steps that change a case. It is not supported on the read-only and internal steps: cases.deleteCase, cases.findCases, cases.findSimilarCases, cases.getAllAttachments, cases.getCase, cases.getCases, cases.getCasesByAlertId, and cases.updateObservable.
- name: create_and_push
type: cases.createCase
push-case: true
with:
title: "Triage required"
severity: high
Getting the case ID. The response from cases.createCase includes the new case ID at steps.<step_name>.output.case.id. Reference it in subsequent steps:
- name: new_case
type: cases.createCase
with:
title: "..."
- name: tag_case
type: cases.addTags
with:
case_id: "{{ steps.new_case.output.case.id }}"
tags: ["triaged"]
The 27 Cases steps group into six operational categories. Jump to any step:
Create, fetch, and search
cases.createCase ·
cases.createCaseFromTemplate ·
cases.getCase ·
cases.getCases ·
cases.findCases ·
cases.findSimilarCases ·
cases.getCasesByAlertId
Update fields
cases.updateCase ·
cases.updateCases ·
cases.setStatus ·
cases.setSeverity ·
cases.setTitle ·
cases.setDescription ·
cases.setCategory ·
cases.setCustomField ·
cases.closeCase
Attach content
cases.addComment ·
cases.addAlerts ·
cases.addEvents ·
cases.addObservables ·
cases.getAllAttachments
Tags and assignees
cases.addTags ·
cases.assignCase ·
cases.unassignCase
Update and delete observables
cases.updateObservable ·
cases.deleteObservable
Delete
cases.deleteCases
Create a case with a title, description, severity, tags, and optional assignees. Accepts an optional top-level connector-id to associate the case with a configured external connector (for example, Jira or ServiceNow).
| Parameter | Location | Type | Required | Description |
|---|---|---|---|---|
connector-id |
top level | string | No | ID of a configured external connector to attach to the case. |
title |
with |
string | Yes | Case title. |
description |
with |
string | Yes | Case description. Markdown supported. |
owner |
with |
string | Yes | Case owner: securitySolution, observability, or cases. |
severity |
with |
string | No | low, medium, high, or critical. |
tags |
with |
string[] |
No | Tags to apply to the case. |
assignees |
with |
array | No | Array of { uid } objects. |
category |
with |
string | No | Case category. |
customFields |
with |
array | No | Custom field values. |
settings |
with |
object | No | Case settings, for example syncAlerts. |
- name: create_case
type: cases.createCase
with:
title: "{{ event.rule.name }} on {{ event.alerts[0].host.name }}"
description: "Automatically created from detection rule."
severity: high
tags: ["auto-triage", "malware"]
owner: "securitySolution"
Create a case from a configured case template. Useful when a team has pre-defined case shapes (default title, description, tags, severity) for common scenarios.
| Parameter | Location | Type | Required | Description |
|---|---|---|---|---|
case_template_id |
with |
string | Yes | ID of the template to apply. |
overwrites |
with |
object | No | Fields to override on the templated case, for example title, tags. |
- name: create_from_template
type: cases.createCaseFromTemplate
with:
case_template_id: "malware-triage-template"
overwrites:
title: "Malware: {{ event.alerts[0].host.name }}"
Retrieve a complete case object by ID. Optionally include comments and attachments in the response.
| Parameter | Location | Type | Required | Description |
|---|---|---|---|---|
case_id |
with |
string | Yes | Case ID. |
include_comments |
with |
boolean | Yes | Whether to include comments and attachments in the response. |
- name: fetch_case
type: cases.getCase
with:
case_id: "{{ inputs.case_id }}"
include_comments: true
Retrieve up to 1000 cases in a single request. IDs that can't be fetched are reported in the errors array on the output. Use this to avoid N sequential cases.getCase calls in fan-out workflows.
| Parameter | Location | Type | Required | Description |
|---|---|---|---|---|
case_ids |
with |
string[] |
Yes | Array of case IDs. Maximum 1000. |
Output: { cases: array, errors: array }. The errors array contains entries for any IDs that couldn't be fetched.
- name: fetch_cases
type: cases.getCases
with:
case_ids: ["case-1", "case-2", "case-3"]
Search for cases matching filter criteria. Supports paging, sorting, and filtering by many fields. The output includes a cases array plus per-status counts (count_open_cases, count_in_progress_cases, count_closed_cases), page, per_page, and total.
| Parameter | Location | Type | Required | Description |
|---|---|---|---|---|
search |
with |
string | No | Free-text search. |
searchFields |
with |
string \| string[] |
No | Fields to search in. |
defaultSearchOperator |
with |
string | No | AND or OR for multi-term search. |
status |
with |
string \| string[] |
No | Filter by status. |
severity |
with |
string \| string[] |
No | Filter by severity. |
tags |
with |
string \| string[] |
No | Filter by tag. |
category |
with |
string \| string[] |
No | Filter by category. |
owner |
with |
string \| string[] |
No | Filter by case owner. |
assignees |
with |
string \| string[] |
No | Filter by assignee UID. |
reporters |
with |
string \| string[] |
No | Filter by reporter. |
customFields |
with |
object | No | Map of custom-field ID to allowed values. |
from |
with |
string | No | Start of the time range. |
to |
with |
string | No | End of the time range. |
page |
with |
number | No | Page number. Defaults to 1. |
perPage |
with |
number | No | Results per page. Defaults to 20. |
sortField |
with |
string | No | title, category, createdAt, updatedAt, closedAt, status, or severity. |
sortOrder |
with |
string | No | asc or desc. |
- name: find_open_high_sev
type: cases.findCases
with:
owner: "securitySolution"
status: "open"
severity: ["high", "critical"]
tags: ["investigation"]
sortField: "updatedAt"
sortOrder: "desc"
perPage: 20
Find cases similar to a given case, matched by shared observables. Useful for deduplication before creating a new case.
| Parameter | Location | Type | Required | Description |
|---|---|---|---|---|
case_id |
with |
string | Yes | Source case ID to find similar cases for. |
page |
with |
integer | Yes | Page number (1-based). |
perPage |
with |
integer | Yes | Results per page. |
The output includes a cases array, page, and per_page.
- name: find_similar
type: cases.findSimilarCases
with:
case_id: "{{ steps.create_case.output.case.id }}"
page: 1
perPage: 20
Find every case that contains a specific alert. The canonical "does a case already exist for this alert?" query.
| Parameter | Location | Type | Required | Description |
|---|---|---|---|---|
alert_id |
with |
string | Yes | Alert signal ID. |
owner |
with |
string | No | Filter by case owner. |
- name: check_existing
type: cases.getCasesByAlertId
with:
alert_id: "{{ event.alerts[0]._id }}"
Update one case's fields. Changes go inside a required updates object. An optional version field enforces optimistic concurrency.
| Parameter | Location | Type | Required | Description |
|---|---|---|---|---|
case_id |
with |
string | Yes | Case ID. |
version |
with |
string | No | Case version for optimistic concurrency. |
updates |
with |
object | Yes | Fields to update. At least one field required. Includes title, description, status, severity, category, tags, assignees, settings, and more. |
- name: escalate
type: cases.updateCase
with:
case_id: "{{ steps.find.output.cases[0].id }}"
updates:
status: "in-progress"
severity: "critical"
tags: ["escalated"]
Update fields must sit inside the updates object, not at the top level of with. updates: { status: open } is valid; with: { status: open, case_id: ... } is not.
Bulk-update multiple cases. Each element specifies its own case_id, optional version, and updates object.
| Parameter | Location | Type | Required | Description |
|---|---|---|---|---|
cases |
with |
array | Yes | Array of { case_id, version?, updates } objects. |
- name: bulk_close
type: cases.updateCases
with:
cases:
- case_id: "c1"
updates:
status: "closed"
- case_id: "c2"
updates:
status: "closed"
Set a case's status.
| Parameter | Location | Type | Required | Description |
|---|---|---|---|---|
case_id |
with |
string | Yes | Case ID. |
status |
with |
string | Yes | open, in-progress, or closed. |
- name: mark_in_progress
type: cases.setStatus
with:
case_id: "{{ steps.create_case.output.case.id }}"
status: "in-progress"
Set a case's severity.
| Parameter | Location | Type | Required | Description |
|---|---|---|---|---|
case_id |
with |
string | Yes | Case ID. |
severity |
with |
string | Yes | low, medium, high, or critical. |
Update a case's title.
| Parameter | Location | Type | Required | Description |
|---|---|---|---|---|
case_id |
with |
string | Yes | Case ID. |
title |
with |
string | Yes | New title. |
Update a case's description.
| Parameter | Location | Type | Required | Description |
|---|---|---|---|---|
case_id |
with |
string | Yes | Case ID. |
description |
with |
string | Yes | New description. Markdown supported. |
Set a case's category.
| Parameter | Location | Type | Required | Description |
|---|---|---|---|---|
case_id |
with |
string | Yes | Case ID. |
category |
with |
string | Yes | Category name. |
owner |
with |
string | No | Case owner. Optional, helps with auto-completion. |
Set a single custom-field value on a case. The field_name parameter is the system-set custom-field identifier (a UUID-style string), not the human-readable label you see in the UI.
| Parameter | Location | Type | Required | Description |
|---|---|---|---|---|
case_id |
with |
string | Yes | Case ID. |
field_name |
with |
string | Yes | Custom-field identifier. System-set, typically a UUID. |
value |
with |
string \| number \| boolean |
Yes | Field value. |
owner |
with |
string | No | Case owner. Optional, helps with auto-completion. |
version |
with |
string | No | Case version for optimistic concurrency. |
- name: set_investigation_owner
type: cases.setCustomField
with:
case_id: "{{ steps.create_case.output.case.id }}"
field_name: "4b8c9d2e-1a5f-4f7a-9c3b-2d6e8f1a3b5c"
value: "soc-team"
Close a case.
| Parameter | Location | Type | Required | Description |
|---|---|---|---|---|
case_id |
with |
string | Yes | Case ID. |
version |
with |
string | No | Case version for optimistic concurrency. |
- name: close_case
type: cases.closeCase
with:
case_id: "{{ steps.find.output.cases[0].id }}"
To record why a case was closed, pair with cases.addComment beforehand:
- name: note_reason
type: cases.addComment
with:
case_id: "{{ inputs.case_id }}"
comment: "Closing: duplicate of case {{ inputs.duplicate_of }}."
- name: close
type: cases.closeCase
with:
case_id: "{{ inputs.case_id }}"
Add a Markdown-formatted comment to a case.
| Parameter | Location | Type | Required | Description |
|---|---|---|---|---|
case_id |
with |
string | Yes | Case ID. |
comment |
with |
string | Yes | Comment body. |
- name: add_triage_note
type: cases.addComment
with:
case_id: "{{ steps.create_case.output.case.id }}"
comment: |
AI classification: **{{ steps.classify.output.category }}**
Rationale: {{ steps.classify.output.rationale }}
Attach detection alerts to a case. Each entry is an object with alertId and index; the optional rule describes the rule that generated the alert.
| Parameter | Location | Type | Required | Description |
|---|---|---|---|---|
case_id |
with |
string | Yes | Case ID. |
alerts |
with |
array | Yes | Array of { alertId, index, rule? } objects. |
- name: attach_alert
type: cases.addAlerts
with:
case_id: "{{ steps.create_case.output.case.id }}"
alerts:
- alertId: "{{ event.alerts[0]._id }}"
index: "{{ event.alerts[0]._index }}"
rule:
id: "{{ event.rule.id }}"
name: "{{ event.rule.name }}"
alerts takes an array of objects, not an array of ID strings. Each alert object requires both alertId and the index where the alert document lives. Cases uses both to resolve the alert document.
Attach arbitrary event documents to a case. Each entry is { eventId, index }.
| Parameter | Location | Type | Required | Description |
|---|---|---|---|---|
case_id |
with |
string | Yes | Case ID. |
events |
with |
array | Yes | Array of { eventId, index } objects. |
- name: attach_context_events
type: cases.addEvents
with:
case_id: "{{ steps.create_case.output.case.id }}"
events:
- eventId: "{{ steps.search_context.output.hits.hits[0]._id }}"
index: "{{ steps.search_context.output.hits.hits[0]._index }}"
Add observables (indicators of compromise such as IPs, file hashes, domains, or URLs) to a case. typeKey must match one of the built-in observable types in Elastic Security.
| Parameter | Location | Type | Required | Description |
|---|---|---|---|---|
case_id |
with |
string | Yes | Case ID. |
observables |
with |
array | Yes | Array of { typeKey, value, description? } objects. |
- name: add_iocs
type: cases.addObservables
with:
case_id: "{{ steps.create_case.output.case.id }}"
observables:
- typeKey: "observable-type-ipv4"
value: "{{ event.alerts[0].source.ip }}"
description: "Source of malicious activity"
- typeKey: "observable-type-hash-sha256"
value: "{{ event.alerts[0].file.hash.sha256 }}"
The typeKey must match one of the built-in observable type keys. Examples of accepted values include: observable-type-ipv4, observable-type-ipv6, observable-type-url, observable-type-domain, observable-type-hash-sha256, observable-type-hash-md5
Fetch every attachment associated with a case without pagination. Use this when you need the complete set of attachments for decision-making, for example when checking evidence before closing or escalating.
| Parameter | Location | Type | Required | Description |
|---|---|---|---|---|
case_id |
with |
string | Yes | Case ID. |
The output is a list of attachments associated with the case you specified.
- name: list_attachments
type: cases.getAllAttachments
with:
case_id: "{{ inputs.case_id }}"
Add tags to a case.
| Parameter | Location | Type | Required | Description |
|---|---|---|---|---|
case_id |
with |
string | Yes | Case ID. |
tags |
with |
string[] |
Yes | Tags to add. |
Assign a case to one or more users.
| Parameter | Location | Type | Required | Description |
|---|---|---|---|---|
case_id |
with |
string | Yes | Case ID. |
assignees |
with |
array | Yes | Array of { uid } objects. |
- name: assign_oncall
type: cases.assignCase
with:
case_id: "{{ steps.create_case.output.case.id }}"
assignees:
- uid: "{{ consts.oncall_uid }}"
Remove assignees from a case.
| Parameter | Location | Type | Required | Description |
|---|---|---|---|---|
case_id |
with |
string | Yes | Case ID. |
assignees |
with |
array | Yes | Array of { uid } objects to remove. Pass an empty array ([]) to remove all assignees from the case. |
Update an existing observable on a case.
| Parameter | Location | Type | Required | Description |
|---|---|---|---|---|
case_id |
with |
string | Yes | Case ID. |
observable_id |
with |
string | Yes | Observable ID. |
value |
with |
string | Yes | Updated value. |
description |
with |
string \| null |
No | Updated description. |
Remove an observable from a case.
| Parameter | Location | Type | Required | Description |
|---|---|---|---|---|
case_id |
with |
string | Yes | Case ID. |
observable_id |
with |
string | Yes | Observable ID. |
Delete one or more cases permanently, including their attachments and comments.
| Parameter | Location | Type | Required | Description |
|---|---|---|---|---|
case_ids |
with |
string[] |
Yes | Array of case IDs to delete. |
- name: cleanup
type: cases.deleteCases
with:
case_ids: ["old-case-1", "old-case-2"]
Deleted cases can't be recovered. If you don't want to permanently delete a case, use cases.closeCase instead.
In 9.3, case management lived under the kibana.* namespace. Those step types remain as deprecated aliases so existing workflows keep running, but new workflows must use cases.*:
| Deprecated (9.3 alias) | Current (9.4) |
|---|---|
kibana.createCaseDefaultSpace |
cases.createCase |
kibana.getCaseDefaultSpace |
cases.getCase |
kibana.updateCaseDefaultSpace |
cases.updateCase |
kibana.addCaseCommentDefaultSpace |
cases.addComment |
Refer to Migrate from 9.3 to 9.4 for side-by-side replacement patterns.
- Alert triggers: The primary source of cases in a SOC workflow.
- AI steps: Use an AI classification or summary to enrich a case description.
- Kibana Cases documentation: The Cases feature in Elastic Security.