Introduction
Every automated task in Cortex XSOAR relies on executing commands from integrations or automations either in a playbook or directly in the incident war room or playground. But what if you wanted to incorporate a command or automation from Cortex XSOAR into your own custom scripts? For that you can use the API.
In the previous post in this series, we demonstrated how to use the Cortex XSOAR API in an automation. In this blog post, we will dive deeper into the API and show you how to execute commands using the Cortex XSOAR API.
To enable you to do this in your own automations, we have created a nitro_execute_api_command function which is available on the NVISO Github:
https://github.com/NVISOsecurity/blogposts/blob/master/CortexXSOAR/nitro_execute_api_command.py
Cortex XSOAR API Endpoints
When reviewing the Cortex XSOAR API documentation, you can find the following API endpoints:
- /entry: API to create an entry (markdown format) in existing investigation
- /entry/execute/sync: API to create an entry (markdown format) in existing investigation
Based on the description it might not be obvious, but both can be used to execute commands using the API. An entry in an existing investigation can contain a command which can be executed in the context of an incident or in the Cortex XSOAR playground.
We will be using the /entry/execute/sync endpoint, because this will wait for the command to be completed and the API request will return the command’s result. The /entry endpoint only creates an entry in the war room/playground without returning the result.
A HTTP POST request to the /entry/execute/sync endpoint accepts the following request body:
{
"args": {
"string": "<<_advancearg>>"
},
"data": "string",
"id": "string",
"investigationId": "string",
"markdown": true,
"primaryTerm": 0,
"sequenceNumber": 0,
"version": 0
}
To execute a simple print command in the context of an incident, you can use the following curl command:
curl -X 'POST' \
'https://xsoar.dev/acc_wstinkens/entry/execute/sync' \
-H 'accept: application/json' \
-H 'Authorization: **********************' \
-H 'Content-Type: application/json' \
-d '{"investigationId": "423","data": "!Print value=\"Printed by API\""}
'
The body of the HTTP POST request should contain the following keys:
- investigationId: the XSOAR Incident ID
- data: the command to execute
After executing the HTTP POST request, you will see the entry created in the incident war room:

When you do not require the command to be executed in the context of an Cortex XSOAR incident, it is possible to execute it in the playground. For this you should replace the investiationId key by the playground ID.
This can be found by using the investigation/search API endpoint:
curl -X 'POST' \
'https://xsoar.dev/acc_wstinkens/investigations/search' \
-H 'accept: application/json' \
-H 'Authorization: **********************' \
-H 'Content-Type: application/json' \
-d '{"filter": {"type": [9]}}'
This will return the following response body:
{
"total": 1,
"data": [
{
"id": "248b2bc0-def4-4492-8c80-d5a7e03be9fb",
"version": 2,
"cacheVersn": 0,
"modified": "2022-04-08T14:20:00.262348298Z",
"name": "Playground",
"users": [
"wstinkens"
],
"status": 0,
"type": 9,
"reason": null,
"created": "2022-04-08T13:56:03.294180041Z",
"closed": "0001-01-01T00:00:00Z",
"lastOpen": "0001-01-01T00:00:00Z",
"creatingUserId": "wstinkens",
"details": "",
"systems": null,
"tags": null,
"entryUsers": [
"wstinkens"
],
"slackMirrorType": "",
"slackMirrorAutoClose": false,
"mirrorTypes": null,
"mirrorAutoClose": null,
"category": "",
"rawCategory": "",
"runStatus": "",
"highPriority": false,
"isDebug": false
}
]
}
By using the id in the investigationId key in the request body of a HTTP POST request to /entry/execute/sync, it will be executed in the Cortex XSOAR playground:
curl -X 'POST' \
'https://xsoar.dev/acc_wstinkens/entry/execute/sync' \
-H 'accept: application/json' \
-H 'Authorization: **********************' \
-H 'Content-Type: application/json' \
-d '{"investigationId": "248b2bc0-def4-4492-8c80-d5a7e03be9fb","data": "!Print value=\"Printed by API\""}'

