# Alerts & Errors

## Overview

The Panther API supports the following alerting operations:

* Listing your alerts and errors with optional filters
* Fetching the details of a particular alert
* Getting the log events associated with an alert
* Updating the status of one or more alerts
* Adding a comment to an alert
* Assigning and un-assigning a user to one or more alerts, by providing:
  * A user's ID
  * A user's email address

You can invoke Panther's API by using your Console's API Playground, or the GraphQL-over-HTTP API. Learn more about these methods on [Panther API](/panther-developer-workflows/api.md#step-1-choose-a-method-for-invoking-the-api).

See the sections below for GraphQL queries, mutations, and end-to-end workflow examples around core alert and error operations.

## Common alert and error operations

Below are some of the most common GraphQL alert and error operations in Panther. These examples demonstrate the documents you have to send using a GraphQL client (or `curl`) to make a call to Panther's GraphQL API.

#### Listing your alerts

The `alerts` query requires an `input` object containing `createdAtAfter` and `createdAtBefore`.

{% tabs %}
{% tab title="First page of all alerts" %}

```graphql
# `FirstPageOfAllAlerts` is a nickname for the operation
query FirstPageOfAllAlerts {
    alerts(input: { 
      createdAtAfter: "2022-01-01T00:00:00.000Z"
      createdAtBefore: "2023-01-01T00:00:00.000Z"
    }) {
      edges {
        node { # you can ask for more alert-related fields here
          id
          title
          severity
          status
        }
      }
      pageInfo {
        hasNextPage
        endCursor
      }
    }
  }
```

{% endtab %}

{% tab title="Subsequent page of alerts" %}

```graphql
# `AnotherPageOfAllAlerts` is a nickname for the operation
query AnotherPageOfAllAlerts {
    alerts(input: { 
      createdAtAfter: "2022-01-01T00:00:00.000Z",
      createdAtBefore: "2023-01-01T00:00:00.000Z",
      cursor: "{\"creationTime\":{\"B\":null,\"BOOL\":null,\"BS\":null,\"L\":null,\"M\":null,\"N\":null,\"NS\":null,\"NULL\":null,\"S\":\"2022-08-11T21:16:12Z\",\"SS\":null},\"id\":{\"B\":null,\"BOOL\":null,\"BS\":null,\"L\":null,\"M\":null,\"N\":null,\"NS\":null,\"NULL\":null,\"S\":\"6472742357f3488aeb3f48a9c5da9262\",\"SS\":null},\"alertMonthPartition\":{\"B\":null,\"BOOL\":null,\"BS\":null,\"L\":null,\"M\":null,\"N\":null,\"NS\":null,\"NULL\":null,\"S\":\"202208\",\"SS\":null}}"
    }) {
      edges {
        node { # you can ask for more alert-related fields here
          id
          title
          severity
          status
        }
      }
      pageInfo {
        hasNextPage
        endCursor
      }
    }
  }
```

{% endtab %}

{% tab title="Filtered page of alerts" %}

```graphql
# `FilteredPageOfAlerts` is a nickname for the operation
query FilteredPageOfAlerts {
    alerts(input: { 
      createdAtAfter: "2022-01-01T00:00:00.000Z",
      createdAtBefore: "2023-01-01T00:00:00.000Z",
      severities: [LOW], 
      statuses: [OPEN] 
    }) { 
      edges {
        node { # you can ask for more alert-related fields here
          id
          title
          severity
          status
        }
      }
      pageInfo {
        hasNextPage
        endCursor
      }
    }
  }
```

{% endtab %}
{% endtabs %}

#### Describing an alert

```graphql
query AlertDetails {
    alert(id: "FAKE_ALERT_ID") {
       id
       title
       severity
       status
    }
  }
```

#### Getting detection or System Error information from an alert

```graphql
query AlertDetails {
    alert(id: "FAKE_ALERT_ID") {
       id
       title
       severity
       origin {
         ... on Detection {
           id
           name
         }
         ... on SystemError {
           relatedComponent
           type
         }
       }
    }
  }
```

#### Getting the log events associated with an alert

```graphql
query FirstPageOfAlertEvents {
  alert(id: "FAKE_ALERT_ID") {
    id,
    events(input: {
      cursor: "",
      pageSize: 25
    }) {
      edges {
        node
      }
      pageInfo {
        endCursor
      }
    }
  }
}
```

#### Updating the status of one or more Alerts

```graphql
mutation UpdateAlertStatus {
    updateAlertStatusById(
       input: {
          ids: ["FAKE_ALERT_ID_1","FAKE_ALERT_ID_2"]
          status: CLOSED # notice how this isn't a string when hardcoded (it's an `enum`)
       }
    ) {
       alerts {
         id
         status
       }
    }
  }
```

#### Adding a comment to an alert

```graphql
mutation CreateAlertComment {
    createAlertComment(
       input: {
          alertId: "FAKE_ALERT_ID"
          body: "<p>This is HTML</p>"
          format: HTML # can also be PLAIN_TEXT if you want HTML parsing disabled
       }
    ) {
       comment {
         id
       }
    }
 
```

#### Assigning and un-assigning a user to one or more alerts by supplying a user ID

```graphql
mutation UpdateAlertsAssigneeById {
  updateAlertsAssigneeById(
    input: {
      assigneeId: "FAKE_USER_ID"
      ids: ["FAKE_ALERT_ID_1","FAKE_ALERT_ID_2"]
    }
  ) {
    alerts {
      assignee {
        email
        givenName
        familyName
        id
      }
    }
  }
}
```

#### Assigning and un-assigning a user to one or more alerts by supplying a user email address

```graphql
mutation UpdateAlertsAssigneeByEmail {
  updateAlertsAssigneeByEmail(
    input: {
      assigneeEmail: "fake@email.com"
      ids: ["FAKE_ALERT_ID_1","FAKE_ALERT_ID_2"]
    }
  ) {
    alerts {
      assignee {
        email
        givenName
        familyName
        id
      }
    }
  }
}
```

## End-to-end examples

Below, we will build on the [Common Operations](#common-operations) examples to showcase an end-to-end flow.

#### Find a particular set of alerts and mark them as *Resolved:*

{% tabs %}
{% tab title="NodeJS" %}

```javascript
// npm install graphql graphql-request

import { GraphQLClient, gql } from 'graphql-request';

const client = new GraphQLClient(
  'YOUR_PANTHER_API_URL', 
  { headers: { 'X-API-Key': 'YOUR_API_KEY' } 
});

// `FindAlerts` is a nickname for the query. You can fully omit it.
const findAlerts = gql`
  query FindAlerts($input: AlertsInput!) {
  alerts(input: $input) {
    edges {
      node {
        id
      }
    }
    pageInfo {
      hasNextPage
      endCursor
    }
  }
}
`;

// `UpdateAlerts` is a nickname for the query. You can fully omit it.
const updateAlerts = gql`
  mutation UpdateAlerts($input: UpdateAlertStatusByIdInput!) {
    updateAlertStatusById(input: $input) {
      alerts {
        id
      }
    }
  }
`;

(async () => {
  try {
    
    // an accumulator that holds all alerts that we fetch all pages
    let allAlerts = [];
    // a helper to know when to exit the loop
    let hasMore = true;
    // the pagination cursor
    let cursor = null;

    // Keep fetching pages until there are no more left
    do {
      const queryData = await client.request(findAlerts, {
        input: {
          severities: ["LOW"],
          createdAtAfter: "2022-02-01T00:00:00.000Z",
          createdAtBefore: "2022-03-01T00:00:00.000Z",
          cursor
        }
      });

      allAlerts = [
        ...allAlerts,
        ...queryData.alerts.edges.map((edge) => edge.node)
      ];
      
      hasMore = queryData.alerts.pageInfo.hasNextPage;
      cursor = queryData.alerts.pageInfo.endCursor;
    } while (hasMore);

    // Now update the alerts
    if (!allAlerts.length) {
      console.log("Could not find any alerts to resolve");
      return;
    }
    const mutationData = await client.request(updateAlerts, {
      input: {
        ids: allAlerts.map((alert) => alert.id),
        status: "RESOLVED"
      }
    });

    console.log(
      `Resolved ${mutationData.updateAlertStatusById.alerts.length} alert(s)!`
    );
  } catch (err) {
    console.error(err.response);
  }
})();

```

{% endtab %}

{% tab title="Python" %}

```python
# pip install gql aiohttp

from gql import gql, Client
from gql.transport.aiohttp import AIOHTTPTransport

transport = AIOHTTPTransport(
  url="YOUR_PANTHER_API_URL",
  headers={"X-API-Key": "YOUR_API_KEY"}
)

client = Client(transport=transport, fetch_schema_from_transport=True)

# `FindAlerts` is a nickname for the query. You can fully omit it.
find_alerts = gql(
    """
    query FindAlerts($input: AlertsInput!) {
      alerts(input: $input) {
        edges {
          node {
            id
          }
        }
        pageInfo {
          hasNextPage
          endCursor
        }
      }
    }
    """
)

# `UpdateAlerts` is a nickname for the query. You can fully omit it.
update_alerts = gql(
    """
    mutation UpdateAlerts($input: UpdateAlertStatusByIdInput!) {
      updateAlertStatusById(input: $input) {
        alerts {
          id
        }
      }
    }
    """
)

# an accumulator that holds all alerts that we fetch all pages
all_alerts = []
# a helper to know when to exit the loop
has_more = True
# the pagination cursor
cursor = None

# Keep fetching pages until there are no more left
while has_more:
    query_data = client.execute(
        find_alerts,
        variable_values={
            "input": {
                "severities": ["LOW"],
                "createdAtAfter": "2022-02-01T00:00:00.000Z",
                "createdAtBefore": "2022-03-01T00:00:00.000Z",
                "cursor": cursor
            }
        }
    )

    all_alerts.extend([edge["node"] for edge in query_data["alerts"]["edges"]])
    has_more = query_data["alerts"]["pageInfo"]["hasNextPage"]
    cursor = query_data["alerts"]["pageInfo"]["endCursor"]

# Now update the alerts
if not len(all_alerts):
    print("Could not find any alerts to resolve")
else:
    mutation_data = client.execute(
        update_alerts,
        variable_values={
            "input": {
                "ids": [alert["id"] for alert in all_alerts],
                "status": "RESOLVED"
            }
        }
    )

    print(f'Resolved {len(mutation_data["updateAlertStatusById"]["alerts"])} alert(s)!')
```

{% endtab %}
{% endtabs %}


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.panther.com/panther-developer-workflows/api/graphql/alerts-and-errors.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
