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.
Rule exemptions, which exclude individual entities from a rule's results, are covered under API operations.
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"
}
Exemptions
EARLY ACCESSAn exemption excludes one entity from a rule's results, so it is left out of the totals the rule's condition is evaluated against and triggers none of the rule's actions. For what exemptions do and which rules can accept them, see Exemptions.
Exemptions are not managed by the JupiterOne Terraform provider.
Check whether results can be exempted
Ask this before offering an exempt action. Exemptability is a property of the evaluation whose rows you hold, not of the rule alone, so evaluatedRuleVersion is required: pass the stored result's collectionOwnerVersion, which is the rule version that produced those rows.
query RuleExemptionEligibility($ruleId: ID!, $evaluatedRuleVersion: Int!) {
ruleExemptionEligibility(
ruleId: $ruleId
evaluatedRuleVersion: $evaluatedRuleVersion
) {
eligible
reason
message
ruleSupported
evaluationSupported
queriesMatch
}
}
Variables:
{
"ruleId": "b1c0f75d-770d-432a-95f5-6f59b4239c72",
"evaluatedRuleVersion": 14
}
| Field | Description |
|---|---|
eligible | True only when the rule accepts exemptions and these results came from the queries it runs now. |
reason | An ExemptionUnsupportedReason. Null when eligible. |
message | Display-ready explanation, suitable for showing to a user as-is. Null when eligible. |
ruleSupported | Whether the rule's current queries can accept an exemption at all. |
evaluationSupported | Whether the queries that produced these results could be exempted. |
queriesMatch | Whether those are the same queries. Compared by query text, so renaming, retagging, or rescheduling a rule does not invalidate its results. For a rule that references a saved question, only the question id is compared. |
eligible: false with ruleSupported: false means the rule can never accept exemptions in its current shape. eligible: false with ruleSupported: true and queriesMatch: false means the rule is fine but these particular results are stale—re-run the rule and exempt from the new results.
reason: NOT_AVAILABLE means exemptions are not enabled for the account. Treat it as "the feature is absent" rather than as a refusal.
Exempt entities from a rule's results
One call files one decision: the justification, reason, and expiry are shared across every entity in the request, and every row written carries the returned groupId.
mutation CreateRuleExemptions($input: CreateRuleExemptionsInput!) {
createRuleExemptions(input: $input) {
groupId
exemptions {
ruleId
entityId
entityDisplayName
justification
reason
expiresOn
status
createdBy
createdOn
}
}
}
Variables:
{
"input": {
"ruleId": "b1c0f75d-770d-432a-95f5-6f59b4239c72",
"entities": [
{
"entityId": "5f9d1e2c-8a3b-4c7d-9e1f-2a3b4c5d6e7f",
"entityKey": "arn:aws:ec2:us-east-1:123456789012:instance/i-0abc123",
"entityScope": "d4e5f6a7-b8c9-4d0e-1f2a-3b4c5d6e7f80",
"entityType": "aws_instance",
"entityDisplayName": "bastion-prod-1"
}
],
"justification": "Bastion host is managed outside this policy; risk accepted by the platform team.",
"reason": "RISK_ACCEPTED",
"expiresOn": 1798761600000
}
}
| Input field | Description |
|---|---|
ruleId | The rule the entities are exempted from. |
entities | Up to 100 entities per request, and a rule may hold at most 100 unexpired exemptions in total. Only entityId is matched when results are filtered; the other fields are stored as diagnostic detail and are worth supplying, because they are what makes an exemption identifiable after the entity is gone. |
justification | Required, and must not be empty. |
reason | RISK_ACCEPTED, NOT_APPLICABLE, FALSE_POSITIVE, or COMPENSATING_CONTROL. |
expiresOn | Optional. Epoch milliseconds, and must be in the future. Omit for an exemption that never expires. |
entityId is the graph entity _id.
Re-exempting an entity replaces its previous exemption rather than creating a second one, so it does not count again toward the rule's total.
A rule may hold at most 100 unexpired exemptions. A request that would take it past that is refused, and the error reports how many the rule already has and how many the request would add. Expired exemptions do not count toward the limit, so a rule that has cycled through short-lived exemptions does not run out of room with nothing actually exempt.
The mutation is refused when the rule's current queries cannot accept exemptions, and the error carries the same message that ruleExemptionEligibility returns. That refusal is judged from the rule's current queries only—it cannot detect that the entity ids you supplied came from a superseded query, so check eligibility for the evaluation you chose them from first.
Exemptions take effect on the rule's next scheduled evaluation. Nothing is re-evaluated when the mutation returns.
Revoke exemptions
Revoke by groupId (the whole decision), by entityIds, or by both, which revokes the intersection. At least one selector is required—revoking a rule's entire set in one call is not supported.
mutation RevokeRuleExemptions($ruleId: ID!, $groupId: ID, $entityIds: [ID!]) {
revokeRuleExemptions(
ruleId: $ruleId
groupId: $groupId
entityIds: $entityIds
) {
revokedCount
}
}
Variables:
{
"ruleId": "b1c0f75d-770d-432a-95f5-6f59b4239c72",
"groupId": "9c8b7a65-4321-4fed-cba9-876543210fed"
}
Revocation works regardless of whether the rule still accepts new exemptions, so a rule that has been edited into an unsupported shape can always have its exemptions cleared. Up to 100 entity ids per request.
Read a rule's exemptions
query RuleExemptions($id: ID!, $limit: Int, $cursor: String) {
questionRuleInstance(id: $id) {
exemptions(limit: $limit, cursor: $cursor, includeExpired: true) {
items {
groupId
entityId
entityKey
entityScope
entityType
entityDisplayName
justification
reason
expiresOn
status
createdBy
createdOn
}
pageInfo {
endCursor
hasNextPage
}
}
}
}
Variables:
{
"id": "b1c0f75d-770d-432a-95f5-6f59b4239c72",
"limit": 100
}
includeExpired defaults to true, so an expired exemption stays visible instead of disappearing.
When includeExpired is false, a page can come back with fewer items than limit while more still exist. Follow endCursor until hasNextPage is false rather than stopping on a short page.
status is derived when you read it and is never stored:
| Status | Meaning |
|---|---|
ACTIVE | In effect. |
EXPIRED | Past its expiresOn. It no longer applies, but it is retained so that a rule that started alerting again is explainable. |
ORPHANED | The entity it covers no longer exists, so the exemption no longer applies to anything. See the limitations for what causes this. |
createdBy is the caller that filed the exemption: a user name, an email address, or token:<tokenId> for an API token.
Download the included or exempted result set
ruleEvaluationQueryResultsCSV takes a resultSet argument. INCLUDED (the default) is the entities the evaluation counted; EXEMPTED is the entities it held back.
query RuleEvaluationQueryResultsCSV(
$rawDataKey: String!
$resultSet: RuleEvaluationQueryResultSet
) {
ruleEvaluationQueryResultsCSV(rawDataKey: $rawDataKey, resultSet: $resultSet) {
csvKey
status
}
}
Variables:
{
"rawDataKey": "<rawDataKey from the evaluation's rawDataDescriptors>",
"resultSet": "EXEMPTED"
}
Both sets come from the one stored result object for that evaluation, so the exempted set reflects what that particular run held back. Revoking an exemption later does not rewrite it.
Exemption reference
ExemptionReason, supplied when creating an exemption:
| Value | Shown in the product as |
|---|---|
RISK_ACCEPTED | Risk accepted |
NOT_APPLICABLE | Not applicable |
FALSE_POSITIVE | False positive |
COMPENSATING_CONTROL | Compensating control |
ExemptionUnsupportedReason, returned by ruleExemptionEligibility and carried by a refused createRuleExemptions. Render message rather than mapping these to your own wording, so what you show cannot drift from what the API enforces.
| Value | What it means |
|---|---|
NOT_AVAILABLE | Exemptions are not enabled for this account. Says nothing about the rule. |
NO_QUERIES | The rule has no query to exempt entities from. |
QUESTION_UNAVAILABLE | The rule's saved question could not be loaded. |
QUERY_NOT_PARSEABLE | The rule's query could not be read. |
AGGREGATED_QUERY | The query aggregates, so its rows are counts and summaries. |
DISTINCT_QUERY | The query uses FIND UNIQUE, so its rows are deduplicated values. |
TREE_RESULTS | The query returns a graph, so it has no result rows. |
NO_RETURNED_ENTITIES | The query's RETURN names only values, so its rows carry no entity identity. |
QUERY_CHANGED_SINCE_EVALUATION | The rule's query changed after this evaluation ran. Re-run the rule and exempt from its current results. |
EVALUATED_VERSION_UNAVAILABLE | The rule version that produced these results can no longer be read. |
The first four groups describe the rule's own queries and are reported by ruleSupported: false. The last two describe the stored evaluation rather than the rule, and can only come from ruleExemptionEligibility.
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.