By default, the Markdown output of the command visible in the war room/playground will be returned by the HTTP POST request:
curl -X 'POST' \
'https://xsoar.dev/acc_wstinkens/entry/execute/sync' \
-H 'accept: application/json' \
-H 'Authorization: **********************' \
-H 'Content-Type: application/json' \
-d '{"investigationId": "248b2bc0-def4-4492-8c80-d5a7e03be9fb","data": "!azure-sentinel-list-tables"}'
This will return the result of the command as Markdown in the contents key:
[
{
"id": "108@248b2bc0-def4-4492-8c80-d5a7e03be9fb",
"version": 1,
"cacheVersn": 0,
"modified": "2022-04-27T10:49:23.872137691Z",
"type": 1,
"created": "2022-04-27T10:49:23.87206309Z",
"incidentCreationTime": "2022-04-27T10:49:23.87206309Z",
"retryTime": "0001-01-01T00:00:00Z",
"user": "",
"errorSource": "",
"contents": "### Azure Sentinel (NITRO) List Tables\n401 tables found in Sentinel Log Analytics workspace.\n|Table name|\n|---|\n| UserAccessAnalytics |\n| UserPeerAnalytics |\n| BehaviorAnalytics |\n| IdentityInfo |\n| ProtectionStatus |\n| SecurityNestedRecommendation |\n| CommonSecurityLog |\n| SecurityAlert |\n| SecureScoreControls |\n| SecureScores |\n| SecurityRegulatoryCompliance |\n| SecurityEvent |\n| SecurityRecommendation |\n| SecurityBaselineSummary |\n| Update |\n| UpdateSummary |\n",
"format": "markdown",
"investigationId": "248b2bc0-def4-4492-8c80-d5a7e03be9fb",
"file": "",
"fileID": "",
"parentId": "107@248b2bc0-def4-4492-8c80-d5a7e03be9fb",
"pinned": false,
"fileMetadata": null,
"parentContent": "!azure-sentinel-list-tables",
"parentEntryTruncated": false,
"system": "",
"reputations": null,
"category": "artifact",
"note": false,
"isTodo": false,
"tags": null,
"tagsRaw": null,
"startDate": "0001-01-01T00:00:00Z",
"times": 0,
"recurrent": false,
"endingDate": "0001-01-01T00:00:00Z",
"timezoneOffset": 0,
"cronView": false,
"scheduled": false,
"entryTask": null,
"taskId": "",
"playbookId": "",
"reputationSize": 0,
"contentsSize": 10315,
"brand": "Azure Sentinel (NITRO)",
"instance": "QA-Azure Sentinel (NITRO)",
"InstanceID": "e39e69f0-3882-4478-824d-ac41089381f2",
"IndicatorTimeline": [],
"Relationships": null,
"mirrored": false
}
]
To return the data of the executed command as JSON, you should add the raw-response=true parameter to your command:
curl -X 'POST' \
'https://xsoar.dev/acc_wstinkens/entry/execute/sync' \
-H 'accept: application/json' \
-H 'Authorization: **********************' \
-H 'Content-Type: application/json' \
-d '{"investigationId": "248b2bc0-def4-4492-8c80-d5a7e03be9fb","data": "!azure-sentinel-list-tables raw-response=true"}'
This will return the result of the command as JSON in the contents key:
[
{
"id": "106@248b2bc0-def4-4492-8c80-d5a7e03be9fb",
"version": 1,
"cacheVersn": 0,
"modified": "2022-04-27T06:34:59.448622878Z",
"type": 1,
"created": "2022-04-27T06:34:59.448396275Z",
"incidentCreationTime": "2022-04-27T06:34:59.448396275Z",
"retryTime": "0001-01-01T00:00:00Z",
"user": "",
"errorSource": "",
"contents": [
"UserAccessAnalytics",
"UserPeerAnalytics",
"BehaviorAnalytics",
"IdentityInfo",
"ProtectionStatus",
"SecurityNestedRecommendation",
"CommonSecurityLog",
"SecurityAlert",
"SecureScoreControls",
"SecureScores",
"SecurityRegulatoryCompliance",
"SecurityEvent",
"SecurityRecommendation",
"SecurityBaselineSummary",
"Update",
"UpdateSummary",
],
"format": "json",
"investigationId": "248b2bc0-def4-4492-8c80-d5a7e03be9fb",
"file": "",
"fileID": "",
"parentId": "105@248b2bc0-def4-4492-8c80-d5a7e03be9fb",
"pinned": false,
"fileMetadata": null,
"parentContent": "!azure-sentinel-list-tables raw-response=\"true\"",
"parentEntryTruncated": false,
"system": "",
"reputations": null,
"category": "artifact",
"note": false,
"isTodo": false,
"tags": null,
"tagsRaw": null,
"startDate": "0001-01-01T00:00:00Z",
"times": 0,
"recurrent": false,
"endingDate": "0001-01-01T00:00:00Z",
"timezoneOffset": 0,
"cronView": false,
"scheduled": false,
"entryTask": null,
"taskId": "",
"playbookId": "",
"reputationSize": 0,
"contentsSize": 9402,
"brand": "Azure Sentinel (NITRO)",
"instance": "QA-Azure Sentinel (NITRO)",
"InstanceID": "e39e69f0-3882-4478-824d-ac41089381f2",
"IndicatorTimeline": [],
"Relationships": null,
"mirrored": false
}
]
nitro_execute_api_command()
Even in Cortex XSOAR automations, executing commands through the API can be useful. When using automations, you will see that outputting results to the war room/playground and context data is only done after the automation has been executed. If you, for example, want to perform a task which requires the entry ID of a war room/playground entry or of a file, you will need to run 2 consequent automations. Another solution would be executing a command using the Cortex XSOAR API which will create the entry in the war room/playground during the runtime of your automation and returns it’s entry ID. Later in this post, we will provide an example of how this can be used.
To execute command through the API from automations, we have created the nitro_execute_api_command function:
def nitro_execute_api_command(command: str, args: dict = None):
"""Execute a command using the Demisto REST API
:type command: ``str``
:param command: command to execute
:type args: ``dict``
:param args: arguments of command to execute
:return: list of returned results of command
:rtype: ``list``
"""
args = args or {}
# build the command string in the form !Command arg1="val1" arg2="val2"
cmd_str = f"!{command}"
for key, value in args.items():
if isinstance(value, dict):
value = json.dumps(json.dumps(value))
else:
value = json.dumps(value)
cmd_str += f" {key}={value}"
results = nitro_execute_command("demisto-api-post", {
"uri": "/entry/execute/sync",
"body": json.dumps({
"investigationId": demisto.incident().get('id', ''),
"data": cmd_str
})
})
if not isinstance(results, list) \
or not len(results)\
or not isinstance(results[0], dict):
return []
results = results[0].get("Contents", {}).get("response", [])
for result in results:
if "contents" in result:
result["Contents"] = result.pop("contents")
return results
To use this function, the Demisto REST API integration needs to be enabled. How to set this up is described in the previous post in this series.
We have added this custom function to the CommonServerUserPython automation. This automation is created for user-defined code that is merged into each script and integration during execution. It will allow you to use nitro_execute_api_command in all your custom automations.
Incident Evidences Example
To demonstrate the use case for executing commands through the Cortex XSOAR API in automations, we will, again, build upon the example of adding evidences to the incident Evidence board. In the previous posts, we added tags to war room/playground entries which we then used in a second automation to search and add them to the incident Evidences board. This required a playbook which execute both automations consequently.
Now we will show you how to do this through the Cortex XSOAR API, negating the requirement of a playbook.
First we need an automation which creates an entry in the incident war room:
results = [
{
'FileName': 'malware.exe',
'FilePath': 'c:\\temp',
'DetectionStatus': 'Detected'
},
{
'FileName': 'evil.exe',
'FilePath': 'c:\\temp',
'DetectionStatus': 'Prevented'
}
]
title = "Malware Mitigation Status"
return_results(
CommandResults(
readable_output=tableToMarkdown(title, results, None, removeNull=True),
raw_response=results
)
)
This automation creates an entry in the incident war room:

