Axonius

This integration is for fetching information about assets in Axonius.

Data Enrichment & Threat Intelligence · Axonius

Details

IDAxonius
ProviderAxonius
CategoryData Enrichment & Threat Intelligence
From Version5.0.0
Docker Imagedemisto/axonius:1.1.0.8767810
Supported ModulesAgentix XSIAM

README

This integration is for fetching information about assets in Axonius.
This integration was integrated and tested with version 3.9 of Axonius

Configure Axonius in Cortex

Parameter Required
Server URL (e.g. https://example.net) True
Axonius API Key True
Axonius API Secret True
Trust any certificate (not secure) False
Use system proxy settings False

Commands

You can execute these commands from the CLI, as part of an automation, or in a playbook.
After you successfully execute a command, a DBot message appears in the War Room with the command details.

axonius-get-devices-by-savedquery


Gather device info by saved query

Base Command

axonius-get-devices-by-savedquery

Input

Argument Name Description Required
saved_query_name The name of the devices saved query within Axonius. See https://docs.axonius.com/docs/saved-queries-devices. Required
max_results The maximum number of results to return. Default is 50. Optional
fields Comma separated list of Axonius fields to return. Optional

Context Output

Path Type Description
Axonius.Devices.adapter_list_length Number The number of adapters with information about the asset.
Axonius.Devices.adapters String The specific adapter names with asset information.
Axonius.Devices.internal_axon_id String The internal unique Axonius identifier for the asset.
Axonius.Devices.hostname String The hostnames of the assset.
Axonius.Devices.name String The names of the asset.
Axonius.Devices.last_seen Date Last seen date/time of the asset.
Axonius.Devices.network_interfaces_macs String The MAC addresses of the asset.
Axonius.Devices.network_interfaces_ips String The IP addresses of the asset.
Axonius.Devices.os_type String The OS type (Windows, Linux, macOS,…).
Axonius.Devices.labels String Tags assigned to the asset.

Command Example

!axonius-get-devices-by-savedquery saved_query_name=example_query

Context Example

{
    "Axonius": {
        "Devices": {
            "adapter_list_length": 5,
            "adapters": [
                "nexpose_adapter",
                "esx_adapter",
                "active_directory_adapter",
                "solarwinds_orion_adapter",
                "crowd_strike_adapter",
                "esx_adapter",
                "crowd_strike_adapter",
                "crowd_strike_adapter",
                "crowd_strike_adapter",
                "esx_adapter"
            ],
            "aggregated_hostname": [
                "DC4"
            ],
            "aggregated_last_seen": "2020-09-08T06:44:31+00:00",
            "aggregated_name": [
                "Windows%20Server%202012%20r2%20dc4.TestDomain.test%20(Avidor)",
                "DC4",
                "Windows Server 2012 R2",
                "Windows Server - 2012 - R2"
            ],
            "aggregated_network_interfaces_ips": [
                "x.x.x.x",
            ],
            "aggregated_network_interfaces_mac": [
                "00:0C:29:B6:DA:46",
                "00:50:56:91:DE:BB",
                "00:50:56:91:3A:EC",
                "00:50:56:91:33:E2",
                "00:50:56:91:21:B3"
            ],
            "aggregated_os_type": [
                "Windows"
            ],
            "internal_axon_id": "d530db3cfef6a2220b315d54fa1901b2"
        }
    }
}

Human Readable Output

Results

adapter_list_length adapters aggregated_hostname aggregated_last_seen aggregated_name aggregated_network_interfaces_ips aggregated_network_interfaces_mac aggregated_os_type internal_axon_id
5 nexpose_adapter,
esx_adapter,
active_directory_adapter,
solarwinds_orion_adapter,
crowd_strike_adapter,
esx_adapter,
crowd_strike_adapter,
crowd_strike_adapter,
crowd_strike_adapter,
esx_adapter
DC4 2020-09-08T06:44:31+00:00 Windows%20Server%202012%20r2%20dc4.TestDomain.test%20(Avidor),
DC4,
Windows Server 2012 R2,
Windows Server - 2012 - R2
192.168.20.17,
192.168.20.58,
fe80::2dba:9118:1fc8:7759,
192.168.20.36,
192.168.20.50,
192.168.20.61
00:0C:29:B6:DA:46,
00:50:56:91:DE:BB,
00:50:56:91:3A:EC,
00:50:56:91:33:E2,
00:50:56:91:21:B3
Windows d530db3cfef6a2220b315d54fa1901b2

axonius-get-devices-by-aql


Gather devices info by AQL query

Base Command

axonius-get-devices-by-aql

Input

Argument Name Description Required
query The AQL query to filter devices by. Required
max_results The maximum number of results to return. Default is 50. Optional
fields Comma separated list of Axonius fields to return. Optional

Context Output

Path Type Description
Axonius.Devices.adapter_list_length Number The number of adapters with information about the asset.
Axonius.Devices.adapters String The specific adapter names with asset information.
Axonius.Devices.internal_axon_id String The internal unique Axonius identifier for the asset.
Axonius.Devices.hostname String The hostnames of the assset.
Axonius.Devices.name String The names of the asset.
Axonius.Devices.last_seen Date Last seen date/time of the asset.
Axonius.Devices.network_interfaces_macs String The MAC addresses of the asset.
Axonius.Devices.network_interfaces_ips String The IP addresses of the asset.
Axonius.Devices.os_type String The OS type (Windows, Linux, macOS,…).
Axonius.Devices.labels String Tags assigned to the asset.

Command Example

!axonius-get-devices-by-aql query="((\"specific_data.data.name\" == ({\"$exists\":true,\"$ne\":\"\"})))" max_results="50"

Context Example

{
    "Axonius": {
        "Devices": {
            "adapter_list_length": 5,
            "adapters": [
                "nexpose_adapter",
                "esx_adapter",
                "active_directory_adapter",
                "solarwinds_orion_adapter",
                "crowd_strike_adapter",
                "esx_adapter",
                "crowd_strike_adapter",
                "crowd_strike_adapter",
                "crowd_strike_adapter",
                "esx_adapter"
            ],
            "aggregated_hostname": [
                "DC4"
            ],
            "aggregated_last_seen": "2020-09-08T06:44:31+00:00",
            "aggregated_name": [
                "Windows%20Server%202012%20r2%20dc4.TestDomain.test%20(Avidor)",
                "DC4",
                "Windows Server 2012 R2",
                "Windows Server - 2012 - R2"
            ],
            "aggregated_network_interfaces_ips": [
                "x.x.x.x",
            ],
            "aggregated_network_interfaces_mac": [
                "00:0C:29:B6:DA:46",
                "00:50:56:91:DE:BB",
                "00:50:56:91:3A:EC",
                "00:50:56:91:33:E2",
                "00:50:56:91:21:B3"
            ],
            "aggregated_os_type": [
                "Windows"
            ],
            "internal_axon_id": "d530db3cfef6a2220b315d54fa1901b2"
        }
    }
}

Human Readable Output

Results

adapter_list_length adapters aggregated_hostname aggregated_last_seen aggregated_name aggregated_network_interfaces_ips aggregated_network_interfaces_mac aggregated_os_type internal_axon_id
5 nexpose_adapter,
esx_adapter,
active_directory_adapter,
solarwinds_orion_adapter,
crowd_strike_adapter,
esx_adapter,
crowd_strike_adapter,
crowd_strike_adapter,
crowd_strike_adapter,
esx_adapter
DC4 2020-09-08T06:44:31+00:00 Windows%20Server%202012%20r2%20dc4.TestDomain.test%20(Avidor),
DC4,
Windows Server 2012 R2,
Windows Server - 2012 - R2
192.168.20.17,
192.168.20.58,
fe80::2dba:9118:1fc8:7759,
192.168.20.36,
192.168.20.50,
192.168.20.61
00:0C:29:B6:DA:46,
00:50:56:91:DE:BB,
00:50:56:91:3A:EC,
00:50:56:91:33:E2,
00:50:56:91:21:B3
Windows d530db3cfef6a2220b315d54fa1901b2

axonius-get-users-by-aql


Gather users info by AQL query

Base Command

axonius-get-users-by-aql

Input

Argument Name Description Required
query The AQL query to filter users by. Required
max_results The maximum number of results to return. Default is 50. Optional
fields Comma separated list of Axonius fields to return. Optional

Context Output

Path Type Description
Axonius.Users.adapter_list_length Number The number of adapters with information about the asset.
Axonius.Users.adapters String The specific adapter names with asset information.
Axonius.Users.internal_axon_id String The internal unique Axonius identifier for the asset.
Axonius.Users.username String Username of the asset.
Axonius.Users.mail String Email address of the asset.
Axonius.Users.is_admin Boolean If the asset has admin privileges.
Axonius.Users.last_seen Date Last seen date/time of the asset.
Axonius.Users.labels String Tags assigned to the asset.

Command Example

!axonius-get-users-by-aql query="((\"specific_data.data.username\" == ({\"$exists\":true,\"$ne\":\"\"})))" max_results="50"

Context Example

{
    "Axonius": {
        "Users": {
            "adapter_list_length": 1,
            "adapters": [
                "active_directory_adapter"
            ],
            "aggregated_domain": "TestDomain.test",
            "aggregated_is_admin": false,
            "aggregated_last_seen": "2018-11-01T14:48:59+00:00",
            "aggregated_username": "test_ldap_login_user",
            "internal_axon_id": "4d5f47f067388e8ffc53b6bbe8a10800"
        }
    }
}

Human Readable Output

Results

adapter_list_length adapters aggregated_domain aggregated_is_admin aggregated_last_seen aggregated_username internal_axon_id
1 active_directory_adapter TestDomain.test false 2018-11-01T14:48:59+00:00 test_ldap_login_user 4d5f47f067388e8ffc53b6bbe8a10800

axonius-get-users-by-savedquery


Gather user info by saved query

Base Command

axonius-get-users-by-savedquery

Input

Argument Name Description Required
saved_query_name The name of the users saved query within Axonius. See https://docs.axonius.com/docs/saved-queries-users. Required
max_results The maximum number of results to return. Default is 50. Optional
fields Comma separated list of Axonius fields to return. Optional

Context Output

Path Type Description
Axonius.Users.adapter_list_length Number The number of adapters with information about the asset.
Axonius.Users.adapters String The specific adapter names with asset information.
Axonius.Users.internal_axon_id String The internal unique Axonius identifier for the asset.
Axonius.Users.username String Username of the asset.
Axonius.Users.mail String Email address of the asset.
Axonius.Users.is_admin Boolean If the asset has admin privileges.
Axonius.Users.last_seen Date Last seen date/time of the asset.
Axonius.Users.labels String Tags assigned to the asset.

Command Example

!axonius-get-users-by-savedquery saved_query_name=example_query

Context Example

{
    "Axonius": {
        "Users": {
            "adapter_list_length": 1,
            "adapters": [
                "active_directory_adapter"
            ],
            "aggregated_domain": "TestDomain.test",
            "aggregated_is_admin": false,
            "aggregated_last_seen": "2018-11-01T14:48:59+00:00",
            "aggregated_username": "test_ldap_login_user",
            "internal_axon_id": "4d5f47f067388e8ffc53b6bbe8a10800"
        }
    }
}

Human Readable Output

Results

adapter_list_length adapters aggregated_domain aggregated_is_admin aggregated_last_seen aggregated_username internal_axon_id
1 active_directory_adapter TestDomain.test false 2018-11-01T14:48:59+00:00 test_ldap_login_user 4d5f47f067388e8ffc53b6bbe8a10800

axonius-get-users-by-mail


Gather user info by email address

Base Command

axonius-get-users-by-mail

Input

Argument Name Description Required
value The user email address to search for within Axonius. Required
max_results The maximum number of results to return. Default is 50. Optional
fields Comma separated list of Axonius fields to return. Optional

Context Output

Path Type Description
Axonius.Users.adapter_list_length Number The number of adapters with information about the asset.
Axonius.Users.adapters String The specific adapter names with asset information.
Axonius.Users.internal_axon_id String The internal unique Axonius identifier for the asset.
Axonius.Users.username String Username of the asset.
Axonius.Users.mail String Email address of the asset.
Axonius.Users.is_admin Boolean If the asset has admin privileges.
Axonius.Users.last_seen Date Last seen date/time of the asset.
Axonius.Users.labels String Tags assigned to the asset.

Command Example

!axonius-get-users-by-mail value=Administrator@testdomain.test

Context Example

{
    "Axonius": {
        "Users": {
            "adapter_list_length": 1,
            "adapters": [
                "active_directory_adapter"
            ],
            "aggregated_mail": [
                "Administrator@testdomain.test"
            ],
            "aggregated_username": [
                "Administrator"
            ],
            "internal_axon_id": "a6f0d051a30d401b7f73416fbc90a3cf"
        }
    }
}

Human Readable Output

Results

adapter_list_length adapters aggregated_mail aggregated_username internal_axon_id
1 active_directory_adapter Administrator@testdomain.test Administrator a6f0d051a30d401b7f73416fbc90a3cf

axonius-get-users-by-mail-regex


Gather user info by email address using regex

Base Command

axonius-get-users-by-mail-regex

Input

Argument Name Description Required
value The user email address to search for within Axonius. Required
max_results The maximum number of results to return. Default is 50. Optional
fields Comma separated list of Axonius fields to return. Optional

Context Output

Path Type Description
Axonius.Users.adapter_list_length Number The number of adapters with information about the asset.
Axonius.Users.adapters String The specific adapter names with asset information.
Axonius.Users.internal_axon_id String The internal unique Axonius identifier for the asset.
Axonius.Users.username String Username of the asset.
Axonius.Users.mail String Email address of the asset.
Axonius.Users.is_admin Boolean If the asset has admin privileges.
Axonius.Users.last_seen Date Last seen date/time of the asset.
Axonius.Users.labels String Tags assigned to the asset.

Command Example

!axonius-get-users-by-mail-regex value=Administrator

Context Example

{
    "Axonius": {
        "Users": {
            "adapter_list_length": 1,
            "adapters": [
                "active_directory_adapter"
            ],
            "aggregated_mail": [
                "Administrator@testdomain.test"
            ],
            "aggregated_username": [
                "Administrator"
            ],
            "internal_axon_id": "a6f0d051a30d401b7f73416fbc90a3cf"
        }
    }
}

Human Readable Output

Results

adapter_list_length adapters aggregated_mail aggregated_username internal_axon_id
1 active_directory_adapter Administrator@testdomain.test Administrator a6f0d051a30d401b7f73416fbc90a3cf

axonius-get-users-by-username


Gather user info by username

Base Command

axonius-get-users-by-username

Input

Argument Name Description Required
value The username to search for within Axonius. Required
max_results The maximum number of results to return. Default is 50. Optional
fields Comma separated list of Axonius fields to return. Optional

Context Output

Path Type Description
Axonius.Users.adapter_list_length Number The number of adapters with information about the asset.
Axonius.Users.adapters String The specific adapter names with asset information.
Axonius.Users.internal_axon_id String The internal unique Axonius identifier for the asset.
Axonius.Users.username String Username of the asset.
Axonius.Users.mail String Email address of the asset.
Axonius.Users.is_admin Boolean If the asset has admin privileges.
Axonius.Users.last_seen Date Last seen date/time of the asset.
Axonius.Users.labels String Tags assigned to the asset.

Command Example

!axonius-get-users-by-username value=test_ldap_login_user

Context Example

{
    "Axonius": {
        "Users": {
            "adapter_list_length": 1,
            "adapters": [
                "active_directory_adapter"
            ],
            "aggregated_username": "test_ldap_login_user",
            "internal_axon_id": "4d5f47f067388e8ffc53b6bbe8a10800"
        }
    }
}

Human Readable Output

Results

adapter_list_length adapters aggregated_username internal_axon_id
1 active_directory_adapter test_ldap_login_user 4d5f47f067388e8ffc53b6bbe8a10800

axonius-get-users-by-username-regex


Gather user info by username using regex

Base Command

axonius-get-users-by-username-regex

Input

Argument Name Description Required
value The username to search for within Axonius. Required
max_results The maximum number of results to return. Default is 50. Optional
fields Comma separated list of Axonius fields to return. Optional

Context Output

Path Type Description
Axonius.Users.adapter_list_length Number The number of adapters with information about the asset.
Axonius.Users.adapters String The specific adapter names with asset information.
Axonius.Users.internal_axon_id String The internal unique Axonius identifier for the asset.
Axonius.Users.username String Username of the asset.
Axonius.Users.mail String Email address of the asset.
Axonius.Users.is_admin Boolean If the asset has admin privileges.
Axonius.Users.last_seen Date Last seen date/time of the asset.
Axonius.Users.labels String Tags assigned to the asset.

Command Example

!axonius-get-users-by-username-regex value=test

Context Example

{
    "Axonius": {
        "Users": {
            "adapter_list_length": 1,
            "adapters": [
                "active_directory_adapter"
            ],
            "aggregated_username": "test_ldap_login_user",
            "internal_axon_id": "4d5f47f067388e8ffc53b6bbe8a10800"
        }
    }
}

Human Readable Output

Results

adapter_list_length adapters aggregated_username internal_axon_id
1 active_directory_adapter test_ldap_login_user 4d5f47f067388e8ffc53b6bbe8a10800

axonius-get-devices-by-hostname


Gather device info by hostname

Base Command

axonius-get-devices-by-hostname

Input

Argument Name Description Required
value The hostname to search for within Axonius. Required
max_results The maximum number of results to return. Default is 50. Optional
fields Comma separated list of Axonius fields to return. Optional

Context Output

Path Type Description
Axonius.Devices.adapter_list_length Number The number of adapters with information about the asset.
Axonius.Devices.adapters String The specific adapter names with asset information.
Axonius.Devices.internal_axon_id String The internal unique Axonius identifier for the asset.
Axonius.Devices.hostname String The hostnames of the assset.
Axonius.Devices.name String The names of the asset.
Axonius.Devices.last_seen Date Last seen date/time of the asset.
Axonius.Devices.network_interfaces_macs String The MAC addresses of the asset.
Axonius.Devices.network_interfaces_ips String The IP addresses of the asset.
Axonius.Devices.os_type String The OS type (Windows, Linux, macOS,…).
Axonius.Devices.labels String Tags assigned to the asset.

Command Example

!axonius-get-devices-by-hostname value=DC4

Context Example

{
    "Axonius": {
        "Devices": {
            "adapter_list_length": 5,
            "adapters": [
                "nexpose_adapter",
                "esx_adapter",
                "active_directory_adapter",
                "solarwinds_orion_adapter",
                "crowd_strike_adapter",
                "esx_adapter",
                "crowd_strike_adapter",
                "crowd_strike_adapter",
                "crowd_strike_adapter",
                "esx_adapter"
            ],
            "aggregated_hostname": [
                "DC4"
            ],
            "aggregated_network_interfaces_ips": [
                "x.x.x.x",
            ],
            "aggregated_network_interfaces_mac": [
                "00:0C:29:B6:DA:46",
                "00:50:56:91:DE:BB",
                "00:50:56:91:3A:EC",
                "00:50:56:91:33:E2",
                "00:50:56:91:21:B3"
            ],
            "aggregated_network_interfaces_subnets": [
                "x.x.x.x/24"
            ],
            "internal_axon_id": "d530db3cfef6a2220b315d54fa1901b2"
        }
    }
}

Human Readable Output

Results

adapter_list_length adapters aggregated_hostname aggregated_network_interfaces_ips aggregated_network_interfaces_mac aggregated_network_interfaces_subnets internal_axon_id
5 nexpose_adapter,
esx_adapter,
active_directory_adapter,
solarwinds_orion_adapter,
crowd_strike_adapter,
esx_adapter,
crowd_strike_adapter,
crowd_strike_adapter,
crowd_strike_adapter,
esx_adapter
DC4 192.168.20.17,
192.168.20.58,
fe80::2dba:9118:1fc8:7759,
192.168.20.36,
192.168.20.50,
192.168.20.61
00:0C:29:B6:DA:46,
00:50:56:91:DE:BB,
00:50:56:91:3A:EC,
00:50:56:91:33:E2,
00:50:56:91:21:B3
x.x.x.x/24 d530db3cfef6a2220b315d54fa1901b2

axonius-get-devices-by-hostname-regex


Gather device info by hostname using regex

Base Command

axonius-get-devices-by-hostname-regex

Input

Argument Name Description Required
value The hostname to search for within Axonius using regex. Required
max_results The maximum number of results to return. Default is 50. Optional
fields Comma separated list of Axonius fields to return. Optional

Context Output

Path Type Description
Axonius.Devices.adapter_list_length Number The number of adapters with information about the asset.
Axonius.Devices.adapters String The specific adapter names with asset information.
Axonius.Devices.internal_axon_id String The internal unique Axonius identifier for the asset.
Axonius.Devices.hostname String The hostnames of the assset.
Axonius.Devices.name String The names of the asset.
Axonius.Devices.last_seen Date Last seen date/time of the asset.
Axonius.Devices.network_interfaces_macs String The MAC addresses of the asset.
Axonius.Devices.network_interfaces_ips String The IP addresses of the asset.
Axonius.Devices.os_type String The OS type (Windows, Linux, macOS,…).
Axonius.Devices.labels String Tags assigned to the asset.

Command Example

!axonius-get-devices-by-hostname-regex value=DC4

Context Example

{
    "Axonius": {
        "Devices": {
            "adapter_list_length": 5,
            "adapters": [
                "nexpose_adapter",
                "esx_adapter",
                "active_directory_adapter",
                "solarwinds_orion_adapter",
                "crowd_strike_adapter",
                "esx_adapter",
                "crowd_strike_adapter",
                "crowd_strike_adapter",
                "crowd_strike_adapter",
                "esx_adapter"
            ],
            "aggregated_hostname": [
                "DC4"
            ],
            "aggregated_network_interfaces_ips": [
                "x.x.x.x",
            ],
            "aggregated_network_interfaces_mac": [
                "00:0C:29:B6:DA:46",
                "00:50:56:91:DE:BB",
                "00:50:56:91:3A:EC",
                "00:50:56:91:33:E2",
                "00:50:56:91:21:B3"
            ],
            "aggregated_network_interfaces_subnets": [
                "x.x.x.x/24"
            ],
            "internal_axon_id": "d530db3cfef6a2220b315d54fa1901b2"
        }
    }
}

Human Readable Output

Results

adapter_list_length adapters aggregated_hostname aggregated_network_interfaces_ips aggregated_network_interfaces_mac aggregated_network_interfaces_subnets internal_axon_id
5 nexpose_adapter,
esx_adapter,
active_directory_adapter,
solarwinds_orion_adapter,
crowd_strike_adapter,
esx_adapter,
crowd_strike_adapter,
crowd_strike_adapter,
crowd_strike_adapter,
esx_adapter
DC4 192.168.20.17,
192.168.20.58,
fe80::2dba:9118:1fc8:7759,
192.168.20.36,
192.168.20.50,
192.168.20.61
00:0C:29:B6:DA:46,
00:50:56:91:DE:BB,
00:50:56:91:3A:EC,
00:50:56:91:33:E2,
00:50:56:91:21:B3
x.x.x.x/24 d530db3cfef6a2220b315d54fa1901b2

axonius-get-devices-by-ip


Gather device info by IP address

Base Command

axonius-get-devices-by-ip

Input

Argument Name Description Required
value The IP address to search for within Axonius. Required
max_results The maximum number of results to return. Default is 50. Optional
fields Comma separated list of Axonius fields to return. Optional

Context Output

Path Type Description
Axonius.Devices.adapter_list_length Number The number of adapters with information about the asset.
Axonius.Devices.adapters String The specific adapter names with asset information.
Axonius.Devices.internal_axon_id String The internal unique Axonius identifier for the asset.
Axonius.Devices.hostname String The hostnames of the assset.
Axonius.Devices.name String The names of the asset.
Axonius.Devices.last_seen Date Last seen date/time of the asset.
Axonius.Devices.network_interfaces_macs String The MAC addresses of the asset.
Axonius.Devices.network_interfaces_ips String The IP addresses of the asset.
Axonius.Devices.os_type String The OS type (Windows, Linux, macOS,…).
Axonius.Devices.labels String Tags assigned to the asset.

Command Example

!axonius-get-devices-by-ip value=192.168.20.17

Context Example

{
    "Axonius": {
        "Devices": {
            "adapter_list_length": 5,
            "adapters": [
                "nexpose_adapter",
                "esx_adapter",
                "active_directory_adapter",
                "solarwinds_orion_adapter",
                "crowd_strike_adapter",
                "esx_adapter",
                "crowd_strike_adapter",
                "crowd_strike_adapter",
                "crowd_strike_adapter",
                "esx_adapter"
            ],
            "aggregated_hostname": [
                "DC4"
            ],
            "aggregated_network_interfaces_ips": [
                "x.x.x.x",
            ],
            "aggregated_network_interfaces_mac": [
                "00:0C:29:B6:DA:46",
                "00:50:56:91:DE:BB",
                "00:50:56:91:3A:EC",
                "00:50:56:91:33:E2",
                "00:50:56:91:21:B3"
            ],
            "aggregated_network_interfaces_subnets": [
                "x.x.x.x/24"
            ],
            "internal_axon_id": "d530db3cfef6a2220b315d54fa1901b2"
        }
    }
}

Human Readable Output

Results

adapter_list_length adapters aggregated_hostname aggregated_network_interfaces_ips aggregated_network_interfaces_mac aggregated_network_interfaces_subnets internal_axon_id
5 nexpose_adapter,
esx_adapter,
active_directory_adapter,
solarwinds_orion_adapter,
crowd_strike_adapter,
esx_adapter,
crowd_strike_adapter,
crowd_strike_adapter,
crowd_strike_adapter,
esx_adapter
DC4 192.168.20.17,
192.168.20.58,
fe80::2dba:9118:1fc8:7759,
192.168.20.36,
192.168.20.50,
192.168.20.61
00:0C:29:B6:DA:46,
00:50:56:91:DE:BB,
00:50:56:91:3A:EC,
00:50:56:91:33:E2,
00:50:56:91:21:B3
x.x.x.x/24 d530db3cfef6a2220b315d54fa1901b2

axonius-get-devices-by-ip-regex


Gather device info by IP address using regex

Base Command

axonius-get-devices-by-ip-regex

Input

Argument Name Description Required
value The IP address to search for within Axonius. Required
max_results The maximum number of results to return. Default is 50. Optional
fields Comma separated list of Axonius fields to return. Optional

Context Output

Path Type Description
Axonius.Devices.adapter_list_length Number The number of adapters with information about the asset.
Axonius.Devices.adapters String The specific adapter names with asset information.
Axonius.Devices.internal_axon_id String The internal unique Axonius identifier for the asset.
Axonius.Devices.hostname String The hostnames of the assset.
Axonius.Devices.name String The names of the asset.
Axonius.Devices.last_seen Date Last seen date/time of the asset.
Axonius.Devices.network_interfaces_macs String The MAC addresses of the asset.
Axonius.Devices.network_interfaces_ips String The IP addresses of the asset.
Axonius.Devices.os_type String The OS type (Windows, Linux, macOS,…).
Axonius.Devices.labels String Tags assigned to the asset.

Command Example

!axonius-get-devices-by-ip-regex value=192.168

Context Example

{
    "Axonius": {
        "Devices": {
            "adapter_list_length": 5,
            "adapters": [
                "nexpose_adapter",
                "esx_adapter",
                "active_directory_adapter",
                "solarwinds_orion_adapter",
                "crowd_strike_adapter",
                "esx_adapter",
                "crowd_strike_adapter",
                "crowd_strike_adapter",
                "crowd_strike_adapter",
                "esx_adapter"
            ],
            "aggregated_hostname": [
                "DC4"
            ],
            "aggregated_network_interfaces_ips": [
                "x.x.x.x",
            ],
            "aggregated_network_interfaces_mac": [
                "00:0C:29:B6:DA:46",
                "00:50:56:91:DE:BB",
                "00:50:56:91:3A:EC",
                "00:50:56:91:33:E2",
                "00:50:56:91:21:B3"
            ],
            "aggregated_network_interfaces_subnets": [
                "x.x.x.x/24"
            ],
            "internal_axon_id": "d530db3cfef6a2220b315d54fa1901b2"
        }
    }
}

Human Readable Output

Results

adapter_list_length adapters aggregated_hostname aggregated_network_interfaces_ips aggregated_network_interfaces_mac aggregated_network_interfaces_subnets internal_axon_id
5 nexpose_adapter,
esx_adapter,
active_directory_adapter,
solarwinds_orion_adapter,
crowd_strike_adapter,
esx_adapter,
crowd_strike_adapter,
crowd_strike_adapter,
crowd_strike_adapter,
esx_adapter
DC4 192.168.20.17,
192.168.20.58,
fe80::2dba:9118:1fc8:7759,
192.168.20.36,
192.168.20.50,
192.168.20.61
00:0C:29:B6:DA:46,
00:50:56:91:DE:BB,
00:50:56:91:3A:EC,
00:50:56:91:33:E2,
00:50:56:91:21:B3
x.x.x.x/24 d530db3cfef6a2220b315d54fa1901b2

axonius-get-devices-by-mac


Gather device info by MAC address

Base Command

axonius-get-devices-by-mac

Input

Argument Name Description Required
value The MAC address to search for within Axonius. Required
max_results The maximum number of results to return. Default is 50. Optional
fields Comma separated list of Axonius fields to return. Optional

Context Output

Path Type Description
Axonius.Devices.adapter_list_length Number The number of adapters with information about the asset.
Axonius.Devices.adapters String The specific adapter names with asset information.
Axonius.Devices.internal_axon_id String The internal unique Axonius identifier for the asset.
Axonius.Devices.hostname String The hostnames of the assset.
Axonius.Devices.name String The names of the asset.
Axonius.Devices.last_seen Date Last seen date/time of the asset.
Axonius.Devices.network_interfaces_macs String The MAC addresses of the asset.
Axonius.Devices.network_interfaces_ips String The IP addresses of the asset.
Axonius.Devices.os_type String The OS type (Windows, Linux, macOS,…).
Axonius.Devices.labels String Tags assigned to the asset.

Command Example

!axonius-get-devices-by-mac value=00:0C:29:B6:DA:46

Context Example

{
    "Axonius": {
        "Devices": {
            "adapter_list_length": 5,
            "adapters": [
                "nexpose_adapter",
                "esx_adapter",
                "active_directory_adapter",
                "solarwinds_orion_adapter",
                "crowd_strike_adapter",
                "esx_adapter",
                "crowd_strike_adapter",
                "crowd_strike_adapter",
                "crowd_strike_adapter",
                "esx_adapter"
            ],
            "aggregated_hostname": [
                "DC4"
            ],
            "aggregated_network_interfaces_ips": [
                "x.x.x.x",
            ],
            "aggregated_network_interfaces_mac": [
                "00:0C:29:B6:DA:46",
                "00:50:56:91:DE:BB",
                "00:50:56:91:3A:EC",
                "00:50:56:91:33:E2",
                "00:50:56:91:21:B3"
            ],
            "aggregated_network_interfaces_subnets": [
                "x.x.x.x/24"
            ],
            "internal_axon_id": "d530db3cfef6a2220b315d54fa1901b2"
        }
    }
}

Human Readable Output

Results

adapter_list_length adapters aggregated_hostname aggregated_network_interfaces_ips aggregated_network_interfaces_mac aggregated_network_interfaces_subnets internal_axon_id
5 nexpose_adapter,
esx_adapter,
active_directory_adapter,
solarwinds_orion_adapter,
crowd_strike_adapter,
esx_adapter,
crowd_strike_adapter,
crowd_strike_adapter,
crowd_strike_adapter,
esx_adapter
DC4 192.168.20.17,
192.168.20.58,
fe80::2dba:9118:1fc8:7759,
192.168.20.36,
192.168.20.50,
192.168.20.61
00:0C:29:B6:DA:46,
00:50:56:91:DE:BB,
00:50:56:91:3A:EC,
00:50:56:91:33:E2,
00:50:56:91:21:B3
x.x.x.x/24 d530db3cfef6a2220b315d54fa1901b2

axonius-get-devices-by-mac-regex


Gather device info by MAC address using regex

Base Command

axonius-get-devices-by-mac-regex

Input

Argument Name Description Required
value The MAC address to search for within Axonius. Required
max_results The maximum number of results to return. Default is 50. Optional
fields Comma separated list of Axonius fields to return. Optional

Context Output

Path Type Description
Axonius.Devices.adapter_list_length Number The number of adapters with information about the asset.
Axonius.Devices.adapters String The specific adapter names with asset information.
Axonius.Devices.internal_axon_id String The internal unique Axonius identifier for the asset.
Axonius.Devices.hostname String The hostnames of the assset.
Axonius.Devices.name String The names of the asset.
Axonius.Devices.last_seen Date Last seen date/time of the asset.
Axonius.Devices.network_interfaces_macs String The MAC addresses of the asset.
Axonius.Devices.network_interfaces_ips String The IP addresses of the asset.
Axonius.Devices.os_type String The OS type (Windows, Linux, macOS,…).
Axonius.Devices.labels String Tags assigned to the asset.

Command Example

!axonius-get-devices-by-mac-regex value=DA:46

Context Example

{
    "Axonius": {
        "Devices": {
            "adapter_list_length": 5,
            "adapters": [
                "nexpose_adapter",
                "esx_adapter",
                "active_directory_adapter",
                "solarwinds_orion_adapter",
                "crowd_strike_adapter",
                "esx_adapter",
                "crowd_strike_adapter",
                "crowd_strike_adapter",
                "crowd_strike_adapter",
                "esx_adapter"
            ],
            "aggregated_hostname": [
                "DC4"
            ],
            "aggregated_network_interfaces_ips": [
                "x.x.x.x",
            ],
            "aggregated_network_interfaces_mac": [
                "00:0C:29:B6:DA:46",
                "00:50:56:91:DE:BB",
                "00:50:56:91:3A:EC",
                "00:50:56:91:33:E2",
                "00:50:56:91:21:B3"
            ],
            "aggregated_network_interfaces_subnets": [
                "x.x.x.x/24"
            ],
            "internal_axon_id": "d530db3cfef6a2220b315d54fa1901b2"
        }
    }
}

Human Readable Output

Results

adapter_list_length adapters aggregated_hostname aggregated_network_interfaces_ips aggregated_network_interfaces_mac aggregated_network_interfaces_subnets internal_axon_id
5 nexpose_adapter,
esx_adapter,
active_directory_adapter,
solarwinds_orion_adapter,
crowd_strike_adapter,
esx_adapter,
crowd_strike_adapter,
crowd_strike_adapter,
crowd_strike_adapter,
esx_adapter
DC4 192.168.20.17,
192.168.20.58,
fe80::2dba:9118:1fc8:7759,
192.168.20.36,
192.168.20.50,
192.168.20.61
00:0C:29:B6:DA:46,
00:50:56:91:DE:BB,
00:50:56:91:3A:EC,
00:50:56:91:33:E2,
00:50:56:91:21:B3
x.x.x.x/24 d530db3cfef6a2220b315d54fa1901b2

axonius-add-note


Add note to assets

Base Command

axonius-add-note

Input

Argument Name Description Required
note Note to add. Required
ids IDs of assets. Required
type Type of Asset. Device or User. Possible values are: devices, users. Required

Context Output

Path Type Description
Axonius.assets.updates Number Number of assets updated.

axonius-add-tag


Add tag to assets

Base Command

axonius-add-tag

Input

Argument Name Description Required
tag_name Name of tag to add. Required
ids IDs of assets. Required
type Type of Asset. Device or User. Possible values are: devices, users. Required

Context Output

Path Type Description
Axonius.assets.updates Number Number of assets updated.

axonius-remove-tag


Remove tag from assets

Base Command

axonius-remove-tag

Input

Argument Name Description Required
tag_name Name of tag to remove. Required
ids IDs of assets. Required
type Type of Asset. Devices or Users. Possible values are: devices, users. Optional

Context Output

Path Type Description
Axonius.asset.updates Number Number of assets updated.

axonius-get-saved-queries


Get all saved query of a given asset type.

Base Command

axonius-get-saved-queries

Input

Argument Name Description Required
type Type of Asset. Device or User. Possible values are: devices, users. Required

Context Output

Path Type Description
Axonius.Devices.saved_queries Unknown Saved queries.
Axonius.Users.saved_queries Unknown Saved queries.

axonius-get-tags


Get all tags of a given asset type.

Base Command

axonius-get-tags

Input

Argument Name Description Required
type Type of Asset. Device or User. Possible values are: devices, users. Required

Context Output

Path Type Description
Axonius.tags Unknown Axonius Tags.

axonius-get-assets


Fetch assets of any type using the Axonius v2 API (POST /api/v2/assets/{asset_type}). Supports all asset types including vulnerability_instances. Use next_token for pagination. Large responses (>10 MB) may be stored as a downloadable file by XSOAR instead of being written to the context. NOTE: All calls write to the fixed context key Axonius.Assets regardless of asset_type. If a playbook calls this command more than once with different asset types, each call overwrites the previous result. To avoid data loss, store or transform the context output (e.g. using Set) before issuing a second call with a different type.

Base Command

axonius-get-assets

Input

Argument Name Description Required
asset_type The asset type to fetch (e.g. devices, users, vulnerability_instances). See axonius-get-asset-types for all supported values. Required
query The AQL filter string to narrow the returned assets. Optional
fields A comma-separated list of fields to include in the response. Optional
fields_to_exclude A comma-separated list of fields to exclude from the response. Optional
page_size The number of assets to request per page (1–2000). Keep this value small for large asset types (e.g. vulnerability_instances) to avoid XSOAR context size limits (~10 MB). Default is 50. Optional
limit The maximum number of assets to return from the page (defaults to page_size). Optional
next_token The pagination cursor returned from a previous call as Axonius.Assets.next_token. Pass this value to retrieve the next page of results. Optional
include_metadata Whether to include metadata in the response. Possible values are: true, false. Default is false. Optional
include_details Whether to include detailed asset data in the response. Possible values are: true, false. Default is false. Optional
use_cache_entry Whether to use a cached response entry if available. Possible values are: true, false. Default is false. Optional

Context Output

Path Type Description
Axonius.Assets.asset_type String The asset type that was queried.
Axonius.Assets.assets Unknown The list of asset records returned by the query.
Axonius.Assets.count Number The number of assets returned in this page.
Axonius.Assets.total_count Number The total number of assets matching the query (when available).
Axonius.Assets.next_token String The pagination cursor for the next page of results. Pass this as next_token in the next call.

axonius-get-asset-types


Return the list of all available asset types in the Axonius instance (GET /api/v2/assets/asset_types).

Base Command

axonius-get-asset-types

Input

There are no input arguments for this command.

Context Output

Path Type Description
Axonius.AssetTypes.asset_type String The asset type name.
Axonius.AssetTypes Unknown The list of available asset types.

axonius-get-custom-data


List custom data management entries (GET /api/v2/custom_data_management).

Base Command

axonius-get-custom-data

Input

Argument Name Description Required
page The page number to retrieve (1-indexed). Default is 1. Optional
page_size The number of entries to return per page. Default is 50. Optional
limit The maximum number of entries to return (defaults to page_size). Optional

Context Output

Path Type Description
Axonius.CustomData.id String The custom data entry unique identifier.
Axonius.CustomData Unknown The list of custom data entries.

axonius-create-custom-data


Create a new custom data entry (POST /api/v2/custom_data_management).

Base Command

axonius-create-custom-data

Input

Argument Name Description Required
payload The JSON string representing the custom data payload to create. Refer to Axonius API documentation for the required schema. Required

Context Output

Path Type Description
Axonius.CustomData Unknown The created custom data entry.

axonius-delete-custom-data


Delete a custom data entry by ID (DELETE /api/v2/custom_data_management/{id}).

Base Command

axonius-delete-custom-data

Input

Argument Name Description Required
id The ID of the custom data entry to delete. Required

Context Output

Path Type Description
Axonius.CustomData.id String The ID of the deleted custom data entry.
Axonius.CustomData.deleted Boolean The flag indicating whether the entry was deleted.

axonius-get-enforcements


List enforcement sets (GET /api/v2/enforcements).

Base Command

axonius-get-enforcements

Input

Argument Name Description Required
page The page number to retrieve (1-indexed). Default is 1. Optional
page_size The number of enforcements to return per page. Default is 50. Optional
limit The maximum number of enforcements to return (defaults to page_size). Optional

Context Output

Path Type Description
Axonius.Enforcements.uuid String The enforcement unique identifier.
Axonius.Enforcements.name String The enforcement name.
Axonius.Enforcements Unknown The full list of enforcement objects.

axonius-run-enforcement


Trigger an enforcement run (POST /api/v2/enforcements/{enforcement_id}/run).

Base Command

axonius-run-enforcement

Input

Argument Name Description Required
enforcement_id The UUID of the enforcement to run. Required

Context Output

Path Type Description
Axonius.Enforcements.enforcement_id String The ID of the enforcement that was triggered.
Axonius.Enforcements.triggered Boolean The flag indicating whether the enforcement was triggered successfully.

axonius-get-queries


List saved queries (GET /api/v2/queries).

Base Command

axonius-get-queries

Input

Argument Name Description Required
asset_type The asset type to filter queries by (e.g. devices, users). Optional
page The page number to retrieve (1-indexed). Default is 1. Optional
page_size The number of queries to return per page. Default is 50. Optional
limit The maximum number of queries to return (defaults to page_size). Optional

Context Output

Path Type Description
Axonius.Queries.uuid String The query unique identifier.
Axonius.Queries.name String The query name.
Axonius.Queries Unknown The full list of query objects.

axonius-create-query


Create a new saved query (POST /api/v2/queries).

Base Command

axonius-create-query

Input

Argument Name Description Required
name The name for the new saved query. Required
query The AQL filter string for the query. Required
asset_type The asset type this query applies to. Default is devices. Optional
description The optional description for the query. Optional

Context Output

Path Type Description
Axonius.Queries.uuid String The UUID of the created query.
Axonius.Queries.name String The name of the created query.
Axonius.Queries Unknown The created query object.

axonius-delete-query


Delete a saved query (DELETE /api/v2/queries/{query_id}).

Base Command

axonius-delete-query

Input

Argument Name Description Required
query_id The UUID of the query to delete. Required

Context Output

Path Type Description
Axonius.Queries.query_id String The ID of the deleted query.
Axonius.Queries.deleted Boolean The flag indicating whether the query was deleted.

axonius-get-grouped-vulnerabilities


Fetch all vulnerability instances, flatten them, group by CVE ID, and return the Top N CVEs sorted by affected host count. Computes average_cvss_score per CVE. Outputs as Axonius.GroupedVulnerabilities keyed by cve_id.

Base Command

axonius-get-grouped-vulnerabilities

Input

Argument Name Description Required
query The optional AQL filter applied before grouping. Optional
team_name The team name to filter vulnerability instances by. Optional
urgent Whether to filter by urgency on vulnerability instances. Possible values are: true, false. Optional
top_n The number of top CVEs to return (sorted by affected_hosts_count descending). Default is 10. Optional
page_size The number of records per page when fetching all instances. Default is 100. Optional

Context Output

Path Type Description
Axonius.GroupedVulnerabilities.cve_id String The CVE identifier.
Axonius.GroupedVulnerabilities.affected_hosts_count Number The number of hosts affected by this CVE.
Axonius.GroupedVulnerabilities.average_cvss_score Number The average CVSS score across all instances of this CVE.

Configuration parameters

  • ax_url — Server URL (e.g. https://example.net) (required)
  • credentials — API Key (required)
  • insecure — Trust any certificate (not secure)
  • proxy — Use system proxy settings

Commands (30)

  • axonius-add-note

    Add note to assets.

  • axonius-add-tag

    Add tag to assets.

  • axonius-create-custom-data

    Create a new custom data entry (POST /api/v2/custom_data_management).

  • axonius-create-query

    Create a new saved query (POST /api/v2/queries).

  • axonius-delete-custom-data

    Delete a custom data entry by ID (DELETE /api/v2/custom_data_management/{id}).

  • axonius-delete-query

    Delete a saved query (DELETE /api/v2/queries/{query_id}).

  • axonius-get-asset-types

    Return the list of all available asset types in the Axonius instance (GET /api/v2/assets/asset_types).

  • axonius-get-assets

    Fetch assets of any type using the Axonius v2 API (POST /api/v2/assets/{asset_type}). Supports all asset types including vulnerability_instances. Use next_token for pagination. Large responses (>10 MB) may be stored as a downloadable file by XSOAR instead of being written to the context. NOTE: All calls write to the fixed context key Axonius.Assets regardless of asset_type. If a playbook calls this command more than once with different asset types, each call overwrites the previous result. To avoid data loss, store or transform the context output (e.g. using Set) before issuing a second call with a different type.

  • axonius-get-custom-data

    List custom data management entries (GET /api/v2/custom_data_management).

  • axonius-get-devices-by-aql

    Gather devices info by AQL query.

  • axonius-get-devices-by-hostname

    Gather device info by hostname.

  • axonius-get-devices-by-hostname-regex

    Gather device info by hostname using regex.

  • axonius-get-devices-by-ip

    Gather device info by IP address.

  • axonius-get-devices-by-ip-regex

    Gather device info by IP address using regex.

  • axonius-get-devices-by-mac

    Gather device info by MAC address.

  • axonius-get-devices-by-mac-regex

    Gather device info by MAC address using regex.

  • axonius-get-devices-by-savedquery

    Gather device info by saved query.

  • axonius-get-enforcements

    List enforcement sets (GET /api/v2/enforcements).

  • axonius-get-grouped-vulnerabilities

    Fetch all vulnerability instances, flatten them, group by CVE ID, and return the Top N CVEs sorted by affected host count. Computes average_cvss_score per CVE. Outputs as Axonius.GroupedVulnerabilities keyed by cve_id.

  • axonius-get-queries

    List saved queries (GET /api/v2/queries).

  • axonius-get-saved-queries

    Get all saved query of a given asset type.

  • axonius-get-tags

    Get all tags of a given asset type.

  • axonius-get-users-by-aql

    Gather users info by AQL query.

  • axonius-get-users-by-mail

    Gather user info by email address.

  • axonius-get-users-by-mail-regex

    Gather user info by email address using regex.

  • axonius-get-users-by-savedquery

    Gather user info by saved query.

  • axonius-get-users-by-username

    Gather user info by username.

  • axonius-get-users-by-username-regex

    Gather user info by username using regex.

  • axonius-remove-tag

    Remove tag from assets.

  • axonius-run-enforcement

    Trigger an enforcement run (POST /api/v2/enforcements/{enforcement_id}/run).

"""Demisto Integration for Axonius."""

import json
import requests
from axonius_api_client.api.assets.devices import Devices
from axonius_api_client.api.assets.users import Users
from axonius_api_client.connect import Connect
from axonius_api_client.tools import dt_parse, strip_left
from CommonServerPython import *

# Added ignore RemovedInMarshmallow4Warning in Axonius_test file.


MAX_ROWS: int = 50
"""Maximum number of assets to allow user to fetch."""
SKIPS: List[str] = ["specific_data.data.image", "view"]
"""Fields to remove from each asset if found."""
FIELDS_TIME: List[str] = ["seen", "fetch", "time", "date"]
"""Fields to try and convert to date time if they have these words in them."""
AXONIUS_ID = "internal_axon_id"
V2_PAGE_SIZE_DEFAULT: int = 50
"""Default page size for v2 API pagination."""
V2_PAGE_SIZE_ALL_PAGES: int = 100
"""Page size used when fetching all pages."""
MAX_PAGES: int = 200
"""Maximum number of pages to fetch in a single paginated request (infinite-loop guard)."""
REQUEST_TIMEOUT: int = 30
"""HTTP request timeout in seconds to prevent silent hangs in Playbooks."""


def get_int_arg(
    key: str,
    required: Optional[bool] = False,
    default: Optional[int] = None,
) -> int:
    """Get a key from a command arg and convert it into an int."""
    args: dict = demisto.args()
    value: int = args.get(key, default)

    if value is None and required:
        raise ValueError(f"No value supplied for argument {key!r}")

    try:
        return int(value)
    except Exception:
        raise ValueError(f"Supplied value {value!r} for argument {key!r} is not an integer.")


def get_csv_arg(
    key: str,
    required: Optional[bool] = False,
    default: Optional[str] = "",
) -> List[str]:
    """Get string values from CSV."""
    args: dict = demisto.args()
    value: List[str] = argToList(arg=args.get(key, default))
    value = [x for x in value if x]

    if not value and required:
        raise ValueError(f"No value supplied for argument {key!r}")

    return value


def test_module(client: Connect) -> str:
    """Tests Axonius API Client connectivity."""
    client.start()
    return "ok"


def parse_kv(key: str, value: Any) -> Any:
    """Parse time stamp into required format."""
    for word in FIELDS_TIME:
        if word in key:
            try:
                return dt_parse(value).isoformat()
            except Exception:
                return value
    return value


def parse_key(key: str) -> str:
    """Parse fields into required format."""
    if key.startswith("specific_data.data."):
        key = strip_left(obj=key, fix="specific_data.data.")
        key = f"aggregated_{key}"
    if key.startswith("adapters_data."):
        key = strip_left(obj=key, fix="adapters_data.")
    key = key.replace(".", "_")
    return key


def parse_asset(asset: dict) -> dict:
    """Initiate field format correction on assets."""
    return {parse_key(key=k): parse_kv(key=k, value=v) for k, v in asset.items() if k not in SKIPS}


def get_saved_queries(client: Connect, args: dict) -> CommandResults:  # noqa: F821, F405
    """Get assets with their defined fields returned by a saved query."""
    api_obj = client.devices if args["type"] == "devices" else client.users
    saved_queries = api_obj.saved_query.get()
    return parse_assets(
        assets=saved_queries,
        api_obj=api_obj,
        outputs_key_field="",
        extension="saved_queries",
        exclude_raw=True,
    )


def make_api_call(
    endpoint: str,
    payload: dict = None,
    method: str = "POST",
    query_params: dict = None,
) -> requests.Response | None:
    """Make an authenticated HTTP API call to the Axonius instance.

    Args:
        endpoint: API endpoint path (appended to the base URL).
        payload: JSON body for POST/DELETE requests.
        method: HTTP method — GET, POST, or DELETE (default: POST).
        query_params: URL query parameters for GET requests.

    Returns:
        The HTTP response, or None if no URL is configured.
    """
    params: dict = demisto.params()
    url: str | None = params.get("ax_url")
    key: str = params.get("credentials", {}).get("identifier")
    secret: str = params.get("credentials", {}).get("password")
    certverify: bool = not params.get("insecure", False)

    if not url:
        return None

    url = url + "/" if url[-1] != "/" else url
    url = url + endpoint

    headers: dict = {
        "accept": "application/json",
        "api-key": key,
        "api-secret": secret,
        "content-type": "application/json",
    }

    try:
        if method == "GET":
            return requests.get(url, headers=headers, params=query_params, verify=certverify, timeout=REQUEST_TIMEOUT)
        elif method == "DELETE":
            return requests.delete(url, json=payload, headers=headers, verify=certverify, timeout=REQUEST_TIMEOUT)
        else:
            return requests.post(url, json=payload, headers=headers, verify=certverify, timeout=REQUEST_TIMEOUT)
    except requests.exceptions.Timeout as exc:
        raise DemistoException(f"Request to Axonius timed out: {exc}") from exc
    except requests.exceptions.RequestException as exc:
        raise DemistoException(f"Error connecting to Axonius: {exc}") from exc


def _handle_api_response(response: Optional[requests.Response], endpoint: str) -> dict:
    """Validate an API response and return parsed JSON.

    Raises:
        DemistoException: on missing URL, non-2xx status, or invalid JSON.
    """
    if response is None:
        raise DemistoException("No URL configured for the Axonius instance.")

    if not response.ok:
        raise DemistoException(f"API call to '{endpoint}' failed with HTTP {response.status_code}: {response.text[:1000]}")

    if not response.content:
        return {}

    try:
        return response.json()
    except ValueError as exc:
        raise DemistoException(f"Failed to parse response from '{endpoint}' as JSON: {exc}") from exc


def add_note(client: Connect, args: dict) -> CommandResults:
    """Add notes to assets."""
    note: str = args["note"]
    asset_type: str = args["type"]
    internal_axon_id_arr: list = args["ids"]
    success_count: int = 0
    if isinstance(internal_axon_id_arr, str):
        internal_axon_id_arr = argToList(internal_axon_id_arr, separator=",")

    payload: dict = {
        "meta": None,
        "data": {
            "attributes": {
                "note": note,
            },
            "type": "notes_schema",
        },
    }

    for id in internal_axon_id_arr:
        response = make_api_call(endpoint=f"api/{asset_type}/{id}/notes", payload=payload)
        if response and response.status_code == 200:
            success_count += 1

    readable_output = f"{success_count} {asset_type}(s) updated."
    return CommandResults(
        outputs_prefix="Axonius.asset.updates",
        readable_output=readable_output,
        outputs=success_count,
        raw_response=success_count,
    )


def get_tags(client: Connect, args: dict) -> CommandResults:  # noqa: F821, F405
    """Get assets with their defined fields returned by a saved query."""
    api_obj = client.devices if args["type"] == "devices" else client.users
    tags = api_obj.labels.get()
    return CommandResults(
        outputs_prefix=f"Axonius.tags.{args['type']}",
        readable_output=",".join(tags),
        outputs=tags,
        raw_response=tags,
    )


def update_tags(client: Connect, args: dict, method_name: str) -> CommandResults:  # noqa: F821, F405
    tag_name: str = args["tag_name"]
    internal_axon_id_arr: list = args["ids"]
    if isinstance(internal_axon_id_arr, str):
        internal_axon_id_arr = argToList(internal_axon_id_arr, separator=",")
    api_obj = client.devices if args["type"] == "devices" else client.users
    api_name = api_obj.__class__.__name__

    if method_name == "add":
        res = api_obj.labels.add(rows=internal_axon_id_arr, labels=[tag_name])
    else:
        res = api_obj.labels.remove(rows=internal_axon_id_arr, labels=[tag_name])

    # res is count of rows included in the output, regardless of success.
    readable_output = f"{res} {api_name}(s) updated."
    return CommandResults(
        outputs_prefix=f"Axonius.asset.updates.{args['type']}",
        readable_output=readable_output,
        outputs=res,
        raw_response=res,
    )


def get_by_sq(api_obj: Union[Users, Devices], args: dict) -> CommandResults:  # noqa: F821, F405
    """Get assets with their defined fields returned by a saved query."""
    name: str = args["saved_query_name"]
    fields: List[str] = get_csv_arg(key="fields", required=False)
    max_rows: int = get_int_arg(key="max_results", required=False, default=MAX_ROWS)
    assets = api_obj.get_by_saved_query(name=name, max_rows=max_rows, fields=fields)
    return parse_assets(assets=assets, api_obj=api_obj)


def get_by_value(
    api_obj: Union[Users, Devices],
    args: dict,
    method_name: str,
) -> CommandResults:  # noqa: F821, F405
    """Get assets by a value using a api_obj.get_by_{method_name}."""
    api_name = api_obj.__class__.__name__
    value: str = args["value"]
    fields: List[str] = get_csv_arg(key="fields", required=False)
    max_rows: int = get_int_arg(key="max_results", required=False, default=MAX_ROWS)

    api_method_name = f"get_by_{method_name}"
    if not hasattr(api_obj, api_method_name):
        valid = []

        for x in dir(api_obj):
            if not x.startswith("get_by_") or x.endswith("s"):
                continue

            valid.append(x.replace("get_by_", ""))

        valid = ", ".join(valid)
        raise Exception(f"Invalid get by {method_name} for {api_name}, valid: {valid}")

    method = getattr(api_obj, api_method_name)
    assets = method(value=value, max_rows=max_rows, fields=fields)
    return parse_assets(assets=assets, api_obj=api_obj)


def get(
    api_obj: Union[Users, Devices],
    args: dict,
) -> CommandResults:
    """Get assets by a query using a api_obj.get."""
    query: str = args["query"]
    fields: List[str] = get_csv_arg(key="fields", required=False)
    max_rows: int = get_int_arg(key="max_results", required=False, default=MAX_ROWS)
    assets = api_obj.get(query=query, max_rows=max_rows, fields=fields)
    return parse_assets(assets=assets, api_obj=api_obj)


def parse_assets(
    assets: List[dict],
    api_obj: Union[Users, Devices],
    outputs_key_field=AXONIUS_ID,
    extension="",
    exclude_raw=False,
) -> CommandResults:  # noqa: F821, F405
    """Parse assets into CommandResults."""
    api_name = api_obj.__class__.__name__
    aql = api_obj.LAST_GET.get("filter")
    results = [parse_asset(asset=asset) for asset in assets]

    readable_output: Optional[str] = None
    outputs: Union[List[dict], dict] = results

    if not results:
        readable_output = f"No {api_name} assets found using AQL: {aql}"

    if len(results) == 1:
        outputs = results[0]

    outputs_prefix = f"Axonius.{api_name}"
    if extension:
        outputs_prefix += f".{extension}"
    raw_response = None if exclude_raw else assets

    return CommandResults(
        outputs_prefix=outputs_prefix,
        outputs_key_field=outputs_key_field,
        readable_output=readable_output,
        outputs=outputs,
        raw_response=raw_response,
    )  # noqa: F821, F405


# ---------------------------------------------------------------------------
# v2 API helpers
# ---------------------------------------------------------------------------


def _build_v2_page(limit: int, cursor: Optional[str] = None) -> dict:
    """Build a v2 API page object."""
    page: dict = {"limit": limit}
    if cursor:
        page["cursor"] = cursor
    return page


# ---------------------------------------------------------------------------
# axonius-get-assets
# ---------------------------------------------------------------------------


def get_assets(args: dict) -> CommandResults:
    """Fetch assets of any type via POST /api/v2/assets/{asset_type}.

    Supports all asset types including alert_findings and
    vulnerability_instances. Returns a single page of results together with a
    cursor (next_token) for subsequent calls.

    Note on large payloads: when a response exceeds ~10 MB, XSOAR automatically
    stores the data as a downloadable file instead of writing it to the context.
    Use the page_size argument to keep responses within manageable bounds.
    """
    asset_type: str = args.get("asset_type", "devices")
    query: Optional[str] = args.get("query") or None
    fields: List[str] = argToList(args.get("fields"))
    fields_to_exclude: List[str] = argToList(args.get("fields_to_exclude"))
    page_size: int = arg_to_number(args.get("page_size")) or V2_PAGE_SIZE_DEFAULT
    limit: int = arg_to_number(args.get("limit")) or page_size
    next_token: Optional[str] = args.get("next_token") or None
    include_metadata: bool = argToBoolean(args.get("include_metadata", False))
    include_details: bool = argToBoolean(args.get("include_details", False))
    use_cache_entry: bool = argToBoolean(args.get("use_cache_entry", False))

    payload: dict = {"page": _build_v2_page(limit=page_size, cursor=next_token)}
    if query:
        payload["query"] = query
    if fields:
        payload["fields"] = fields
    if fields_to_exclude:
        payload["fields_to_exclude"] = fields_to_exclude
    if include_metadata:
        payload["include_metadata"] = True
    if include_details:
        payload["include_details"] = True
    if use_cache_entry:
        payload["use_cache_entry"] = True

    endpoint = f"api/v2/assets/{asset_type}"
    response = make_api_call(endpoint=endpoint, payload=payload, method="POST")
    data = _handle_api_response(response=response, endpoint=endpoint)

    assets: List[dict] = data.get("assets") or []
    assets = assets[:limit]
    meta: dict = data.get("meta") or {}
    next_cursor: Optional[str] = meta.get("next_page")
    page_meta: dict = meta.get("page") or {}
    total_count: Optional[int] = page_meta.get("totalResources")

    if not assets:
        readable_output = f"No {asset_type} assets found."
    else:
        readable_output = tableToMarkdown(
            f"Axonius Assets — {asset_type} ({len(assets)} returned)",
            assets,
            removeNull=True,
        )

    outputs: dict = {
        "asset_type": asset_type,
        "assets": assets,
        "count": len(assets),
    }
    if total_count is not None:
        outputs["total_count"] = total_count
    if next_cursor:
        outputs["next_token"] = next_cursor

    return CommandResults(
        outputs_prefix="Axonius.Assets",
        outputs_key_field="asset_type",
        readable_output=readable_output,
        outputs=outputs,
        raw_response=data,
    )


# ---------------------------------------------------------------------------
# axonius-get-asset-types
# ---------------------------------------------------------------------------


def get_asset_types() -> CommandResults:
    """Return available asset types via GET /api/v2/assets/asset_types."""
    endpoint = "api/v2/assets/asset_types"
    response = make_api_call(endpoint=endpoint, method="GET")
    data = _handle_api_response(response=response, endpoint=endpoint)

    raw_types: list = data.get("asset_types") or []
    asset_types: List[dict] = [t if isinstance(t, dict) else {"asset_type": t} for t in raw_types]

    readable_output = tableToMarkdown("Axonius Asset Types", asset_types, removeNull=True)

    return CommandResults(
        outputs_prefix="Axonius.AssetTypes",
        outputs_key_field="asset_type",
        readable_output=readable_output,
        outputs=asset_types,
        raw_response=data,
    )


# ---------------------------------------------------------------------------
# Custom Data Management
# ---------------------------------------------------------------------------


def get_custom_data(args: dict) -> CommandResults:
    """List custom data entries via GET /api/v2/custom_data_management."""
    page: int = arg_to_number(args.get("page")) or 1
    page_size: int = arg_to_number(args.get("page_size")) or 50
    limit: int = arg_to_number(args.get("limit")) or page_size
    offset: int = (page - 1) * page_size
    endpoint = "api/v2/custom_data_management"
    response = make_api_call(
        endpoint=endpoint,
        method="GET",
        query_params={"limit": min(page_size, limit), "offset": offset},
    )
    data = _handle_api_response(response=response, endpoint=endpoint)

    entries: list = (data.get("custom_fields") or [])[:limit]

    readable_output = tableToMarkdown(
        "Axonius Custom Data",
        entries,
        removeNull=True,
    )

    return CommandResults(
        outputs_prefix="Axonius.CustomData",
        outputs_key_field="id",
        readable_output=readable_output,
        outputs=entries,
        raw_response=data,
    )


def create_custom_data(args: dict) -> CommandResults:
    """Create a custom data entry via POST /api/v2/custom_data_management."""
    payload_str: Optional[str] = args.get("payload")
    if not payload_str:
        raise DemistoException("The 'payload' argument is required for axonius-create-custom-data.")
    try:
        payload: dict = json.loads(payload_str)
    except (ValueError, TypeError) as exc:
        raise DemistoException(f"Invalid JSON in 'payload' argument: {exc}") from exc

    endpoint = "api/v2/custom_data_management"
    response = make_api_call(endpoint=endpoint, payload=payload, method="POST")
    data = _handle_api_response(response=response, endpoint=endpoint)

    return CommandResults(
        outputs_prefix="Axonius.CustomData",
        readable_output="Custom data entry created successfully.",
        outputs=data,
        raw_response=data,
    )


def delete_custom_data(args: dict) -> CommandResults:
    """Delete a custom data entry via DELETE /api/v2/custom_data_management/{id}."""
    entry_id: str = args.get("id", "")
    if not entry_id:
        raise DemistoException("The 'id' argument is required for axonius-delete-custom-data.")

    endpoint = f"api/v2/custom_data_management/{entry_id}"
    response = make_api_call(endpoint=endpoint, method="DELETE")
    _handle_api_response(response=response, endpoint=endpoint)

    return CommandResults(
        outputs_prefix="Axonius.CustomData",
        readable_output=f"Custom data entry '{entry_id}' deleted successfully.",
        outputs={"id": entry_id, "deleted": True},
    )


# ---------------------------------------------------------------------------
# Enforcements
# ---------------------------------------------------------------------------


def get_enforcements(args: dict) -> CommandResults:
    """List enforcements via GET /api/v2/enforcements."""
    page: int = arg_to_number(args.get("page")) or 1
    page_size: int = arg_to_number(args.get("page_size")) or 50
    limit: int = arg_to_number(args.get("limit")) or page_size
    offset: int = (page - 1) * page_size
    endpoint = "api/v2/enforcements"
    response = make_api_call(
        endpoint=endpoint,
        method="GET",
        query_params={"limit": min(page_size, limit), "offset": offset},
    )
    data = _handle_api_response(response=response, endpoint=endpoint)

    enforcements: list = (data.get("enforcements") or [])[:limit]

    readable_output = tableToMarkdown(
        "Axonius Enforcements",
        enforcements,
        removeNull=True,
    )

    return CommandResults(
        outputs_prefix="Axonius.Enforcements",
        outputs_key_field="uuid",
        readable_output=readable_output,
        outputs=enforcements,
        raw_response=data,
    )


def run_enforcement(args: dict) -> CommandResults:
    """Trigger an enforcement run via POST /api/v2/enforcements/{id}/run."""
    enforcement_id: str = args.get("enforcement_id", "")
    if not enforcement_id:
        raise DemistoException("The 'enforcement_id' argument is required for axonius-run-enforcement.")

    endpoint = f"api/v2/enforcements/{enforcement_id}/run"
    response = make_api_call(endpoint=endpoint, method="POST")
    data = _handle_api_response(response=response, endpoint=endpoint)

    return CommandResults(
        outputs_prefix="Axonius.Enforcements",
        readable_output=f"Enforcement '{enforcement_id}' triggered successfully.",
        outputs={"enforcement_id": enforcement_id, "triggered": True, "response": data},
        raw_response=data,
    )


# ---------------------------------------------------------------------------
# Queries
# ---------------------------------------------------------------------------


def get_queries(args: dict) -> CommandResults:
    """List saved queries via GET /api/v2/queries."""
    asset_type: Optional[str] = args.get("asset_type") or None
    page: int = arg_to_number(args.get("page")) or 1
    page_size: int = arg_to_number(args.get("page_size")) or 50
    limit: int = arg_to_number(args.get("limit")) or page_size
    offset: int = (page - 1) * page_size
    endpoint = "api/v2/queries"
    qp: dict = {"limit": min(page_size, limit), "offset": offset}
    if asset_type:
        qp["asset_type"] = asset_type
    response = make_api_call(endpoint=endpoint, method="GET", query_params=qp)
    data = _handle_api_response(response=response, endpoint=endpoint)

    queries: list = (data.get("queries") or [])[:limit]

    readable_output = tableToMarkdown(
        "Axonius Queries",
        queries,
        removeNull=True,
    )

    return CommandResults(
        outputs_prefix="Axonius.Queries",
        outputs_key_field="uuid",
        readable_output=readable_output,
        outputs=queries,
        raw_response=data,
    )


def create_query(args: dict) -> CommandResults:
    """Create a saved query via POST /api/v2/queries."""
    name: str = args.get("name", "")
    query: str = args.get("query", "")
    asset_type: str = args.get("asset_type", "devices")
    description: Optional[str] = args.get("description") or None

    if not name:
        raise DemistoException("The 'name' argument is required for axonius-create-query.")
    if not query:
        raise DemistoException("The 'query' argument is required for axonius-create-query.")

    payload: dict = {"name": name, "query": query, "asset_type": asset_type}
    if description:
        payload["description"] = description

    endpoint = "api/v2/queries"
    response = make_api_call(endpoint=endpoint, payload=payload, method="POST")
    data = _handle_api_response(response=response, endpoint=endpoint)

    return CommandResults(
        outputs_prefix="Axonius.Queries",
        outputs_key_field="uuid",
        readable_output=f"Query '{name}' created successfully.",
        outputs=data,
        raw_response=data,
    )


def delete_query(args: dict) -> CommandResults:
    """Delete a saved query via DELETE /api/v2/queries/{query_id}."""
    query_id: str = args.get("query_id", "")
    if not query_id:
        raise DemistoException("The 'query_id' argument is required for axonius-delete-query.")

    endpoint = f"api/v2/queries/{query_id}"
    response = make_api_call(endpoint=endpoint, method="DELETE")
    _handle_api_response(response=response, endpoint=endpoint)

    return CommandResults(
        outputs_prefix="Axonius.Queries",
        readable_output=f"Query '{query_id}' deleted successfully.",
        outputs={"query_id": query_id, "deleted": True},
    )


# ---------------------------------------------------------------------------
# Grouped Vulnerabilities
# ---------------------------------------------------------------------------


def _fetch_all_pages(asset_type: str, query: Optional[str] = None, page_size: int = V2_PAGE_SIZE_ALL_PAGES) -> List[dict]:
    """Fetch all pages of an asset type from the v2 API.

    Args:
        asset_type: Axonius asset type (e.g. 'vulnerability_instances').
        query: Optional AQL filter string.
        page_size: Number of records per page request.

    Returns:
        Flat list of all asset records across all pages.
    """
    all_assets: List[dict] = []
    cursor: Optional[str] = None
    endpoint = f"api/v2/assets/{asset_type}"

    for _ in range(MAX_PAGES):
        payload: dict = {"page": _build_v2_page(limit=page_size, cursor=cursor)}
        if query:
            payload["query"] = query

        response = make_api_call(endpoint=endpoint, payload=payload, method="POST")
        data = _handle_api_response(response=response, endpoint=endpoint)

        page_assets: List[dict] = data.get("assets") or []
        all_assets.extend(page_assets)

        meta: dict = data.get("meta") or {}
        cursor = meta.get("next_page")

        if not cursor or not page_assets:
            break
    else:
        if cursor:
            demisto.debug(
                f"_fetch_all_pages: reached MAX_PAGES ({MAX_PAGES}) for '{asset_type}'; "
                f"stopping pagination early with {len(all_assets)} records collected."
            )

    return all_assets


def _flatten_instance(instance: dict) -> dict:
    """Flatten single-element lists in a vulnerability instance dict."""
    flattened: dict = {}
    for key, value in instance.items():
        if isinstance(value, list) and len(value) == 1:
            flattened[key] = value[0]
        else:
            flattened[key] = value
    return flattened


def _safe_float(value: Any) -> Optional[float]:
    """Convert value to float, returning None on failure."""
    try:
        return float(value)
    except (TypeError, ValueError):
        return None


def get_grouped_vulnerabilities(args: dict) -> CommandResults:
    """Fetch all vulnerability instances and group them by CVE ID.

    Fetches all pages of /v2/assets/vulnerability_instances, flattens each
    instance, groups by cve_id, counts affected hosts, averages CVSS scores,
    and returns the Top N CVEs sorted by affected_hosts_count descending.
    """
    query: Optional[str] = args.get("query") or None
    team_name: Optional[str] = args.get("team_name") or None
    urgent: Optional[str] = args.get("urgent") or None
    top_n: int = arg_to_number(args.get("top_n")) or 10
    page_size: int = arg_to_number(args.get("page_size")) or V2_PAGE_SIZE_ALL_PAGES

    # Build composite query filter
    query_parts: List[str] = []
    if query:
        query_parts.append(query)
    if team_name:
        query_parts.append(f'(specific_data.data.team_name == "{team_name}")')
    if urgent is not None:
        urgent_bool = "true" if argToBoolean(urgent) else "false"
        query_parts.append(f"(specific_data.data.urgent == {urgent_bool})")
    final_query = " and ".join(query_parts) if query_parts else None

    instances = _fetch_all_pages(asset_type="vulnerability_instances", query=final_query, page_size=page_size)

    if not instances:
        return CommandResults(
            outputs_prefix="Axonius.GroupedVulnerabilities",
            outputs_key_field="cve_id",
            readable_output="No vulnerability instances found.",
            outputs=[],
        )

    # Group by CVE ID
    grouped: dict = {}
    for raw in instances:
        inst = _flatten_instance(raw)
        cve_id = inst.get("cve_id") or inst.get("specific_data.data.cve_id")
        if not cve_id:
            continue

        if cve_id not in grouped:
            grouped[cve_id] = {"affected_hosts_count": 0, "cvss_scores": []}

        grouped[cve_id]["affected_hosts_count"] += 1
        cvss = _safe_float(inst.get("cvss_score") or inst.get("specific_data.data.cvss_score"))
        if cvss is not None:
            grouped[cve_id]["cvss_scores"].append(cvss)

    # Build sorted output
    results: List[dict] = []
    for cve_id, group_data in grouped.items():
        scores = group_data["cvss_scores"]
        avg_cvss = round(sum(scores) / len(scores), 2) if scores else None
        results.append(
            {
                "cve_id": cve_id,
                "affected_hosts_count": group_data["affected_hosts_count"],
                "average_cvss_score": avg_cvss,
            }
        )

    results.sort(key=lambda x: x["affected_hosts_count"], reverse=True)
    top_results = results[:top_n]

    readable_output = tableToMarkdown(
        f"Top {top_n} CVEs by Affected Hosts",
        top_results,
        removeNull=True,
    )

    return CommandResults(
        outputs_prefix="Axonius.GroupedVulnerabilities",
        outputs_key_field="cve_id",
        readable_output=readable_output,
        outputs=top_results,
        raw_response=top_results,
    )


def run_command(client: Connect, args: dict, command: str):
    results: Union[CommandResults, str, None] = None

    if command == "test-module":
        results = test_module(client=client)

    elif command == "axonius-get-devices-by-savedquery":
        results = get_by_sq(api_obj=client.devices, args=args)

    elif command == "axonius-get-users-by-savedquery":
        results = get_by_sq(api_obj=client.users, args=args)

    elif command == "axonius-get-devices-by-aql":
        results = get(api_obj=client.devices, args=args)

    elif command == "axonius-get-users-by-aql":
        results = get(api_obj=client.users, args=args)

    elif command == "axonius-get-users-by-mail":
        results = get_by_value(api_obj=client.users, args=args, method_name="mail")

    elif command == "axonius-get-users-by-mail-regex":
        results = get_by_value(api_obj=client.users, args=args, method_name="mail_regex")

    elif command == "axonius-get-users-by-username":
        results = get_by_value(api_obj=client.users, args=args, method_name="username")

    elif command == "axonius-get-users-by-username-regex":
        results = get_by_value(api_obj=client.users, args=args, method_name="username_regex")

    elif command == "axonius-get-devices-by-hostname":
        results = get_by_value(api_obj=client.devices, args=args, method_name="hostname")

    elif command == "axonius-get-devices-by-hostname-regex":
        results = get_by_value(api_obj=client.devices, args=args, method_name="hostname_regex")

    elif command == "axonius-get-devices-by-ip":
        results = get_by_value(api_obj=client.devices, args=args, method_name="ip")

    elif command == "axonius-get-devices-by-ip-regex":
        results = get_by_value(api_obj=client.devices, args=args, method_name="ip_regex")

    elif command == "axonius-get-devices-by-mac":
        results = get_by_value(api_obj=client.devices, args=args, method_name="mac")

    elif command == "axonius-get-devices-by-mac-regex":
        results = get_by_value(api_obj=client.devices, args=args, method_name="mac_regex")

    elif command == "axonius-get-saved-queries":
        results = get_saved_queries(client=client, args=args)

    elif command == "axonius-get-tags":
        results = get_tags(client=client, args=args)

    elif command == "axonius-add-note":
        results = add_note(client=client, args=args)

    elif command == "axonius-add-tag":
        results = update_tags(client=client, args=args, method_name="add")

    elif command == "axonius-remove-tag":
        results = update_tags(client=client, args=args, method_name="remove")

    # v2 API commands
    elif command == "axonius-get-assets":
        results = get_assets(args=args)

    elif command == "axonius-get-asset-types":
        results = get_asset_types()

    elif command == "axonius-get-custom-data":
        results = get_custom_data(args=args)

    elif command == "axonius-create-custom-data":
        results = create_custom_data(args=args)

    elif command == "axonius-delete-custom-data":
        results = delete_custom_data(args=args)

    elif command == "axonius-get-enforcements":
        results = get_enforcements(args=args)

    elif command == "axonius-run-enforcement":
        results = run_enforcement(args=args)

    elif command == "axonius-get-queries":
        results = get_queries(args=args)

    elif command == "axonius-create-query":
        results = create_query(args=args)

    elif command == "axonius-delete-query":
        results = delete_query(args=args)

    elif command == "axonius-get-grouped-vulnerabilities":
        results = get_grouped_vulnerabilities(args=args)

    return results


def main():
    """PARSE AND VALIDATE INTEGRATION PARAMS."""
    params: dict = demisto.params()
    command: str = demisto.command()

    url: str = params["ax_url"]
    key: str = params.get("credentials", {}).get("identifier")
    secret: str = params.get("credentials", {}).get("password")
    certverify: bool = not params.get("insecure", False)

    proxies: dict = handle_proxy()  # noqa: F821, F405
    demisto.debug(f"Attempting to connect via proxy with: {proxies}")

    demisto.debug(f"Command being called is {command}")
    args: dict = demisto.args()

    try:
        client = Connect(
            url=url,
            key=key,
            secret=secret,
            certverify=certverify,
            certwarn=False,
            proxy=proxies.get("https") or proxies.get("http"),
        )
        return_results(run_command(client, args, command))  # noqa: F821, F405

    except Exception as exc:
        demisto.error(traceback.format_exc())
        msg: List[str] = [f"Failed to execute {command} command", "Error:", str(exc)]
        return_error("\n".join(msg))  # noqa: F821, F405


if __name__ in ("__main__", "__builtin__", "builtins"):
    main()