Alert rules API
An alert rule runs one or more J1QL queries on a schedule, evaluates the results against a filter, and runs a configured set of actions when the filter matches. Actions can create alerts, send email or Slack, open Jira tickets, call webhooks, publish to AWS messaging services, and more.
This page covers two things:
- API operations — the GraphQL mutations and queries for managing rules programmatically.
- Rule definition reference — the full schema of a rule's body: properties, operations, every action type, the templating language, parameters, and filtering.
For the conceptual product overview, see Alerts and rules.
API operations
List alert instances
Returns the active, inactive, or dismissed alerts produced by your rules.
query ListAlertInstances(
$alertStatus: AlertStatus
$limit: Int
$cursor: String
) {
listAlertInstances(
alertStatus: $alertStatus
limit: $limit
cursor: $cursor
) {
instances {
id
accountId
ruleId
level
status
lastUpdatedOn
lastEvaluationBeginOn
lastEvaluationEndOn
createdOn
dismissedOn
lastEvaluationResult {
rawDataDescriptors {
recordCount
}
}
questionRuleInstance {
id
name
description
question {
queries {
query
name
}
}
}
}
pageInfo {
endCursor
hasNextPage
}
}
}
Variables:
{
"alertStatus": "ACTIVE"
}
{
"alertStatus": "INACTIVE"
}
{
"alertStatus": "DISMISSED"
}
{
"limit": 10
}
To paginate through the results, pass the endCursor received in the response
as the cursor variable in the request. If endCursor is null then there are no more results to retrieve.
Create an inline alert rule from J1QL
This operation was formerly named createQuestionRuleInstance. That name is
now deprecated, and you should update all usages.
mutation CreateInlineQuestionRuleInstance(
$instance: CreateInlineQuestionRuleInstanceInput!
) {
createInlineQuestionRuleInstance(instance: $instance) {
id
name
description
version
pollingInterval
question {
queries {
query
version
}
}
operations {
when
actions
}
outputs
}
}
Variables:
{
"instance": {
"name": "unencrypted-prod-data",
"description": "Data stores in production tagged critical and unencrypted",
"version": "v1",
"pollingInterval": "ONE_DAY",
"outputs": ["alertLevel"],
"operations": [
{
"when": {
"type": "FILTER",
"version": 1,
"condition": [
"AND",
["queries.unencryptedCriticalData.total", "!=", 0]
]
},
"actions": [
{
"type": "SET_PROPERTY",
"targetProperty": "alertLevel",
"targetValue": "CRITICAL"
},
{
"type": "CREATE_ALERT"
}
]
}
],
"question": {
"queries": [
{
"query": "Find DataStore with (production=true or tag.Production=true) and classification='critical' and encrypted!=true as d return d.tag.AccountName as Account, d.displayName as UnencryptedDataStores, d._type as Type, d.encrypted as Encrypted",
"version": "v1",
"name": "unencryptedCriticalData"
}
]
}
}
}
Note that the recommended interval for query based alert rules (aka a question) is ONE_DAY. \
Supported intervals for enterprise customers are:
DISABLED, THIRTY_MINUTES, ONE_HOUR, FOUR_HOURS, EIGHT_HOURS,
TWELVE_HOURS, ONE_DAY, and ONE_WEEK.
Free accounts only have access to the ONE_WEEK interval by default, but
any upgrades to Compliance, Security, or Integrations will provide access
to the ONE_DAY polling interval too.
Update an inline alert rule
This operation was formerly named updateQuestionRuleInstance. That name is
now deprecated, and you should update all usages.
mutation UpdateInlineQuestionRuleInstance(
$instance: UpdateInlineQuestionRuleInstanceInput!
) {
updateInlineQuestionRuleInstance(instance: $instance) {
id
name
description
version
pollingInterval
question {
queries {
query
version
}
}
operations {
when
actions
}
outputs
}
}
Variables:
{
"instance": {
"id": "b1c0f75d-770d-432a-95f5-6f59b4239c72",
"name": "unencrypted-prod-data",
"description": "Data stores in production tagged critical and unencrypted",
"version": "v1",
"pollingInterval": "ONE_DAY",
"outputs": ["alertLevel"],
"operations": [
{
"when": {
"type": "FILTER",
"version": 1,
"condition": [
"AND",
["queries.unencryptedCriticalData.total", "!=", 0]
]
},
"actions": [
{
"type": "SET_PROPERTY",
"targetProperty": "alertLevel",
"targetValue": "CRITICAL"
},
{
"type": "CREATE_ALERT"
}
]
}
],
"question": {
"queries": [
{
"query": "Find DataStore with (production=true or tag.Production=true) and classification='critical' and encrypted!=true as d return d.tag.AccountName as Account, d.displayName as UnencryptedDataStores, d._type as Type, d.encrypted as Encrypted",
"version": "v1",
"name": "unencryptedCriticalData"
}
]
}
}
}
Note that the only difference for update is the "id" property
associated with the rule instance. You can modify all settings of a rule instance.
Create an alert rule by referencing a saved question
mutation CreateReferencedQuestionRuleInstance(
$instance: CreateReferencedQuestionRuleInstanceInput!
) {
createReferencedQuestionRuleInstance(instance: $instance) {
id
name
description
version
pollingInterval
questionId
questionName
operations {
when
actions
}
outputs
}
}
Variables:
{
"instance": {
"name": "unencrypted-prod-data",
"description": "Data stores in production tagged critical and unencrypted",
"version": "v1",
"pollingInterval": "ONE_DAY",
"outputs": ["alertLevel"],
"operations": [
{
"when": {
"type": "FILTER",
"version": 1,
"condition": [
"AND",
["queries.unencryptedCriticalData.total", "!=", 0]
]
},
"actions": [
{
"type": "SET_PROPERTY",
"targetProperty": "alertLevel",
"targetValue": "CRITICAL"
},
{
"type": "CREATE_ALERT"
}
]
}
],
"questionId": "uuid-of-saved-question",
"questionName": "name-of-saved-question" // either questionId or questionName must be specified
}
}
Note that you must specify either questionName or questionId in the instance for creation.
If you specify both, they must refer to the same question. After the rule is saved, subsequent requests will return both questionId and questionName.
Update an alert rule with a referenced question
mutation UpdateReferencedQuestionRuleInstance(
$instance: UpdateReferencedQuestionRuleInstanceInput!
) {
updateReferencedQuestionRuleInstance(instance: $instance) {
id
name
description
version
pollingInterval
questionId
questionName
operations {
when
actions
}
outputs
}
}
Variables:
{
"instance": {
"id": "b1c0f75d-770d-432a-95f5-6f59b4239c72",
"name": "unencrypted-prod-data",
"description": "Data stores in production tagged critical and unencrypted",
"version": "v1",
"pollingInterval": "ONE_DAY",
"outputs": ["alertLevel"],
"operations": [
{
"when": {
"type": "FILTER",
"version": 1,
"condition": [
"AND",
["queries.unencryptedCriticalData.total", "!=", 0]
]
},
"actions": [
{
"type": "SET_PROPERTY",
"targetProperty": "alertLevel",
"targetValue": "CRITICAL"
},
{
"type": "CREATE_ALERT"
}
]
}
],
"questionId": "uuid-of-saved-question",
"questionName": "name-of-saved-question"
}
}
Note that the only difference in update is the "id" property
associated with the rule instance. You can modify any of the settings of
a rule instance. Updates are not required to specify questionId or questionName, but you can specify either for update, and if you specify both they must refer to the same saved question.
Delete an alert rule
You can use this operation to delete any rule instance, regardless of whether it uses an inline question or a referenced question.
mutation DeleteRuleInstance($id: ID!) {
deleteRuleInstance(id: $id) {
id
}
}
Variables:
{
"id": "b1c0f75d-770d-432a-95f5-6f59b4239c72"
}
Deleting an alert rule this way does not dismiss active alerts already triggered by this rule. It is recommended that you Disable the rule from the Rules page instead of deleting one.
Trigger an alert rule on demand
mutation EvaluateRuleInstance($id: ID!) {
evaluateRuleInstance(id: $id) {
outputs {
name
value
}
}
}
Variables:
{
"id": "b1c0f75d-770d-432a-95f5-6f59b4239c72"
}
Rule definition reference
A rule uses the results of one or more queries to execute one or more actions. You can author the rule body in the JupiterOne UI, or pass it directly into the API operations above.
Configuring a rule
- Navigate to the JupiterOne Rules page (https://apps.us.jupiterone.io/rules)
- Click New rule
- Click Advanced editor (JSON) to open the advanced rule editor.
JSON Example:
{
"name": "unencrypted-critical-data-stores",
"description": "Unencrypted data store with classification label of 'critical' or 'sensitive' or 'confidential' or 'restricted'",
"version": 1,
"specVersion": 1,
"pollingInterval": "ONE_DAY",
"question": {
"queries": [
{
"name": "query0",
"query": "Find DataStore with classification=('critical' or 'sensitive' or 'confidential' or 'restricted') and encrypted!=true",
"version": "v1"
}
]
},
"operations": [
{
"when": {
"type": "FILTER",
"condition": "{{queries.query0.total > 0}}"
},
"actions": [
{
"type": "CREATE_ALERT"
}
]
}
],
"outputs": ["queries.query0.total", "alertLevel"]
}
You can also configure rules to include deleted data in the results. For example:
// ...
"question": {
"queries": [
{
"name": "query0",
"query": "Find DataStore with classification='critical' and encrypted=false as d return d.tag.AccountName as Account, d.displayName as UnencryptedDataStores, d._type as Type, d.encrypted as Encrypted",
"version": "v1",
"includeDeleted": true
},
{
"name": "query1",
"query": "...",
"version": "v1",
"includeDeleted": false
},
{
"name": "query2",
"query": "...",
"version": "v1"
}
]
},
// ...
}
Rule properties
| Property | Type | Description |
|---|---|---|
id | string | Auto-generated, globally unique ID of each rule. |
version | number | Current version of the rule. Incremented each time the rule is updated. |
name | string | Name of the rule, which is unique to each account. |
description | string | Optional description of the rule. |
specVersion | number | Rule evaluation version in the case of breaking changes. This should always be 1. |
pollingInterval | PollingInterval | Optional frequency of automated rule evaluation. Defaults to ONE_DAY. |
question | Question | Contains properties related to queries used in the rule evaluation. |
questionId | string | A known unique ID for a question in the question library. |
operations | RuleOperation[] | Actions that are executed when a corresponding condition is met. |
templates | object | Optional key/value pairs of template name to template. |
outputs | string[] | Names of properties that can be used throughout the rule evaluation process and will be included in each record of a rule evaluation (for example, queries.query0.total). |
notifyOnFailure | boolean | Will send a notification to stakeholders (account admins, assigned users) if the rule query or any of its actions does not succeed. This property defaults to true if not provided when creating a rule. |
triggerActionsOnNewEntitiesOnly | boolean | Will only trigger actions to be run when entities that did not exist during the previous rule evaluation appear in the question results. This property defaults to true if not provided when creating a rule. |
PollingInterval
Enumeration of the scheduled frequencies on which rules will automatically be evaluated.
Possible values are DISABLED, THIRTY_MINUTES, ONE_HOUR, FOUR_HOURS, EIGHT_HOURS, TWELVE_HOURS, ONE_DAY, and ONE_WEEK.
Question and queries
Question
A Question contains a collection of named queries that should be executed during
the rule evaluation process and whose responses can be used in any
RuleOperation.
| Property | Type | Description |
|---|---|---|
queries | QuestionQuery[] | The collection of queries that are used during the rule evaluation. |
QuestionQuery
A named query that should be executed during the rule evaluation process and
whose responses can be used in any RuleOperation.
| Property | Type | Description |
|---|---|---|
name | string | Optional name to assign the query that will be used when referencing query data in RuleOperations. If not provided, the query name is automatically assigned based on the index in the queries array (for example, query0, query1). |
query | string | JupiterOne query to execute. |
version | string | JupiterOne query language execution version (for example, v1). |
includeDeleted | boolean | Whether deleted data should be considered for the specific query (defaults to false). |
Operations
A RuleOperation is a single condition and series of actions that are
executed when the condition is met.
| Property | Type | Description |
|---|---|---|
when | RuleOperationCondition|RuleOperationCondition[] | Type of conditional used to determine whether the associated actions should be executed. |
actions | RuleOperationAction[] | Actions that should be executed when the when conditions have been met. |
RuleOperationCondition
The condition that determines whether the associated actions should be executed.
The type of RuleOperationCondition is determined using the type property.
FilterRuleOperationCondition
| Property | Type | Description |
|---|---|---|
type | string | Rule operation condition type: FILTER. |
condition | string | Template condition (for example, {{queries.query0.total > 0}}). |
Actions
Action that is executed when a corresponding condition is met. The type of
RuleOperationAction is determined using the type property.
SET_PROPERTY
Includes a property that can be used in rule evaluation input.
| Property | Type | Description |
|---|---|---|
type | string | Rule operation action type: `SET_PROPERTY. |
targetProperty | string | Property to include in the evaluation input. |
targetValue | `number | string |
Example:
{
"type": "SET_PROPERTY",
"targetProperty": "alertLevel",
"targetValue": "CRITICAL"
}
CREATE_ALERT
Creates a JupiterOne alert that is visible in J1 Alerts.
| Property | Type | Description |
|---|---|---|
type | string | Rule operation action type: CREATE_ALERT |
Example:
{
"type": "CREATE_ALERT"
}
TAG_ENTITIES
Adds queryable tag values to result entities. With a value of
null, the tag will be removed.
| Property | Type | Description |
|---|---|---|
type | string | Rule operation action type: TAG_ENTITIES . |
entities | obj[] | Array of result entities with J1 metadata. Generally a direct reference to a query result set, e.g. {{queries.query0.data}} |
tags | obj[] | Array of objects containing name and value properties specifying tags to add. The value can be any JSON primitive. |
Note:
Depending on result count, tags may take up to 10 minutes after rule evaluation completes to be available for query.
Example:
{
"type": "TAG_ENTITIES",
"entities": "{{queries.query0.data}}",
"tags": [ { "name": "myTag", "value":"tag-value" } ]
}
SEND_EMAIL
Sends an email to a list of recipients with details related to alerts that are created during the rule evaluation.
| Property | Type | Description |
|---|---|---|
type | string | Rule operation action type: SEND_EMAIL. |
recipients | string[] | Email addresses of the recipients of this alert. |
body | string | Optional additional body information of the email. |
Example:
{
"type": "SEND_EMAIL",
"body": "Number of items above threshold: {{queries.query0.total}}",
"recipients": ["recipient@example.com"]
}
CREATE_JIRA_TICKET
Creates a Jira ticket using a specific JupiterOne Jira integration configuration.
| Property | Type | Description |
|---|---|---|
type | string | Rule operation action type: CREATE_JIRA_TICKET. |
integrationInstanceId | string | The id of the JupiterOne Jira integration that should be used to create the ticket. |
entityClass | string | The class of the new ticket entity that should be created in JupiterOne. (for example,Vulnerability) |
project | string | The unique Jira project ID that the ticket is created in. |
summary | string | Summary of the Jira ticket. Used as the ticket title. |
issueType | string | The Jira issue type (for example, Task). |
additionalFields | object | Optional additional fields that are passed directly to the Jira API. (see table below for details) |
createOnlyFields | array | Optional list of top-level additionalFields keys set when the ticket is created and never rewritten. Use it for computed values, such as a due date, that must not shift on later runs. (see computed additional fields) |
updateContentOnChanges | boolean | Optional (default false). When true, later runs update the ticket already created for this rule instead of creating a new one. |
autoResolve | boolean | Optional (default false). When true, the ticket created for this rule is transitioned to resolvedStatus once the query returns no matching results. |
resolvedStatus | string | The Jira status the ticket is transitioned to when autoResolve is true (for example, Closed). Required for auto-resolve to take effect. |
Note: By default (when both
updateContentOnChangesandautoResolvearefalse) this action creates a new Jira ticket on every run that matches results. Set either flag to have JupiterOne reuse the ticket it already created for the rule instead of creating duplicates.
Jira description field
The description field can have a raw string value or be passed as depicted in
these examples as Jira
ADF.
NOTE: string in either the text or description keys supports markdown
syntax.
Computed additional fields
additionalFields values support the full
rule evaluation templating language,
including custom transforms, at any depth. This
is how a mandatory date field is filled relative to each ticket instead of being
hardcoded to a date that was current when the rule was written:
{
"type": "CREATE_JIRA_TICKET",
"integrationInstanceId": "<JIRA_INTEGRATION_INSTANCE_ID>",
"entityClass": "Vulnerability",
"project": "81198",
"summary": "Ticket summary",
"issueType": "Task",
"updateContentOnChanges": true,
"additionalFields": {
"custom_date": "{{ evaluationBeginOn | dateAdd(90, 'days') | formatDate('YYYY-MM-DD') }}"
},
"createOnlyFields": ["custom_date"]
}
evaluationBeginOn is the timestamp of the run that creates the ticket — see
evaluation reference variables — and
dateAdd and
formatDate shift and format it.
Whenever updateContentOnChanges is true, list computed fields in
createOnlyFields. That flag re-sends every additionalFields value to the
existing ticket on each run, so a value derived from the evaluation time is
rewritten every time: a due date 90 days out would move 90 days further out on
every run and never come due. Naming the field in createOnlyFields fixes its
value at the moment the ticket was created, while the description keeps updating.
autoResolve on its own needs createOnlyFields too. With
updateContentOnChanges off, the ticket is matched each run by a fingerprint
that includes the rendered additionalFields. A computed value changes it every
run, so the action stops recognizing the ticket it opened last time — leaving
that one unresolved and opening another. Create-only fields are left out of the
fingerprint.
Freezing every field stops updates. With nothing left to send, no update is
made — and the description is an additionalFields key, so freezing everything
freezes it too.
Create-only values are never back-filled. Adding a field to the list later does not write it to a ticket that already exists.
Note: with neither
updateContentOnChangesnorautoResolveset, every matching run creates a new ticket (see the note above), and each of those tickets gets a date computed from its own run. If you want a single ticket whose date is fixed when it is created, setupdateContentOnChangesand name the field increateOnlyFields.
Other / custom additional fields
Fields passed into additionalFields will be passed directly to the Jira API
and as such should match the required input format of each field type.
This table outlines some of the common field types and their value formats.
Please use the
Official Jira Rest API
for more information.
| Field Type Label | Schema Type | Input Format | Example |
|---|---|---|---|
| Text | textfield | String value | "Value of Field" |
| Number | number | Number value | 2 |
| Date | date | YYYY-MM-DD string | "2026-10-30" |
| DateTime | datetime | ISO 8601 string with numeric offset | "2026-10-30T00:00:00.000+0000" |
| Select | select | Object with value key | { "value": "Select Option Label"} |
| MultiSelect | multiselect | Array of string values | ["Option 1", "Option 2"] |
| MultiCheckBoxes | multicheckboxes | Array of Objects with value keys | [{ "value": "Option 1" }, { "value": "Option 2"}] |
| UserPicker | userpicker | Object with accountId | { "accountId": "userInternalId" } |
| MultiUserPicker | multiuserpicker | Array of Objects with accountId keys | [{ "accountId": "user1InternalId" }, { "accountId": "user2InternalId"}] |
Example:
{
"type": "CREATE_JIRA_TICKET",
"integrationInstanceId": "<JIRA_INTEGRATION_INSTANCE_ID>",
"entityClass": "Vulnerability",
"project": "81198",
"summary": "Ticket summary",
"issueType": "Task",
"additionalFields": {
"custom_text": "field_value",
"custom_number": 2,
"custom_select": {
"value": "Select Option Label"
},
"custom_multi_select": ["Option 1", "Option 2"],
"custom_multi_checkboxes": [
{
"value": "Option 1"
},
{
"value": "Option 2"
}
],
"custom_user_picker": {
"accountId": "usersInternalId"
},
"custom_multi_user_picker": [
{
"accountId": "user1InternalId"
},
{
"accountId": "user2InternalId"
}
],
"description": {
"type": "doc",
"version": 1,
"content": [
{
"type": "paragraph",
"content": [
{
"type": "text",
"text": "Jira description here! **Full Markdown Supported Text**"
}
]
}
]
}
}
}
FOR_EACH_ITEM
Runs a set of actions for each item in a list. This list can be query results, or a composed list of items.
| Property | Type | Description |
|---|---|---|
type | string | Rule operation action type: FOR_EACH_ITEM. |
itemRef | string | Optional name of the item reference to use in the templates. Defaults to item |
items | string | The list of items to iterate over. Can be a template (see example). |
actions | array | The actions to run for each item. |
Examples:
// will call POST https://example.com with the body { "name": "John Doe", "webLink": "https://apps.<region>.jupiterone.io/asset/reference/link" }
// for each user in the query results, e.g. `Find User`
{
"type": "FOR_EACH_ITEM",
"itemRef": "user",
"items": "{{queries.query0.data}}",
"actions": [
{
"type": "WEBHOOK",
"method": "POST",
"url": "https://example.com",
"body": {
"name": "{{user.properties.name}}",
"webLink": "{{user.properties.webLink}}"
},
}
]
}
RETURN statement (i.e. Find User that USES Device as d RETURN d.name), you must alias the returned properties to use them inside the body of the action.Updated query: Find User that USES Device as d RETURN d.name as deviceName
{
"type": "FOR_EACH_ITEM",
"itemRef": "device",
"items": "{{queries.query0.data}}",
"actions": [
{
"type": "WEBHOOK",
"method": "POST",
"url": "https://example.com",
"body": {
"name": "{{device.deviceName}}"
}
}
]
}
JUPITERONE_QUERY
Runs a JupiterOne query and stores the results on the
queriestemplate parameter with a givenname. It is recommended that you only use this action within aFOR_EACH_ITEMaction. Including it in normaloperationsactions is not recommended, as you can retrieve results you want with the normalquestionqueries.
| Property | Type | Description |
|---|---|---|
type | string | Rule operation action type: JUPITERONE_QUERY. |
query | string | The JupiterOne query to run. |
name | string | The name of the query result to store. |
Example:
//
{
"type": "FOR_EACH_ITEM",
"itemRef": "user",
"items": "{{queries.query0.data}}",
"actions": [
// JUPITERONE_QUERY actions always run first in order to enable the use of the results in the templates
// of other actions in a FOR_EACH_ITEM action
{
"type": "JUPITERONE_QUERY",
"query": "Find Account with email='{{user.properties.email}}'",
"name": "accountsOwnedByUserQuery"
},
{
"type": "WEBHOOK",
"method": "POST",
"url": "https://example.com",
"body": {
"name": "{{user.properties.displayName}}",
"accounts": "{{queries.accountsOwnedByUserQuery.data | mapProperty('displayName')}}"
}
}
]
}
SEND_SLACK_MESSAGE
Sends a Slack message to a given Slack webhook URL.
| Property | Type | Description |
|---|---|---|
integrationInstanceId | string | The id of the JupiterOne Jira integration used to create the ticket. |
type | string | Rule operation action type: SEND_SLACK_MESSAGE. |
channels | string | A string or list of strings beginning with a # to denote Slack channels to send to. |
webhookUrl | string | Webhook URL for the account/channel that this message should be delivered to. |
severity | string | Optional severity of this alert that determined the color of the message shown in Slack. |
NOTE: By default, the color of the alert in Slack is derived from the value
of the alertLevel that is created in a SET_PROPERTY action. You can override
the color of the alert using the severity property.
Example:
After you have configured the integration, copy the integration ID from the
integration instance page, which looks similar to
d1549f40-b9fd-447a-bec5-4360c9ca7e8c.
Configure a rule with the SEND_SLACK_MESSAGE action and specify the integrationInstanceId with the unique identifier of the integration and channels denoting the destinations. The following is an example alert rule configuration with the SEND_SLACK_MESSAGE action:
NOTE: For the JupiterOne Slack bot to deliver messages to a private Slack channel, the JupiterOne Slack bot must be a member of that private channel.
{
"id": "83136d41-23d0-415c-8726-84363d5a8a30",
"name": "acm-cert-expiry",
"description": null,
"version": 1,
"specVersion": 1,
"notifyOnFailure": null,
"triggerActionsOnNewEntitiesOnly": null,
"pollingInterval": "ONE_DAY",
"templates": {},
"outputs": [
"alertLevel"
],
"question": {
"queries": [
{
"query": "Find aws_acm_certificate with inUse = true and expiresOn > date.now and expiresOn < date.now + 30days",
"name": "query0",
"version": "v1",
"includeDeleted": false
}
]
},
"questionId": null,
"operations": [
{
"when": {
"type": "FILTER",
"specVersion": 1,
"condition": [
"AND",
[
"queries.query0.total",
">",
0
]
]
},
"actions": [
{
"targetValue": "CRITICAL",
"id": "e1d40781-831f-43bf-b813-b28b6f2218ef",
"type": "SET_PROPERTY",
"targetProperty": "alertLevel"
},
{
"type": "CREATE_ALERT",
"id": "a3a32646-db5c-4c18-89be-c02798cd84c4"
},
{
"integrationInstanceId": "8d677b84-c32e-45d3-9b46-902912a00304",
"id": "c1a3a9bc-7c4f-4eba-afff-528f9cbd2ff1",
"type": "SEND_SLACK_MESSAGE",
"body": "*Affected Items:* \n\n- {{queries.query0.data|mapProperty('displayName')|join('\n- ')}}",
"channels": []
}
]
}
],
"state": null,
"tags": []
}
WEBHOOK
Sends an HTTP request to a given endpoint.
| Property | Type | Description |
|---|---|---|
type | string | Rule operation action type: WEBHOOK |
endpoint | string | Webhook endpoint to send the request to. |
method | string | HTTP method to use when making the request Allowed values: POST, PUT, GET, HEAD, PATCH, DELETE. |
body | object | Optional body data to include in the request. Can only be used with POST, PUT, and PATCH. |
headers | object | Optional HTTP headers to include in the request. |
Webhook reference variables
You can reference the following variables via a template pattern (such as {{alertLevel}}) inside the webhook action:
| Property | Type | Description |
|---|---|---|
alertLevel | string | Level of severity of the rule. |
alertRuleName | string | Name of the alert rule. |
alertRuleId | string | Identifier for the alert in the J1 platform. |
alertRuleDescription | string | Description saved in the rule. |
Example:
{
"type": "WEBHOOK",
"method": "POST",
"body": {
"myApiPayload": " {{alertLevel}} alert has been triggered: {{alertRuleName}} "
},
"headers": {
"Authorization": "Bearer abc123"
}
}
Tines trigger
If you opt to use a Tines alert action when you create a rule, J1 creates a webhook with the Tines URL you provided and pushes the data to that endpoint. You can use any of the Tines APIs to configure the webhook action.
PUBLISH_SNS_MESSAGE
Publishes a message to the specified SNS topic.
| Property | Type | Description |
|---|---|---|
type | string | Rule operation action type: PUBLISH_SNS_MESSAGE. |
integrationInstanceId | string | The ID of the AWS integration instance to use. The integration role must have sns:Publish permission. |
topicArn | string | The ARN of the SNS topic to publish the message to. |
data | object | User-provided data to include in the message. See Operation Templating for details on using variable data. |
Example:
{
"type": "PUBLISH_SNS_MESSAGE",
"integrationInstanceId": "<AWS_INTEGRATION_INSTANCE_ID>",
"topicArn": "arn:aws:sns:<REGION>:arn:aws:sns:<ACCOUNT_ID>:<SNS_TOPIC_NAME>",
"data": {
"query0Data": "{{queries.query0.data}}",
"anotherCustomProperty": true
}
}
!!! Note:
data is stringified in the payload. For example:
{
Sns: {
Message: '{"data":{"query0Data": ..., "anotherCustomProperty": true}}';
}
}
SEND_SQS_MESSAGE
Publishes a message to the specified SQS queue.
| Property | Type | Description |
|---|---|---|
type | string | Rule operation action type: SEND_SQS_MESSAGE. |
integrationInstanceId | string | The ID of the AWS integration instance to use. The integration role must have sqs:SendMessage permission. |
queueUrl | string | The URL of the SQS queue to publish the message to. |
data | object | User-provided data to include in the message. See Operation Templating for details on using variable data. |
Example:
{
"type": "SEND_SQS_MESSAGE",
"integrationInstanceId": "<AWS_INTEGRATION_INSTANCE_ID>",
"queueUrl": "https://sqs.<REGION>.amazonaws.com/<ACCOUNT_ID>/<SQS_QUEUE_NAME>",
"data": {
"query0Data": "{{queries.query0.data}}",
"anotherCustomProperty": true
}
}
!!! warning data is stringified in the payload. For example:
{
body: '{"data":{"query0Data": ..., "anotherCustomProperty": true}}';
}
SEND_TO_S3
Uploads data to an AWS S3 bucket.
| Property | Type | Description |
|---|---|---|
type | string | Rule operation action type: SEND_TO_S3. |
integrationInstanceId | string | The ID of the AWS integration instance to use. The integration role must have s3:PutObject permission. |
bucket | string | The name of the bucket to upload the data to. |
region | string | The region in which the S3 bucket is located. |
data | object | User-provided data to include in the message. See Operation Templating for details on using variable data. |
NOTE: The data will be a json file with a filename of ${ruleId}_${evaluationTimestampMilliseconds}.json. This may look like 83136d41-23d0-415c-8726-84363d5a8a30_1631619200000.json.
Example:
{
"type": "SEND_TO_S3",
"integrationInstanceId": "<AWS_INTEGRATION_INSTANCE_ID>",
"bucket": "s3-bucket-name",
"region": "us-east-1",
"data": {
"description": "**Affected Items:**\n\n* {{queries.query0.data|mapProperty('displayName')|join('\n* ')}}"
}
}
Patterns
Multiple queries in a single rule
You can pass multiple queries into an alert rule that allows each query to output its results into the same, single alert.
Note the
whencondition in the example below will invoke actions if either query returns results.
This example shows multiple queries sending out an email alert to multiple recipients:
{
"name": "Multiple Queries in a Rule",
"description": "",
"version": 1,
"specVersion": 1,
"pollingInterval": "ONE_WEEK",
"templates": {
"tempMap": "Project: {{item.Project}}, ProjectFindings: {{item.ProjectFindings}}, RepoFindings: {{item.RepoFindings}}"
},
"outputs": [
"alertLevel"
],
"question": {
"queries": [
{
"name": "query0",
"query": "Find CodeRepo THAT RELATES TO Project with repoName!=undefined as p THAT HAS Finding as f RETURN p.repoName as Project, count(f) as ProjectFindings",
"version": "v1",
"includeDeleted": false
},
{
"name": "query1",
"query": "Find Project with repoName!=undefined THAT RELATES TO CodeRepo as p THAT HAS Finding as f RETURN p.displayName as Project, count(f) as RepoFindings",
"version": "v1",
"includeDeleted": false
}
]
},
"operations": [
{
"when": {
"type": "FILTER",
"specVersion": 1,
"condition": [
"OR",
[
"queries.query0.total",
">",
0
],
[
"queries.query1.total",
">",
0
]
]
},
"actions": [
{
"targetValue": "INFO",
"type": "SET_PROPERTY",
"targetProperty": "alertLevel"
},
{
"type": "CREATE_ALERT"
},
{
"type": "SEND_EMAIL",
"body": "Affected Items: <br><br>* {{ queries.query0.data | mapTemplate('tempMap') | join('<br>* ') }} / <br>* {{ queries.query1.data | mapTemplate('tempMap') | join('<br>* ') }}",
"recipients": [
"person1@example.com",
"person2@example.com",
"person3@example.com"
]
}
]
}
],
"tags": []
}
This example shows multiple queries sending results to Jira to create a single Jira issue:
{
"name": "Multiple Queries in a Rule to Jira Example",
"description": "Multiple queries can be composed into a Jira Alert",
"version": 1,
"specVersion": 1,
"pollingInterval": "ONE_DAY",
"templates": {
"projectInfo": "Project: {{item.Project}}, ProjectFindings: {{item.ProjectFindings}}, RepoFindings: {{item.RepoFindings}}"
},
"outputs": [
"alertLevel"
],
"question": {
"queries": [
{
"name": "query0",
"query": "Find CodeRepo THAT RELATES TO Project with repoName!=undefined as p THAT HAS Finding as f RETURN p.repoName as Project, count(f) as ProjectFindings",
"version": "v1",
"includeDeleted": false
},
{
"name": "query1",
"query": "Find Project with repoName!=undefined THAT RELATES TO CodeRepo as p THAT HAS Finding as f RETURN p.displayName as Project, count(f) as RepoFindings",
"version": "v1",
"includeDeleted": false
}
]
},
"operations": [
{
"when": {
"type": "FILTER",
"specVersion": 1,
"condition": [
"OR",
[
"queries.query0.total",
">",
0
],
[
"queries.query1.total",
">",
0
]
]
},
"actions": [
{
"targetValue": "INFO",
"type": "SET_PROPERTY",
"targetProperty": "alertLevel"
},
{
"type": "CREATE_ALERT"
},
{
"summary": ": {{queries.query0.total}}",
"issueType": "Task",
"entityClass": "Finding",
"integrationInstanceId": "<JIRA_INTEGRATION_INSTANCE_ID>",
"additionalFields": {
"description": {
"type": "doc",
"version": 1,
"content": [
{
"type": "paragraph",
"content": [
{
"type": "text",
"text": "{{alertWebLink}}\n\n**Affected Items:**\n\n* {{ queries.query0.data | mapTemplate('projectInfo') | join('\n* ') }} \n\n***************\n\n {{ queries.query1.data | mapTemplate('projectInfo') | join('\n* ') }}"
}
]
}
]
}
},
"project": "CT",
"type": "CREATE_JIRA_TICKET"
}
]
}
],
"tags": []
}
One Jira ticket per query result with FOR_EACH_ITEM
If your query returns multiple results, you can run a second query using the
results of the first query to create a Jira ticket for each item in the first
query results. You do this by
editing the advanced JSON of the alert rule to use the
FOR_EACH_ITEM action type.
It is not recommended that you use this action type if your results sizes are very large. This example limits the number of possible Jira tickets created to a maximum of 100, and sends an email when the limit is exceeded. Additionally, the "Test" and "Preview" buttons will not include query data in them at this time.
For accessing values within result iterations:
Properties referenced by {{obj.properties.displayName}}
Tags referenced by {{obj.properties['tag.AccountName']}}
Metadata values referenced by {{obj.entity._type}}
For example:
{
"name": "Unencrypted critical data stores",
"description": "",
"specVersion": 1,
"pollingInterval": "ONE_WEEK",
"question": {
"queries": [
{
"name": "query0",
"query": "Find DataStore with classification='critical' and encrypted=false",
"version": "v1",
"includeDeleted": false
}
]
},
"operations": [
{
"when": {
"type": "FILTER",
"specVersion": 1,
"condition": ["AND", ["queries.query0.total", ">", 0], ["queries.query0.total", "<=", 100]]
},
"actions": [
{
"type": "SET_PROPERTY",
"targetValue": "CRITICAL",
"targetProperty": "alertLevel"
},
{
"type": "CREATE_ALERT"
},
{
"itemRef": "obj",
"type": "FOR_EACH_ITEM",
"items": "{{queries.query0.data}}",
"actions": [
{
"type": "CREATE_JIRA_TICKET",
"summary": "{{alertRuleDescription}}",
"issueType": "",
"entityClass": "{{ obj.entity._type | join(',') }}",
"integrationInstanceId": "{{ obj.entity._integrationInstanceId }}",
"additionalFields": {
"description": {
"type": "doc",
"version": 1,
"content": [
{
"type": "paragraph",
"content": [
{
"type": "text",
"text": "{{alertWebLink}}\n\n**Affected Items:**\n\n* {{obj.properties.webLink}}"
}
]
}
]
}
},
"project": "{{param.MySpecialProject}}"
}
]
}
]
}, {
"when": {
"type": "FILTER",
"specVersion": 1,
"condition": ["queries.query0.total", ">", 100]
},
"actions": [
{
"type": "SET_PROPERTY",
"targetValue": "CRITICAL",
"targetProperty": "alertLevel"
},
{
"type": "CREATE_ALERT"
},
{
"type": "SEND_EMAIL",
"body": "The alert rule {{alertRuleName}} has {{queries.query0.total}} results. Please review the results in the JupiterOne app: {{alertWebLink}}.",
"recipients": ["emergency@example.com"]
}
]
}
],
"outputs": ["alertLevel"],
"templates": {}
}
Operation templating
You can use templates inside any property under the operations property on a
rule. The templates can contain a JavaScript-like syntax that automatically have
input variables injected for usage.
For example, FilterRuleOperationConditions are often used with rules as the
condition for whether rule actions should be executed. You can use query
response data inside of the rule conditions:
{
"operations": [
{
"when": {
"type": "FILTER",
// Use the `.total` property from query named `query0`.
"condition": "{{queries.query0.total > 0}}"
},
"actions": [
{
"type": "CREATE_ALERT"
}
]
}
]
}
You can use data from query results inside of rule operations by referencing the
query.query0.data property and custom templating transforms. For example:
{
"name": "lambda-function-settings-check-runtime-nodejs610",
"description": "Node.js 6.10 is end of life (EOL) and should no longer be used.",
"specVersion": 1,
"pollingInterval": "ONE_DAY",
"templates": {
// The email template that we will use later
"emailBody": "({{itemIndex+1}} of {{itemCount}}) [{{item.account}}] Function Name: {{item.functionName}}<br>"
},
"question": {
"queries": [
{
"name": "query0",
"query": "Find aws_lambda_function with runtime='nodejs6.10' as f return f.name as functionName, f.version as version, f.tag.AccountName as account, f.tag.Project as project order by account",
"version": "v1"
}
]
},
"operations": [
{
"when": {
"type": "FILTER",
"specVersion": 1,
"condition": "{{queries.query0.total > 0}}"
},
"actions": [
{
"targetValue": "HIGH",
"type": "SET_PROPERTY",
"targetProperty": "alertLevel"
},
{
"type": "CREATE_ALERT"
},
{
"type": "SEND_EMAIL",
// Reference the `query0` data and include it in a template
"body": "Affected Functions: <br><br>{{ queries.query0.data | mapTemplate('emailBody') | join(' ') }}",
"recipients": ["person1@example.com"]
}
]
}
],
"outputs": ["queries.query0.total", "alertLevel"]
}
Rule evaluation templating language
You can create a template in any RuleOperation using the {{...}} syntax.
Inside the {{...}} is a JavaScript-like language that allows for powerful rule
evaluation functionality. Additionally, if the template contains exactly one
expression and nothing else, the original type of the computed value is
preserved. If multiple expressions are used, the entire value is casted to a
string.
The following is an example where the type boolean is preserved because there
is only a single expression:
{{true}}
The following is an example where the entire value would be cast to a string because it contains multiple expressions:
{{age + 10}} is my age and my name is {{firstName + " " + lastName}}
All templating expressions support references to account parameters:
My name is {{param.myFirstName}} and I am {{age}}
Evaluation reference variables
Alongside query data and account parameters, every operation template can reference the following:
| Variable | Type | Description |
|---|---|---|
evaluationBeginOn | number | Unix timestamp in milliseconds for when the evaluation started. Pair it with dateAdd and formatDate to compute a date relative to the run. |
alertRuleId | string | Identifier of the rule being evaluated. |
alertRuleName | string | Name of the rule being evaluated. |
alertRuleDescription | string | Description saved on the rule. |
alertWebLink | string | Link to the alert raised by this evaluation. Only present when the rule raises one. |
Unary operators
| Operation | Symbol |
|---|---|
| Negate | ! |
Binary operators
| Operation | Symbol |
| ---------------- | :----: | --- | --- |
| Add, Concat | + |
| Subtract | - |
| Multiply | * |
| Divide | / |
| Divide and floor | // |
| Modulus | % |
| Power of | ^ |
| Logical AND | && |
| Logical OR | | | |
Comparisons
| Comparison | Symbol |
|---|---|
| Equal | == |
| Not equal | != |
| Greater than | > |
| Greater than or equal | >= |
| Less than | < |
| Less than or equal | <= |
| Element in array or string | in |
Ternary operator
| Expression | Result |
|---|---|
"" ? "Full" : "Empty" | Empty |
"foo" in "foobar" ? "Yes" : "No" | Yes |
{agent: "Archer"}.agent ?: "Kane" | Archer |
Native types
| Type | Examples |
|---|---|
| Booleans | true, false |
| Strings | "Hello \"user\"", 'Hey there!' |
| Numerics | 6, -7.2, 5, -3.14159 |
| Objects | {hello: "world!"} |
| Arrays | ['hello', 'world!'] |
Groups
Grouping operations with parentheses:
| Expression | Result |
|---|---|
(83 + 1) / 2 | 42 |
| `1 < 3 && (4 > 2 |
Custom templating transforms
Some custom transforms are exposed in the rule templating language. These are functions that perform actions on an input, and can be chained together to accomplish some powerful actions.
mapTemplate(templateName: string)
mapTemplate is separates and reuses templates inside of a rule. The transform
expects a single array and the first argument should be a string whose value
matches a template in rule templates object.
The mapTemplate transform exposes additional input variable to the template:
| Property | Type | Description |
|---|---|---|
item | any | The individual item of this iteration. |
itemCount | number | The total count of items in the array. |
itemIndex | number | The index of the current item in the array |
!!! note The properties that are accessible on the item property are pulled
from the properties object and the entity object if the item matches the
schema for an entity.
Example operation:
{
"type": "SEND_EMAIL",
// Reference the `query0` data and include it in a template
"body": "{{ queries.query0.data | mapTemplate('emailBody') | join(' ') }}",
"recipients": ["person1@example.com"]
}
Example templates:
{
"emailBody": "({{itemIndex+1}} of {{itemCount}}) [{{item.account}}] Function Name: {{item.somePropertyOnItem}}<br>"
}
mapProperty(...properties: string)
Allows for mapping individual properties from an array. You can supply a single
or multiple properties. The properties that are accessible are pulled from the
properties object and the entity object if the item matches the schema for
an entity. If the array that is being evaluated with mapProperty matches the
schema of an entity, the rule evaluator attempts to pull properties passed
to mapProperty from the entity properties.
Example query data:
{
"query": "FIND Person",
"data": [
{
"id": "",
"entity": {
"_createdOn": 1234
// ...
},
"properties": {
"firstName": "Jon"
// ...
}
},
{
"id": "",
"entity": {
"_createdOn": 12345
// ...
},
"properties": {
"firstName": "Jane"
// ...
}
}
]
}
This is an example of accessing properties data using mapProperty and the
above data:
{
"type": "SEND_EMAIL",
// This would return: `Jon,Jane`
"body": "{{ queries.query0.data | mapProperty('firstName') | join}}",
"recipients": ["person1@example.com"]
}
This is an example accessing entity data using mapProperty and the above
data:
{
"type": "SEND_EMAIL",
// This would return: `1234,12345`
"body": "{{ queries.query0.data | mapProperty('_createdOn') | join}}",
"recipients": ["person1@example.com"]
}
merge(...data: array)
Allows for merging 1 or more arrays together to create a new array of the combined results. This transform does not perform any deduplication; all elements of the input arrays are present in the output. This is usually used to combine the results of two or more J1QL queries into a common set of query results.
Example query configuration:
{
"queries": [
{
"query": "Find User with name ~=\"first.last\" as a RETURN a.displayName as DisplayName",
"name": "query0",
"version": "v1",
"includeDeleted": false
},
{
"query": "Find User with name ~=\"firstLast\" as b RETURN b.displayName as DisplayName",
"name": "query1",
"version": "v1",
"includeDeleted": false
}
]
}
This is an example of merging the results of these two queries together, and getting the number of deduplicated total results.
{
"type": "SEND_EMAIL",
"body": "Total Query Results: {{ queries.query0.data | merge(queries.query1.data) | uniquePropertyValues('displayName') | length}}",
"recipients": ["person1@example.com"]
}
join(separator?: string)
This function is similar to the Array.prototype.join function in JavaScript.
It returns a new string by concatenating all of the elements in an array. If the
separatorargument is not passed to join, the array elements are separated by
a comma, by default.
This transform is often used with mapTemplate or mapProperty.
Example:
{
"type": "SEND_EMAIL",
"body": "{{ queries.query0.data | mapTemplate('emailBody') | join(' ') }}",
"recipients": ["person1@example.com"]
}
Example of default if no separator is passed to join:
{
"type": "SEND_EMAIL",
"body": "{{ queries.query0.data | mapTemplate('emailBody') | join }}",
"recipients": ["person1@example.com"]
}
uniquePropertyValues(propertyName: string)
Creates a set of unique values for the given property name. The transform will
pull all properties from the entity and properties sub-properties to the top
level to try to access the passed in propertyName.
Example query data:
{
"query": "FIND Person",
"data": [
{
"id": "1",
"entity": {
"_createdOn": 1234
// ...
},
"properties": {
"firstName": "Jon"
// ...
}
},
{
"id": "2",
"entity": {
"_createdOn": 12345
// ...
},
"properties": {
"firstName": "Jane"
// ...
}
},
{
"id": "3",
"entity": {
"_createdOn": 5555
// ...
},
"properties": {
"firstName": "Jon"
// ...
}
},
]
}
This is an example of accessing deduplicating a property of an array of entities:
{
"type": "SEND_EMAIL",
// This would return: ["Jon","Jane"]
"body": "{{ queries.query0.data | uniquePropertyValues('firstName') }}",
"recipients": ["person1@example.com"]
}
length
Returns the number of items in an array. This transform takes no arguments, and
the templating language does not accept empty parentheses — write | length,
not | length().
Example:
{
"type": "SEND_EMAIL",
"body": "{{ queries.query0.data | length }} people were found.",
"recipients": ["person1@example.com"]
}
length is most useful at the end of a chain, to count what the transforms
before it produced:
{
"type": "SEND_EMAIL",
// The number of distinct first names across both queries
"body": "Unique names: {{ queries.query0.data | merge(queries.query1.data) | uniquePropertyValues('firstName') | length }}",
"recipients": ["person1@example.com"]
}
If the value piped into length is not an array — because a transform earlier
in the chain produced no value, for example — length returns 0 rather than
failing the render.
capitalize
Upper-cases the first character of a string and leaves the rest of it as it is.
This transform takes no arguments, and, as with length, the templating
language does not accept empty parentheses — write | capitalize, not
| capitalize().
Because only the first character changes, capitalize is for making a
lower-case value read as a sentence rather than for normalizing casing
throughout a value: high becomes High, high severity becomes
High severity, and HIGH is unchanged.
This transform is often used inside a mapTemplate template, where each item
property is a single value:
{
"type": "SEND_EMAIL",
"body": "{{ queries.query0.data | mapTemplate('findingLine') | join(' ') }}",
"recipients": ["person1@example.com"]
}
Example templates:
{
"findingLine": "{{item.displayName}} — {{item.severity | capitalize}} severity<br>"
}
Any value that is not a string, including an array, passes through capitalize
unchanged.
dateAdd(amount: number, unit?: string)
Shifts a timestamp by a calendar amount and returns an ISO 8601 string.
The input may be Unix milliseconds, Unix milliseconds inside a string, or an ISO
8601 string, so it works with evaluationBeginOn as well as with date properties
read from query data.
amount is required and may be negative to shift backwards. unit defaults to
days and accepts hours, days, weeks, months and years, singular or
plural. These are calendar units — weekends and holidays are not skipped.
Because the result is ISO 8601, it can be piped into formatDate to produce
whatever format the destination field wants — most often YYYY-MM-DD for a Jira
Date field:
// evaluationBeginOn = 1785542400000, which is 2026-08-01T00:00:00.000Z
{{ evaluationBeginOn | dateAdd(90, 'days') }}
// 2026-10-30T00:00:00.000Z
{{ evaluationBeginOn | dateAdd(-30, 'days') }}
// 2026-07-02T00:00:00.000Z
{{ evaluationBeginOn | dateAdd(3, 'months') }}
// 2026-11-01T00:00:00.000Z
If the input is missing or is not a date, the transform produces no value and the field is left out of the request rather than being sent as an invalid date.
formatDate(format?: string, timeZone?: string)
Formats a timestamp, accepting the same inputs as dateAdd.
Jira has two distinct date field types, and they want different formats — see other / custom additional fields:
- A Date field (schema type
date) wantsYYYY-MM-DD. This isformat's default, soformatDatewith no arguments produces it. - A DateTime field (schema type
datetime) wants ISO 8601 with a numeric offset, which isYYYY-MM-DDTHH:mm:ss.SSSZZ.
Format tokens follow Day.js.
timeZone defaults to UTC and accepts an IANA time zone name. Rule
evaluations run on a schedule with no user in context, so there is no time zone
to infer — pass one explicitly if a UTC date is not what you want. Note that
this changes which calendar day you get, not just the clock time.
// evaluationBeginOn = 1785542400000, which is 2026-08-01T00:00:00.000Z
{{ evaluationBeginOn | formatDate('YYYY-MM-DD') }}
// 2026-08-01 — for a Date field
{{ evaluationBeginOn | formatDate('YYYY-MM-DDTHH:mm:ss.SSSZZ') }}
// 2026-08-01T00:00:00.000+0000 — for a DateTime field
{{ evaluationBeginOn | formatDate('YYYY-MM-DD', 'America/New_York') }}
// 2026-07-31 — still the previous day in New York
As with dateAdd, a missing or non-date input produces no value.
queryDataDifference(renderDataProperty: string)
Returns the items that one query found and another did not — the set difference between two sets of query results. Use it to report what is new since an earlier run, or which items a broad query found that a narrower one did not.
Unlike the other transforms, queryDataDifference operates on an array of two
arrays, so the value piped into it has to be written as an array literal. No
single path in the render data produces this shape:
{{ [queries.query0.data, queries.query1.data] | queryDataDifference('newItems') }}
The first list is subtracted from the second, so the example above returns the
items in query1 that are not in query0.
Items are matched on _id, falling back to _key when the results carry no
_id. Either key is read from the top level of an item or from its entity
sub-object, so both raw and entity-shaped query data work. Which of the two keys
is used is decided once, from the first item of the first non-empty list, and is
then applied to every item — so both queries need to return the same shape of
data. An item that carries none of these keys fails the render with
"_id" or "_key" is required.
The renderDataProperty argument is required. Alongside returning the
difference, the transform stores it in the render data under that name, so the
rest of the rule can reference the same result without repeating the transform:
{
"type": "SEND_EMAIL",
"body": "{{ [queries.query0.data, queries.query1.data] | queryDataDifference('newFindings') | length }} new findings: {{ newFindings | mapProperty('displayName') | join(', ') }}",
"recipients": ["person1@example.com"]
}
Common templating examples
Filling a mandatory Jira Date field with a deadline relative to the run that creates the ticket
// In your rule action's additionalFields
{{ evaluationBeginOn | dateAdd(90, 'days') | formatDate('YYYY-MM-DD') }}
// Will display something like:
// 2026-10-30
// For a DateTime field instead, ask for that format:
{{ evaluationBeginOn | dateAdd(90, 'days') | formatDate('YYYY-MM-DDTHH:mm:ss.SSSZZ') }}
// 2026-10-30T00:00:00.000+0000
Merging multiple query results to generate a unique set of values for a given property printed on a new line
{{ queries.query0.data | merge(queries.query1.data) | uniquePropertyValues('firstName') | join('\n') }}
// Will display something like:
//
// firstName1
// firstName2
// ...
Using a template to display multiple properties from a set of query results
// In the templates definition
{
"myTemplateName": "Property1: {{item.propertyName1}} - Property2: {{item.propertyName2}}"
}
// In your rule action
{{ queries.query0.data | mapProperty('propertyName1', 'propertyName2') | mapTemplate('myTemplateName') | join('\n') }}
// Will display something like:
// Property1: value1 - Property2 - value2
// Property1: value3 - Property2 - value4
// ...
Filtering a set of query results to a specific name and using a template for extra propoerties
// In the templates definition
{
"myTemplateName": "Property1: {{item.name}} - Property2: {{item.id}}"
}
// In your rule action
{{ queries.query0.data[.name == 'repository1'] | mapProperty('name', 'id') | mapTemplate('myTemplateName') | join('\n') }}
// Will display something like:
// repository1 - anIdForEntity1
// repository1 - anIdForEntity2
// repository1 - anIdForEntity3
// ...
Filtering collections
Collections, or arrays of objects, can be filtered by including a filter expression in brackets. Properties of each collection can be referenced by prefixing them with a leading dot. The result will be an array of the objects for which the filter expression resulted in a truthy value.
Example context:
{
employees: [
{first: 'Sterling', last: 'Archer', age: 36},
{first: 'Malory', last: 'Archer', age: 75},
{first: 'Lana', last: 'Kane', age: 33},
{first: 'Cyril', last: 'Figgis', age: 45},
{first: 'Cheryl', last: 'Tunt', age: 28}
],
retireAge: 62
}
| Expression | Result |
|---|---|
employees[.first == 'Sterling'] | [{first: 'Sterling', last: 'Archer', age: 36}] |
employees[.last == 'Tu' + 'nt'].first | Cheryl |
employees[.age >= 30 && .age < 40] | [{first: 'Sterling', last: 'Archer', age: 36},{first: 'Lana', last: 'Kane', age: 33}] |
employees[.age >= 30 && .age < 40][.age < 35] | [{first: 'Lana', last: 'Kane', age: 33}] |
employees[.age >= retireAge].first | Malory |
Parameters in rules
Rules support reference to parameter values stored at the account-level. These parameters simplify the task of referencing long, sensitive, or widely reused values in rules or queries. For example, the following action trigger is nearly identical to the slack webhook example:
{
"type": "WEBHOOK",
"method": "POST",
"body": {
"name": "Jon"
},
"headers": {
"Authorization": "Bearer {{param.SlackAuthToken}}"
}
}
This showcases a primary use case of parameter storage: a value that is long, not human-readable, and may represent a sensitive value which should not be leaked in the configuration.
param.SlackAuthToken invokes a parameter stored at the account level, which is
referenced when the rule is evaluated. These parameters are always referenced
with the preceding token param.. The subsequent string (without special
characters) identifies the name of a parameter.
Parameters are supported anywhere that Operation Templating is supported, and the value of a parameter can be any type of native type with the exclusion of objects, which support comparison against parameters but cannot be the contents of a parameter. Additionally, parameters can store lists of native types, and template expressions can invoke parameter lists similarly to examples above. For example, using the email example, we can parameterize the recipient list:
{
"type": "SEND_EMAIL",
"body": "{{ queries.query0.data | mapTemplate('emailBody') | join(' ') }}",
// a stored list of email strings:
"recipients": "{{param.alertEmailRecipientList}}"
}
For more info on JupiterOne parameters, reference the documentation.