This step-by-step guide covers how to automate API-based responses to security events and XOps stories.
Use it to extract story data, apply remediation actions such as isolating hosts, blocking IOCs, and quarantining users, and annotate the story with actions taken.
1. API Access & Prerequisites
Item | Value |
|---|---|
Endpoint |
Note: Endpoint format varies by CMA region prefix (e.g. api.us1.catonetworks.com). Learn more here. |
Auth header |
|
Content-Type |
|
Method | POST (every call, queries and mutations) |
accountId | You can find this in the CMA (Administration > General Info) |
Key type | Service Principal (Resources > Service API Keys) Note: Learn more about Service Principal keys, key rotation, and IP restrictions here. |
Required permissions |
|
Store API credentials securely for use in the scripts on this page | Create a secure header file named Note: Running commands with raw secrets in terminal history can leak credentials. We recommend using environment files like this as a standard best practice. |
1.1 HTTP Request Pattern
Every Cato API call follows the same shape: POST to /api/v1/graphql2 with a JSON body containing query and variables. Identical pattern for queries and mutations.
Generic curl
curl -X POST https://api.catonetworks.com/api/v1/graphql2 \
-H @headers.txt \
-d '{
"query": "<GraphQL operation as string>",
"variables": { ... }
}'
Generic raw HTTP
POST /api/v1/graphql2 HTTP/1.1
Host: api.catonetworks.com
x-api-key: <your_api_key>
Content-Type: application/json
Content-Length: <byte_count>
{ "query": "<GraphQL operation>", "variables": { ... } }
Generic success response
HTTP/1.1 200 OK
Content-Type: application/json
{
"data": {
"<root_field>": { /* operation-specific fields */ }
}
}
Generic error response (GraphQL validation)
HTTP/1.1 200 OK # GraphQL returns 200 even on validation errors
Content-Type: application/json
{
"errors": [
{
"message": "Variable '$accountId' of required type 'ID!' was not provided.",
"locations": [{ "line": 1, "column": 7 }],
"extensions": { "code": "GRAPHQL_VALIDATION_FAILED" }
}
],
"data": null
}
GraphQL returns HTTP 200 even when the operation fails. The error is in errors[] in the body. Integration steps must check errors[] before processing data.
1.2 Pre-Production Validation Checklist
# | Validation step |
|---|---|
1 | Generate a dedicated Service Principal API key (do not reuse admin keys) |
2 | Restrict the API key to your SIEM source IPs |
3 | Test every mutation in the GraphQL Playground against your account, as explained here |
4 | Confirm response shapes match what your SIEM Parse JSON steps expect |
5 | Run a full enforcement-to-reversal cycle for each remediation action in test |
6 | Verify CMA audit trail reflects every test action |
2. Extracting Remediation Data
Two integration paths feed the same remediation mutations. Which one you use depends on your Cato licenses:
2.1 Choosing an Integration Path
Path | When to use | Signal source | Correlation |
|---|---|---|---|
Story-based (XOps) | Account has an XDR/XOps license and Stories Workbench is populated |
| Cato pre-correlates producers into stories. Entities live in |
Event-based (non-XOps) | Account does not have XOps and the SIEM receives raw security verdicts and performs its own correlation |
| SIEM (or SOAR playbook) correlates. Entities live in the flat |
2.2 Story-Based Extraction (XOps)
Story data reaches your platform via two paths: (1) xdr.stories() query for direct pull; (2) eventsFeed query with the full story JSON in additional_data.
Note
Learn more about XOps and the Stories Workbench here.
Learn more about how to configure the response policy to generate events for eventsFeed, here.
2.2.1 Pull Stories via xdr.stories()
GraphQL operation
query GetStories($accountId: ID!) {
xdr(accountID: $accountId) {
stories(input: {
paging: { limit: 50, from: 0 }
filter: [{
timeFrame: { time: "last.P7D" } # REQUIRED
status: { in: [Open] }
severity: { in: [High, Medium] }
}]
sort: [{ fieldName: criticality, order: desc }]
}) {
paging { total }
items {
id
createdAt
updatedAt
incident {
producerType
indication
criticality
sourceIp
firstSignal
lastSignal
user { id name }
site { id name }
entities { type role ref { id name } }
}
}
}
}
}
curl
curl -X POST https://api.catonetworks.com/api/v1/graphql2 \
-H @headers.txt \
--data-binary @get_stories.json
Success response (illustrative)
HTTP/1.1 200 OK
{
"data": {
"xdr": {
"stories": {
"paging": { "total": 47 },
"items": [
{
"id": "sty_01HX9F8K7M2QXAZB3VR4PQYNTC",
"createdAt": "2026-06-27T14:22:11Z",
"updatedAt": "2026-06-27T14:38:42Z",
"incident": {
"producerType": "ThreatPrevention",
"indication": "Ransomware Communication",
"criticality": 9,
"sourceIp": "10.1.2.50",
"firstSignal": "2026-06-27T14:22:11Z",
"lastSignal": "2026-06-27T14:38:42Z",
"user": { "id": "usr_8K2QXA3VR4PQYNTC", "name": "jane.doe@example.com" },
"site": { "id": "ste_2QXA3VR4PQYNTC", "name": "HQ-Site" },
"entities": [
{ "type": "ip", "role": "target", "ref": { "id": "ip_x1", "name": "203.0.113.5" } },
{ "type": "domain", "role": "target", "ref": { "id": "d_x2", "name": "malicious.example.com" } },
{ "type": "host", "role": "source", "ref": { "id": "h_x3", "name": "WIN-LAPTOP-23" } }
]
}
}
]
}
}
}
}
Error response (missing required timeFrame)
{
"errors": [
{
"message": "Field 'StoryFilterInput.timeFrame' of required type 'TimeFrameInput!' was not provided.",
"locations": [{ "line": 4, "column": 15 }],
"extensions": { "code": "GRAPHQL_VALIDATION_FAILED" }
}
],
"data": null
}
2.2.2 Key Story Fields for Remediation
JSONPath | Type | Remediation use |
|---|---|---|
| String | Story ID. For |
| String | Host/device source IP. Use for Isolate Host. |
| String | Cato user entity ID. Pass directly to user mutations. |
| String | Username/email. Use only if |
| Array | IOCs, hosts, users. Filter by |
| String | IOC IP value → BlockListIps. |
| String | IOC domain value → BlockListUrls. |
| Enum |
|
| Int (1–10) | Auto-remediation threshold. |
2.2.3 Polling XOps Events via eventsFeed
Note:
Learn more about setup and integration patterns for eventsFeed, here.
GraphQL operation
query FetchXOpsEvents($accountId: ID!, $marker: String) {
eventsFeed(
accountIDs: [$accountId]
marker: $marker
filters: [{ fieldName: event_type, operator: is, values: ["Detection and Response"] }]
) {
marker
fetchedCount
accounts { id records { fieldsMap } }
}
}
Success response
{
"data": {
"eventsFeed": {
"marker": "W3siVG9waWMiOi4uLn0=","fetchedCount": 1,
"accounts": [
{
"id": "YOUR_ACCOUNT_ID",
"records": [
{
"fieldsMap": {
"event_type": "Detection and Response",
"event_sub_type": "Threat Prevention",
"story_id": "sty_01HX9F8K7M2QXAZB3VR4PQYNTC",
"src_ip": "10.1.2.50",
"user_name": "jane.doe@example.com",
"severity": "High",
"additional_data": "{ \"id\": \"sty_01HX...\", \"incident\": { ... } }"
}
}
]
}
]
}
}
}
2.2.4 Parsing the Event Record
The full story payload is in additional_data as a JSON string. Parse it separately from the flat fieldsMap keys.
You need | JSONPath | Notes |
|---|---|---|
user entity ID |
| Direct (no user lookup needed) |
username (fallback) |
| Use only if |
host IP |
| For Isolate Host |
IOC IPs (array) |
| For Block IOC IP |
IOC domains (array) |
| For Block IOC Domain |
producer enum |
| PascalCase routing key |
criticality |
| Auto-remediation threshold |
2.2.5 jq Examples
# Extract user.id
jq -r '.data.eventsFeed.accounts[].records[].fieldsMap.additional_data
| fromjson | .incident.user.id // empty' response.json
# All IOC IPs
jq -r '.data.eventsFeed.accounts[].records[].fieldsMap.additional_data
| fromjson | .incident.entities[]
| select(.type == "ip") | .ref.name' response.json
# All IOC domains
jq -r '.data.eventsFeed.accounts[].records[].fieldsMap.additional_data
| fromjson | .incident.entities[]
| select(.type == "domain" or .type == "url") | .ref.name' response.json
2.3 Event-based Extraction (non-XOps)
Non-XOps customers subscribe to eventsFeed and filter on event_type = "Security". Each event carries a single verdict from one enforcement point — IPS signature match, Anti-Malware scan, Threat Prevention DNS/URL match, Application Control decision, or DNS Protection lookup. Entities are in the flat fieldsMap; there is no additional_data story JSON to parse. Correlation and dedup are the SIEM/SOAR’s responsibility.
2.3.1 Polling Security Events via eventsFeed
GraphQL operation
query FetchSecurityEvents($accountId: ID!, $marker: String) {
eventsFeed(
accountIDs: [$accountId]
marker: $marker
filters: [{ fieldName: event_type, operator: is, values: ["Security"] }]
) {
marker
fetchedCount
accounts { id records { fieldsMap } }
}
}
To narrow to specific producers, add a second filter clause on event_sub_type:
filters: [
{ fieldName: event_type, operator: is, values: ["Security"] }
{ fieldName: event_sub_type, operator: is, values: ["IPS", "Anti Malware", "Threat Prevention"] }
]Success response (illustrative — Anti-Malware verdict)
{
"data": {
"eventsFeed": {
"marker": "W3siVG9waWMiOi4uLn0=",
"fetchedCount": 1,
"accounts": [
{
"id": "YOUR_ACCOUNT_ID",
"records": [
{
"fieldsMap": {
"event_type": "Security",
"event_sub_type": "Anti Malware",
"action": "Block",
"src_ip": "10.1.2.50",
"dest_ip": "203.0.113.5",
"user_name": "jane.doe@example.com",
"domain_name": "malicious.example.com",
"threat_name": "Trojan.Generic.KX",
"severity": "High",
"rule_name": "Anti-Malware Default"
}
}
]
}
]
}
}
}
Field names vary by sub_type. All fieldsMap keys in this section were verified against the EventFieldName schema enum, but the event_type and event_sub_type string values (e.g. "Security", "Anti Malware") are runtime values and should be confirmed against a live sample from your account (Events Discovery page in the CMA). Spelling/spacing on "Anti Malware" vs "Anti-Malware" in particular is worth a spot-check before wiring the parser.
2.3.2 Key Event Fields for Remediation
You need | fieldsMap key | Notes / used by |
|---|---|---|
host IP (source) |
| For Isolate Host. |
username |
| Resolve to |
user ID (if present) |
| Some sub_types include it directly. If this is already populated, you can ignore the section Resolving a User Entity ID. |
IOC IP (destination) |
| For Block IOC, IP Address. Only when the verdict indicates outbound C2/IOC traffic. Do not blocklist legitimate destinations. |
IOC domain |
| For Block IOC, Domain. Also |
producer |
| Routing key: |
verdict |
|
|
severity |
| Auto-remediation threshold. |
threat name |
| |
rule |
| Which Cato policy fired. Useful for audit and dedup. |
2.3.3 Sub-type → Remediation Action
Not every security verdict maps to every remediation mutation action. Suggested mapping:
event_sub_type | Isolate Host | Isolate User | Block IOC IP | Block IOC Domain |
|---|---|---|---|---|
IPS | ✅ repeat offender | ⚠️ policy-dependent | ✅ external attacker src | — |
Anti Malware | ✅ infected host | ✅ user’s device | ✅ C2 dest | ✅ C2 domain |
Threat Prevention | ⚠️ policy-dependent | ⚠️ policy-dependent | ✅ | ✅ |
Application Control | — usually alert-only | ⚠️ policy-dependent | — | ⚠️ risky-app domain |
DNS Protection | ⚠️ repeat lookups | — | — | ✅ malicious FQDN |
Legend: ✅ typical, ⚠️ policy-dependent, — not applicable.
2.3.4 SIEM-side Audit Record
Log the remediation locally so the SOC has an audit trail. Suggested minimum shape:
{
"timestamp": "2026-08-12T14:38:42Z",
"cato_event_id": "<fieldsMap.event_id or SIEM-assigned>",
"event_sub_type": "Anti Malware",
"action_taken": "block_ioc_ip",
"entity": "203.0.113.5",
"cato_rule_id": "IFW_BLOCK_IOC_IPS_RULE_ID",
"api_status": "SUCCESS",
"operator": "SOAR-playbook-v3"
}
2.3.5 jq Examples
# All source IPs from Security events (candidates for Isolate Host)
jq -r '.data.eventsFeed.accounts[].records[].fieldsMap
| select(.event_type == "Security")
| .src_ip // empty' response.json
# IOC destination IPs from Anti-Malware blocks
jq -r '.data.eventsFeed.accounts[].records[].fieldsMap
| select(.event_sub_type == "Anti Malware" and .action == "Block")
| .dest_ip // empty' response.json
# Malicious FQDNs from DNS Protection
jq -r '.data.eventsFeed.accounts[].records[].fieldsMap
| select(.event_sub_type == "DNS Protection")
| .domain_name // empty' response.json
2.4 Shared: Entity → Remediation Mutations Action Mapping
The remediation mutation targets are identical whether entities came from a story or a flat event (see Choosing an Integration Path).
Pass the extracted values directly into the remediation mutation inputs.
Extracted entity | Story path | Event path | Remediation Mutation action |
|---|---|---|---|
host IP |
|
| |
user ID |
|
| |
IOC IP(s) |
|
| |
IOC domain(s) |
|
| |
story ID |
| (N/A — use SIEM audit record) |
3. Resolving a User Entity ID (Fallback)
If incident.user.id is populated, pass it directly to user mutations. Use user.userList only when a username/email is the only identifier available.
curl
curl -X POST https://api.catonetworks.com/api/v1/graphql2 \
-H @headers.txt \
-d '{
"query": "query LookupUser($accountId: ID!, $search: String!) {
user(accountId: $accountId) {
userList(input: {
filter: { searchTerm: { search: $search } }
paging: { limit: 5, from: 0 }
}) {
items { id email userPrincipalName firstName lastName }
pageInfo { total }
}
} }",
"variables": { "accountId": "YOUR_ACCOUNT_ID", "search": "jane.doe@example.com" }
}'
searchTerm.search performs a free-text search across UPN and email. For an exact-email lookup use filter: { email: [{ ... }] } (see EmailFilterInput).
Success response
{
"data": {
"user": {
"userList": {
"items": [
{
"id": "usr_8K2QXA3VR4PQYNTC",
"email": "jane.doe@example.com",
"userPrincipalName": "jane.doe@example.com",
"firstName": "Jane",
"lastName": "Doe"
}
],
"pageInfo": { "total": 1 }
}
}
}
}
4. One-Time Setup (Per Account)
Run these mutations once per account. After publishing, no further policy changes are required for per-alert actions. The remediation mutations described below only update the rules created here.
Note:
Learn more about Container structure, use cases, and enforcement model, here.
Learn more about Client Connectivity Policy (used by Quarantine User), here.
Learn more about Firewall rule ordering and best practices, here.
4.1 Object Map
Object | Name | Used by |
|---|---|---|
IP Container |
| Block IOC IP - IFW BLOCK rule destination |
FQDN Container |
| Block IOC Domain - IFW BLOCK rule destination |
IFW rule |
| Destination refs |
IFW rule |
| Destination refs |
IFW rule |
| Source-IP list; appends host IPs |
WAN FW rule |
| Source-IP list; appends host IPs |
Client Connectivity rule |
| Source-user list; appends user IDs |
Store the six returned IDs (2 container IDs and 4 rule IDs) in your credential vault. The per-alert mutations reference them by ID.
4.2 Create the IP/FQDN Containers
Same mutation shape (createFromList) for both; only the input variables change. Containers require at least one seed value at creation (RFC 5737 documentation IP 192.0.2.1 is a safe placeholder; example.com for FQDN).
curl — BlockListIps
curl -X POST https://api.catonetworks.com/api/v1/graphql2 \
-H @headers.txt \
-d '{
"query": "mutation CreateIpContainer($accountId: ID!, $input: CreateIpAddressRangeContainerFromListInput!) {
container(accountId: $accountId) { ipAddressRange { createFromList(input: $input) {
container { id name size } } } } }",
"variables": {
"accountId": "YOUR_ACCOUNT_ID",
"input": {
"name": "BlockListIps",
"description": "IOC IPs - Internet FW destination",
"values": [{ "from": "192.0.2.1", "to": "192.0.2.1" }]
}
}
}'
Success response
{
"data": {
"container": {
"ipAddressRange": {
"createFromList": {
"container": { "id": "cnt_1A2B3C", "name": "BlockListIps", "size": 1 }
}
}
}
}
}
curl — BlockListUrls
curl -X POST https://api.catonetworks.com/api/v1/graphql2 \
-H @headers.txt \
-d '{
"query": "mutation CreateFqdnContainer($accountId: ID!, $input: CreateFqdnContainerFromListInput!) {
container(accountId: $accountId) { fqdn { createFromList(input: $input) {
container { id name size } } } } }",
"variables": {
"accountId": "YOUR_ACCOUNT_ID",
"input": {
"name": "BlockListUrls",
"description": "IOC domains - Internet FW destination",
"values": ["example.com"]
}
}
}'
4.3 Create Internet Firewall BLOCK Rules
Three rules at FIRST_IN_POLICY:
Rules 1 and 2 reference the containers in their destination
Rule 3 (Isolate Host) uses
source.ipempty at creation
The Isolate Host mutation appends host IPs at alert time. (Use source.ip for single addresses. The API's source.ipRange validator rejects {from:X, to:X} even when the values are equal. Reserve source.ipRange for actual multi-address ranges.)
curl — Rule 1: Block IOC IPs
curl -X POST https://api.catonetworks.com/api/v1/graphql2 \
-H @headers.txt \
-d '{
"query": "mutation AddIfwRule($accountId: ID!, $input: InternetFirewallAddRuleInput!) {
policy(accountId: $accountId) { internetFirewall { addRule(input: $input) {
status errors { errorMessage errorCode } rule { rule { id name } } } } } }",
"variables": {
"accountId": "YOUR_ACCOUNT_ID",
"input": {
"at": { "position": "FIRST_IN_POLICY" },
"rule": {
"name": "XOps - Block IOC IPs",
"enabled": true,
"action": "BLOCK",
"source": {},
"destination": {
"containers": {
"ipAddressRangeContainer": [{ "by": "NAME", "input": "BlockListIps" }]
}
},
"tracking": { "event": { "enabled": true } }
}
}
}
}'
Success response
{
"data": {
"policy": {
"internetFirewall": {
"addRule": {
"status": "SUCCESS",
"errors": [],
"rule": { "rule": { "id": "rul_XK7M2Q", "name": "XOps - Block IOC IPs" } }
}
}
}
}
}
Rule 2: Block IOC Domains — same mutation, change destination
"destination": {
"containers": {
"fqdnContainer": [{ "by": "NAME", "input": "BlockListUrls" }]
}
}
Rule 3: Isolate Host — source-IP list, empty at creation
"source": { "ip": [] },
"destination": {}
Store the returned rule IDs as IFW_ISOLATE_HOST_RULE_ID, IFW_BLOCK_IOC_IPS_RULE_ID, IFW_BLOCK_IOC_DOMAINS_RULE_ID.
4.4 Create WAN Firewall BLOCK Rule (Host Isolation)
Same shape as IFW Rule 3 above. WAN Firewall source does not support containers: always use direct ip (single addresses) or ipRange (multi-address ranges).
curl
curl -X POST https://api.catonetworks.com/api/v1/graphql2 \
-H @headers.txt \
-d '{
"query": "mutation AddWfw($accountId: ID!, $input: WanFirewallAddRuleInput!) {
policy(accountId: $accountId) { wanFirewall { addRule(input: $input) {
status errors { errorMessage errorCode } rule { rule { id name } } } } } }",
"variables": {
"accountId": "YOUR_ACCOUNT_ID",
"input": {
"at": { "position": "FIRST_IN_POLICY" },
"rule": {
"name": "XOps - Isolate Host (WAN)",
"enabled": true,
"action": "BLOCK",
"source": { "ip": [] },
"destination": {},
"tracking": { "event": { "enabled": true } }
}
}
}
}'
Store the returned rule ID as WAN_ISOLATE_HOST_RULE_ID.
4.5 Create Client Connectivity BLOCK Rule (User Quarantine)
Source-user list, empty at creation. Quarantine User (Persistent) appends offending user IDs at alert time.
curl
curl -X POST https://api.catonetworks.com/api/v1/graphql2 \
-H @headers.txt \
-d '{
"query": "mutation AddCC($accountId: ID!, $input: ClientConnectivityAddRuleInput!) {
policy(accountId: $accountId) { clientConnectivity { addRule(input: $input) {
status errors { errorMessage errorCode } rule { rule { id name } } } } } }",
"variables": {
"accountId": "YOUR_ACCOUNT_ID",
"input": {
"at": { "position": "FIRST_IN_POLICY" },
"rule": {
"name": "XOps - Block Quarantined Users",
"enabled": true,
"action": "BLOCK",
"source": { "user": [] }
}
}
}
}'
Store the returned rule ID as CC_QUARANTINE_RULE_ID.
4.6 Publish All Policies
Run once per policy after creating its rules. After this, The per-alert mutations take effect immediately without further publishes.
curl — Internet Firewall
curl -X POST https://api.catonetworks.com/api/v1/graphql2 \
-H @headers.txt \
-d '{
"query": "mutation { policy(accountId: \"YOUR_ACCOUNT_ID\") {
internetFirewall { publishPolicyRevision { status errors { errorMessage errorCode } } } } }"
}'
Success response
{
"data": {
"policy": {
"internetFirewall": {
"publishPolicyRevision": { "status": "SUCCESS", "errors": [] }
}
}
}
}
Repeat with wanFirewall and clientConnectivity for the other two policies.
5. Remediation Mutations (Per Alert)
Sequence:
Call the matching mutation(s) (from this section)
There are two enforcement patterns:
Container-write (5.3 Block IOC, IP Address and 5.4 Block IOC, Domain (FQDN)): append value to a container that's referenced from a rule's destination. Immediate effect. The value is unchanged.
Rule-update (5.1.2 Quarantine User (Persistent) and 5.2 Isolate Host): query the target rule, append the offender to its source list,
updateRulewith the merged list.updateRulereplaces the source list: always read-modify-write.
5.1 Isolate User
5.1.1 Revoke Session
Session revocation behaviour on Windows, macOS, and per-IdP is explained here.
curl
curl -X POST https://api.catonetworks.com/api/v1/graphql2 \
-H @headers.txt \
-d '{
"query": "mutation RevokeSession($accountId: ID!, $input: RevokeUserSessionInput!) {
user(accountId: $accountId) { revokeUserSession(input: $input) { status } } }",
"variables": {
"accountId": "YOUR_ACCOUNT_ID",
"input": { "userId": "usr_8K2QXA3VR4PQYNTC" }
}
}'
Raw HTTP
POST /api/v1/graphql2 HTTP/1.1
Host: api.catonetworks.com
x-api-key: <your_api_key>
Content-Type: application/json
{
"query": "mutation RevokeSession($accountId: ID!, $input: RevokeUserSessionInput!) { user(accountId: $accountId) { revokeUserSession(input: $input) { status } } }",
"variables": { "accountId": "YOUR_ACCOUNT_ID", "input": { "userId": "usr_8K2QXA3VR4PQYNTC" } }
}
Success response
{
"data": {
"user": {
"revokeUserSession": { "status": "SUCCESS" }
}
}
}
Error response — unknown user ID
{
"errors": [
{ "message": "User 'usr_INVALID' not found in account.",
"extensions": { "code": "RESOURCE_NOT_FOUND" } }
],
"data": { "user": { "revokeUserSession": null } }
}
5.1.2 Quarantine User (Persistent)
Appends the user to CC_QUARANTINE_RULE_ID's source-user list. Prevents Cato Client reconnection until reversed.
The process involves two steps:
Query the rule's current source
Use
updateRulewith the appended list.
Step 1: Read current source.user list on the Quarantine rule
curl -X POST https://api.catonetworks.com/api/v1/graphql2 \
-H @headers.txt \
-d '{
"query": "query GetQuarantineUsers($accountId: ID!) {
policy(accountId: $accountId) {
clientConnectivity { policy(input: {}) {
rules { rule { id name source { user { id name } } } } } } } }",
"variables": { "accountId": "YOUR_ACCOUNT_ID" }
}'
Filter the response for the rule whose id == CC_QUARANTINE_RULE_ID. Extract source.user[].id.
Step 2: updateRule with appended user
curl -X POST https://api.catonetworks.com/api/v1/graphql2 \
-H @headers.txt \
-d '{
"query": "mutation QuarantineUser($accountId: ID!, $input: ClientConnectivityUpdateRuleInput!) {
policy(accountId: $accountId) { clientConnectivity { updateRule(input: $input) {
status errors { errorMessage errorCode } } } } }",
"variables": {
"accountId": "YOUR_ACCOUNT_ID",
"input": {
"id": "CC_QUARANTINE_RULE_ID",
"rule": {
"source": {
"user": [
{ "by": "ID", "input": "usr_existing1" },
{ "by": "ID", "input": "usr_8K2QXA3VR4PQYNTC" }
]
}
}
}
}
}'
Success response
{
"data": {
"policy": {
"clientConnectivity": {
"updateRule": { "status": "SUCCESS", "errors": [] }
}
}
}
}
updateRule replaces the whole source.user list. Step 1 → Step 2 must be atomic per rule. Serialise Quarantine calls per account, or maintain the current list in your SOAR state and always send the full merged list.
5.2 Isolate Host
Appends the host IP to both IFW_ISOLATE_HOST_RULE_ID and WAN_ISOLATE_HOST_RULE_ID source-IP lists. Blocks all network access (Internet + WAN) for that IP.
Step 1: Read current source.ip on both Isolate Host rules
curl -X POST https://api.catonetworks.com/api/v1/graphql2 \
-H @headers.txt \
-d '{
"query": "query GetIsolationSources($accountId: ID!) {
policy(accountId: $accountId) {
internetFirewall { policy(input: {}) {
rules { rule { id name source { ip ipRange { from to } } } } } }
wanFirewall { policy(input: {}) {
rules { rule { id name source { ip ipRange { from to } } } } } } } }",
"variables": { "accountId": "YOUR_ACCOUNT_ID" }
}'
Filter IFW rules for IFW_ISOLATE_HOST_RULE_ID, WAN rules for WAN_ISOLATE_HOST_RULE_ID, extract each source.ip list (and source.ipRange if you also isolate CIDR ranges).
Step 2a: updateRule on Internet Firewall Isolate Host rule
curl -X POST https://api.catonetworks.com/api/v1/graphql2 \
-H @headers.txt \
-d '{
"query": "mutation IsolateHostIfw($accountId: ID!, $input: InternetFirewallUpdateRuleInput!) {
policy(accountId: $accountId) { internetFirewall { updateRule(input: $input) {
status errors { errorMessage errorCode } } } } }",
"variables": {
"accountId": "YOUR_ACCOUNT_ID",
"input": {
"id": "IFW_ISOLATE_HOST_RULE_ID",
"rule": {
"source": {
"ip": [ "10.1.2.50" ]
}
}
}
}
}'
updateRule replaces the whole source.ip list. Always send the merged Step-1 list plus the new host IP. If your account also uses source.ipRange for CIDR isolations, include it in the same update; source.ip and source.ipRange are independent lists and are both cleared if omitted.
Step 2b: same shape for WAN Firewall. Swap internetFirewall → wanFirewall, InternetFirewallUpdateRuleInput → WanFirewallUpdateRuleInput, IFW_ISOLATE_HOST_RULE_ID → WAN_ISOLATE_HOST_RULE_ID. Send the merged list from Step 1.
Success response
{
"data": {
"policy": {
"internetFirewall": {
"updateRule": { "status": "SUCCESS", "errors": [] }
}
}
}
}
5.3 Block IOC, IP Address
Container write. No rule update needed. The Block IOC IPs rule already references BlockListIps in its destination (see Create Internet Firewall BLOCK Rules, rule 1). Source: incident.entities[type=='ip'].
curl
curl -X POST https://api.catonetworks.com/api/v1/graphql2 \
-H @headers.txt \
-d '{
"query": "mutation BlockIp($accountId: ID!, $input: IpAddressRangeContainerAddValuesInput!) {
container(accountId: $accountId) { ipAddressRange {
addValues(input: $input) { container { id size } } } } }",
"variables": {
"accountId": "YOUR_ACCOUNT_ID",
"input": {
"ref": { "by": "NAME", "input": "BlockListIps" },
"values": [
{ "from": "203.0.113.5", "to": "203.0.113.5" },
{ "from": "198.51.100.7", "to": "198.51.100.7" }
]
}
}
}'
Success response
{
"data": {
"container": {
"ipAddressRange": {
"addValues": {
"container": { "id": "cnt_1A2B3C", "size": 23 }
}
}
}
}
}
5.4 Block IOC, Domain (FQDN)
Container write. Source: incident.entities[type ∈ (domain, url, fqdn)]. FQDN values: alphanumeric characters only; no wildcards.
curl
curl -X POST https://api.catonetworks.com/api/v1/graphql2 \
-H @headers.txt \
-d '{
"query": "mutation BlockFqdn($accountId: ID!, $input: FqdnContainerAddValuesInput!) {
container(accountId: $accountId) { fqdn {
addValues(input: $input) { container { id size } } } } }",
"variables": {
"accountId": "YOUR_ACCOUNT_ID",
"input": {
"ref": { "by": "NAME", "input": "BlockListUrls" },
"values": ["malicious.example.com", "phish.example.org"]
}
}
}'
Success response
{
"data": {
"container": {
"fqdn": {
"addValues": { "container": { "id": "cnt_fqdn_1", "size": 23 } }
}
}
}
}
6. Annotating the XOps Story
After triggering any remediation, add a comment to the XOps story to keep the CMA Stories Workbench in sync with automated actions.
Note:
As an alternative to annotating XOps stories, event-based customers (see Choosing an Integration Path) should log remediation actions to their own SIEM/case system.
curl
curl -X POST https://api.catonetworks.com/api/v1/graphql2 \
-H @headers.txt \
-d '{
"query": "mutation AddStoryComment($accountId: ID!, $input: AddStoryCommentInput!) {
xdr(accountId: $accountId) { addStoryComment(input: $input) {
comment { id text } } } }",
"variables": {
"accountId": "YOUR_ACCOUNT_ID",
"input": {
"storyId": "sty_01HX9F8K7M2QXAZB3VR4PQYNTC",
"text": "Automated remediation: Isolated host 10.1.2.50, blocked IOC IP 203.0.113.5 and domain malicious.example.com.",
"type": "USER"
}
}
}'
Success response
{
"data": {
"xdr": {
"addStoryComment": {
"comment": {
"id": "cmt_XK7M2Q",
"text": "Automated remediation: Isolated host 10.1.2.50..."
}
}
}
}
}
Query root xdr takes accountID (capital ID); mutation root xdr takes accountId (lowercase d). For the full casing reference, see Account Argument Casing Reference.
7. Reversing Remediation Actions
Action to reverse | Mechanism | See |
|---|---|---|
Release isolated host |
| |
Unquarantine user |
| |
Remove blocked IOC IP |
| |
Remove blocked domain |
|
Important:
There is no reversal action for session revocation: re-authentication is required.
7.1 Release Isolated Host
Same read-modify-write pattern as the Isolate Host mutation, but the modification removes the target IP from both source.ip lists.
Step 1: identical to step 1 of the Isolate Host mutation (read both Isolate Host rules).
Step 2: updateRule on both IFW and WAN Isolate Host rules with the target IP filtered out of source.ip.
curl — Internet Firewall (WAN is identical, swap policy field and rule ID)
curl -X POST https://api.catonetworks.com/api/v1/graphql2 \
-H @headers.txt \
-d '{
"query": "mutation ReleaseHostIfw($accountId: ID!, $input: InternetFirewallUpdateRuleInput!) {
policy(accountId: $accountId) { internetFirewall { updateRule(input: $input) {
status errors { errorMessage errorCode } } } } }",
"variables": {
"accountId": "YOUR_ACCOUNT_ID",
"input": {
"id": "IFW_ISOLATE_HOST_RULE_ID",
"rule": {
"source": {
"ip": [ /* every remaining isolated IP EXCEPT 10.1.2.50 */ ]
}
}
}
}
}'
7.2 Unquarantine User
Same read-modify-write pattern as the Quarantine User (Persistent) mutation, but the modification removes the target user from source.user.
curl
curl -X POST https://api.catonetworks.com/api/v1/graphql2 \
-H @headers.txt \
-d '{
"query": "mutation UnquarantineUser($accountId: ID!, $input: ClientConnectivityUpdateRuleInput!) {
policy(accountId: $accountId) { clientConnectivity { updateRule(input: $input) {
status errors { errorMessage errorCode } } } } }",
"variables": {
"accountId": "YOUR_ACCOUNT_ID",
"input": {
"id": "CC_QUARANTINE_RULE_ID",
"rule": {
"source": {
"user": [
/* every remaining quarantined user EXCEPT usr_8K2QXA3VR4PQYNTC */
]
}
}
}
}
}'
7.3 Remove IP from Container
curl
curl -X POST https://api.catonetworks.com/api/v1/graphql2 \
-H @headers.txt \
-d '{
"query": "mutation RemoveIp($accountId: ID!, $input: IpAddressRangeContainerRemoveValuesInput!) {
container(accountId: $accountId) { ipAddressRange {
removeValues(input: $input) { container { id size } } } } }",
"variables": {
"accountId": "YOUR_ACCOUNT_ID",
"input": {
"ref": { "by": "NAME", "input": "BlockListIps" },
"values": [{ "from": "203.0.113.5", "to": "203.0.113.5" }]
}
}
}'
7.4 Remove Domain from Container
curl
curl -X POST https://api.catonetworks.com/api/v1/graphql2 \
-H @headers.txt \
-d '{
"query": "mutation RemoveFqdn($accountId: ID!, $input: FqdnContainerRemoveValuesInput!) {
container(accountId: $accountId) { fqdn {
removeValues(input: $input) { container { id size } } } } }",
"variables": {
"accountId": "YOUR_ACCOUNT_ID",
"input": {
"ref": { "by": "NAME", "input": "BlockListUrls" },
"values": ["malicious.example.com"]
}
}
}'
8. End-to-End Example
A ransomware story from detection to remediation. All JSON is illustrative.
8.1 Scenario
XOps detects a Threat Prevention story: Ransomware Communication. Criticality 9.
Source:
WIN-LAPTOP-23(10.1.2.50), userjane.doe@example.com.Targets: C2 IP
203.0.113.5, domainmalicious.example.com.
8.2 Event Received (fieldsMap excerpt)
{
"event_type": "Detection and Response",
"event_sub_type": "Threat Prevention",
"story_id": "sty_01HX9F8K7M2QXAZB3VR4PQYNTC",
"src_ip": "10.1.2.50",
"user_name": "jane.doe@example.com",
"severity": "High",
"additional_data": "<JSON string — parsed below>"
}
8.3 Parsed Entities
story_id: "sty_01HX9F8K7M2QXAZB3VR4PQYNTC"
producer: "ThreatPrevention"
indication: "Ransomware Communication"
criticality: 9
user_id: "usr_8K2QXA3VR4PQYNTC"
source_ip: "10.1.2.50"
ioc_ips: ["203.0.113.5"]
ioc_domains: ["malicious.example.com"]
8.4 Remediation Sequence
POST
container.ipAddressRange.addValues(BlockListIps, 203.0.113.5)POST
container.fqdn.addValues(BlockListUrls, malicious.example.com)POST
user.revokeUserSession(userId: usr_8K2QXA3VR4PQYNTC)QUERY IFW+WAN policies → append 10.1.2.50 to
IFW_ISOLATE_HOST_RULE_ID.source.ipandWAN_ISOLATE_HOST_RULE_ID.source.ipPOST
internetFirewall.updateRule+wanFirewall.updateRule(Isolate Host, merged IP list)QUERY CC policy → append usr_8K2QXA3VR4PQYNTC to
CC_QUARANTINE_RULE_ID.source.userPOST
clientConnectivity.updateRule(Block Quarantined Users, merged user list)POST
xdr.addStoryComment(story_id, summary of actions)
8.5 Final Story Comment
"Automated remediation:
Blocked IOC IP 203.0.113.5;
Blocked IOC domain malicious.example.com;
Revoked session for user usr_8K2QXA3VR4PQYNTC;
Isolated host 10.1.2.50 (IFW + WAN);
Quarantined user usr_8K2QXA3VR4PQYNTC (Client Connectivity)."
9. Quick Reference
9.1 Entity Extraction
You need | JSONPath | Notes |
|---|---|---|
userId |
| Direct. No user lookup needed. |
userId (fallback) |
| |
hostIp |
| For Isolate Host. |
iocIps[] |
| Submit all in one |
iocDomains[] |
| Also |
storyId |
| For |
9.2 Action > Mutation Map
Action | Mutation | Input | Publish needed? |
|---|---|---|---|
Revoke Session |
|
| No |
Quarantine User |
|
| No |
Isolate Host |
|
| No |
Block IOC IP |
|
| No |
Block IOC Domain |
|
| No |
9.3 Account Argument Casing Reference
The account arg name is split between query and mutation for xdr, and eventsFeed uses a plural form. All other roots use accountId (lowercase d).
Root | Query | Mutation |
|---|---|---|
|
|
|
|
| n/a |
|
| n/a |
|
|
|
|
|
|
|
|
|
|
|
|