# 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](https://docs.panther.com/panther-developer-workflows/api/..#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 %}