We call this automation using the nitro_execute_api_command function:
results = nitro_execute_api_command(command='MalwareStatus')
The entry ID of the war room entry will be available in the returned result in the id key:
[
{
"IndicatorTimeline": [],
"InstanceID": "ScriptServicesModule",
"Relationships": null,
"brand": "Scripts",
"cacheVersn": 0,
"category": "artifact",
"contentsSize": 152,
"created": "2022-04-27T08:37:29.197107197Z",
"cronView": false,
"dbotCreatedBy": "wstinkens",
"endingDate": "0001-01-01T00:00:00Z",
"entryTask": null,
"errorSource": "",
"file": "",
"fileID": "",
"fileMetadata": null,
"format": "markdown",
"id": "21@6974",
"incidentCreationTime": "2022-04-27T08:37:29.197107197Z",
"instance": "Scripts",
"investigationId": "6974",
"isTodo": false,
"mirrored": false,
"modified": "2022-04-27T08:37:29.197139897Z",
"note": false,
"parentContent": "!MalwareStatus",
"parentEntryTruncated": false,
"parentId": "20@6974",
"pinned": false,
"playbookId": "",
"recurrent": false,
"reputationSize": 0,
"reputations": null,
"retryTime": "0001-01-01T00:00:00Z",
"scheduled": false,
"startDate": "0001-01-01T00:00:00Z",
"system": "",
"tags": null,
"tagsRaw": null,
"taskId": "",
"times": 0,
"timezoneOffset": 0,
"type": 1,
"user": "",
"version": 1,
"Contents": "### Malware Mitigation Status\n|DetectionStatus|FileName|FilePath|\n|---|---|---|\n| Detected | malware.exe | c:\\temp |\n| Prevented | evil.exe | c:\\temp |\n"
}
]
Next, we get all entry IDs from the results of nitro_execute_api_command:
entry_ids = [result.get('id') for result in results]
Finally we loop through all entry IDs in the nitro_execute_api_command result and use the AddEvidence command to add them to the evidence board:
for entry_id in entry_ids:
nitro_execute_command(command='AddEvidence', args={'entryIDs': entry_id, 'desc': 'Example Evidence'})
The war room entry created by the command executed through the Cortex XSOAR API will now be added to the Evidence Board of the incident:

References
https://xsoar.pan.dev/docs/concepts/concepts#playground
https://xsoar.pan.dev/marketplace/details/DemistoRESTAPI
About the author
Wouter is an expert in the SOAR engineering team in the NVISO SOC. As the SOAR engineering team lead, he is responsible for the development and deployment of automated workflows in Palo Alto Cortex XSOAR which enable the NVISO SOC analysts to faster detect attackers in customers environments. With his experience in cloud and DevOps, he has enabled the SOAR engineering team to automate the development lifecycle and increase operational stability of the SOAR platform.
You can contact Wouter via his LinkedIn page.
Want to learn more about SOAR? Sign- up here and we will inform you about new content and invite you to our SOAR For Fun and Profit webcast.
https://forms.office.com/r/dpuep3PL5W
Hello can you provide the API command for changing the state of an Xsoar incident; from Raised to Monitoring