The Panther API supports the following alerting operations:
Listing your alerts and errors with optional filters
Fetching the details of a particular 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.
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.
# `FirstPageOfAllAlerts` is a nickname for the operationqueryFirstPageOfAllAlerts { 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 } } }
# `AnotherPageOfAllAlerts` is a nickname for the operationqueryAnotherPageOfAllAlerts { 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 } } }
# `FilteredPageOfAlerts` is a nickname for the operationqueryFilteredPageOfAlerts { 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 } } }
Describing an Alert
queryAlertDetails { alert(id: "FAKE_ALERT_ID") { id title severity status } }
Getting Detection or System Error information from an Alert
queryAlertDetails { alert(id: "FAKE_ALERT_ID") { id title severity origin {...onDetection { id name }...onSystemError { relatedComponent type } } } }
Updating the status of one or more Alerts
mutationUpdateAlertStatus { 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
mutationCreateAlertComment { 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
Below, we will build on the Common Operations examples to showcase an end-to-end flow.
Find a particular set of alerts and mark them as Resolved:
// npm install graphql graphql-requestimport { GraphQLClient, gql } from'graphql-request';constclient=newGraphQLClient('YOUR_PANTHER_API_URL', { headers: { 'X-API-Key':'YOUR_API_KEY' } });// `FindAlerts` is a nickname for the query. You can fully omit it.constfindAlerts=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.constupdateAlerts=gql` mutation UpdateAlerts($input: UpdateAlertStatusByIdInput!) { updateAlertStatusById(input: $input) { alerts { id } } }`;(async () => {try {// an accumulator that holds all alerts that we fetch all pageslet allAlerts = [];// a helper to know when to exit the looplet hasMore =true;// the pagination cursorlet cursor =null;// Keep fetching pages until there are no more leftdo {constqueryData=awaitclient.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 alertsif (!allAlerts.length) {console.log("Could not find any alerts to resolve");return; }constmutationData=awaitclient.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); }})();
# pip install gql aiohttpfrom gql import gql, Clientfrom gql.transport.aiohttp import AIOHTTPTransporttransport =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 pagesall_alerts = []# a helper to know when to exit the loophas_more =True# the pagination cursorcursor =None# Keep fetching pages until there are no more leftwhile 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 alertsifnotlen(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)!')