Skip to content

Getting started

Overview

This guide takes you through the steps you need to follow to start using the new Live Discover API in Sophos Fusion. Live Discover allows you to run EDR queries on protected endpoints that are online even when there is no local user signed in. Using this API, you can perform all the same tasks as you can from the Live Discover section in Threat Analysis Center of Sophos Fusion Admin.

At the end of this guide, you will know how to run a Live Discover query on an endpoint and retrieve the results.

[!NOTE] Moving to GraphQL?

Sophos Fusion also exposes this workflow through the Live Endpoint Search GraphQL API. If you have an existing integration against this REST API, see Migrating from the Live Discover REST API.

Pre-requisites

You must go through one of our Getting Started guides. This helps you set up your API credentials.

  • Partners use this guide. You must be enrolled in the Sophos Partner Program to call our Partner APIs.
  • Enterprise customers use this guide. You should have Enterprise Admin enabled for one or more tenants in your organization.
  • If neither profile fits you, use this guide for tenants.

You can make API calls in the next few sections using cURL. Follow the instructions on cURL's website to install this tool.

Note: Some command lines in the examples below have been split over multiple lines for legibility. Remove the \ before running the commands. Also replace curl with curl.exe on Windows.

The instructions in this guide assume you have authenticated using API credentials and obtained a valid JWT token (<jwt>). You should also have the UUID of a tenant that you can pass to each API call (<tenant-id>), as well as the API host for the data region where the tenant's data is located (<data-region>).

You must also have at least one endpoint online, protected with the Sophos Endpoint Protection agent, and managed by Sophos Fusion Admin. In the rest of guide, replace "ep01" with the hostname of your endpoint.

Live Discover operations

First, some terminology:

  • Query: A bit of osquery SQL code you can run remotely from Sophos Fusion on one or more endpoints simultaneously.
  • Category: Related queries are grouped together in categories.
  • Query run: An instance of a query executed against a selection of endpoints.
  • Canned query: A query provided by Sophos.
  • Custom query: A query created by you.
  • Saved query: A query named and saved for future reuse. Saved queries may be canned or custom.
  • Ad hoc query: A query that doesn't need to be saved for reuse.

In the next few sections, we will show you how to:

  1. Get a list of Live Discover saved queries
  2. List query categories
  3. Run a saved query by ID
  4. Check the status of the query run
  5. Fetch query run results
  6. Run an ad hoc query
  7. List query runs

1. List saved queries

Call the following API to list all the queries that you can execute:

curl -XGET -H "Authorization: Bearer <jwt>" \
           -H "X-Tenant-ID: <tenant-id>" \
                <data-region>/live-discover/v1/queries

A successful response has status code 200 and looks something like this:

{
    "items": [
        {
            "id": "acb50710-5d0f-475a-b521-81fc4193e2d5",
            "name": "Windows security products",
            "code": "windows-security-products",
            "description": "Lists the registered Windows security products",
            "template": "SELECT\n    type,\n    name,\n    state,\n    state_timestamp,\n    remediation_path,\n    signatures_up_to_date\nFROM windows_security_products",
            "variables": [],
            "supportedOSes": [
                "windowsComputer",
                "windowsServer"
            ],
            "categories": [
                {
                    "id": "35c14ebb-cafc-44dd-a613-1e35f8c79792"
                },
                {
                    "id": "a14886f4-c30b-4769-b390-944f146e5a6c"
                }
            ],
            "performance": {
                "score": "notEvaluated",
                "averageExecutionTimeInMillis": -1,
                "averageDataTransferredInBytes": -1
            },
            "type": "canned",
            "createdAt": "2020-07-17T09:33:03.754Z"
        },

        // Other query objects
    ],

    "pages": {
        "current": 1,
        "size": 50,
        "total": 1,
        "items": 12,
        "maxSize": 250
    }
}

Note that each query has a unique ID, associated SQL code, and belongs to one or more query categories. The SQL code may be template with placeholders for variable substitution, although the query in the example above isn't a template. Some queries have useful performance metrics based on historical query runs.

Next, call the following API to get a single query by its ID:

curl -XGET -H "Authorization: Bearer <jwt>" \
           -H "X-Tenant-ID: <tenant-id>" \
                <data-region>/live-discover/v1/queries/acb50710-5d0f-475a-b521-81fc4193e2d5

A successful response returns all the details for a single query.

2. List query categories

Call the following API to list all the query categories:

curl -XGET -H "Authorization: Bearer <jwt>" \
           -H "X-Tenant-ID: <tenant-id>" \
                <data-region>/live-discover/v1/queries/categories

A successful response looks like this:

{
    "items": [
        {
            "id": "a14886f4-c30b-4769-b390-944f146e5a6c",
            "name": "Compliance",
            "code": "compliance",
            "description": "Compliance with security standards",
            "icon": "compliance",
            "type": "canned",
            "queryCount": 12,
            "createdAt": "2020-03-05T21:07:05.105647Z"
        },
        {
            "id": "35c14ebb-cafc-44dd-a613-1e35f8c79792",
            "name": "Device",
            "code": "laptop",
            "description": "Device OS, patches, services and more",
            "icon": "laptop",
            "type": "canned",
            "queryCount": 27,
            "createdAt": "2020-03-05T21:07:05.105647Z"
        },

        // Other categories
    ]
}

Note that this response is not paginated. Each query category has a unique ID and name. Call the following API to get a single category query by its ID:

curl -XGET -H "Authorization: Bearer <jwt>" \
           -H "X-Tenant-ID: <tenant-id>" \
                <data-region>/live-discover/v1/queries/categories/a14886f4-c30b-4769-b390-944f146e5a6c

A successful response returns the Compliance query category.

3. Run a canned query

Create a file named "request.json" and add the following content to it:

{
    "savedQuery": {
          "queryId": "acb50710-5d0f-475a-b521-81fc4193e2d5"
    },
    "matchEndpoints": {
       "filters": [
           {
               "hostnameContains": "ep01"
           }
       ]
    }
}

The "queryId" is that of the "Windows security products" canned query from the examples above.

Then call this API to kick off that query:

curl -XPOST -H 'Content-Type: application/json' \
            -H 'Authorization: Bearer <jwt>' \
            -H 'X-Tenant-ID: <tenant-id>' \
            --data request.json  \
                   <data-region>/live-discover/v1/queries/runs

This runs the query to list all Windows security products on all endpoints with "ep01" in their name. Note: The hostnameContains filter is not an exact match.

A successful response looks like this:

{
    "id": "f032fffb-472f-4aeb-8289-e993453a3320",
    "query": {
        "id": "acb50710-5d0f-475a-b521-81fc4193e2d5",
        "name": "Windows security products",
        "code": "windows-security-products"
    },
    "name": "Windows security products",
    "template": "SELECT\n    type,\n    name,\n    state,\n    state_timestamp,\n    remediation_path,\n    signatures_up_to_date\nFROM windows_security_products",
    "variables": [],
    "status": "started",
    "result": "notAvailable",
    "createdAt": "2020-12-12T22:02:51.966Z",
    "createdBy": {
        "id": "50b5bdad-ca56-4037-96ee-5b3e7e585b3d",
        "type": "service",
        "accountType": "tenant"
    },
    "maxDurationInSeconds": 600,
    "timeRemainingInSeconds": 600,
    "performance": {
        "score": "notEvaluated",
        "dataTransferredInBytes": {
            "total": 0,
            "min": 0,
            "max": 0,
            "median": 0,
            "average": 0
        },
        "executionTimeInMillis": {
            "total": 0,
            "min": 0,
            "max": 0,
            "median": 0,
            "average": 0
        }
    },
    "endpointIds": [
        "13e9df04-5729-47e7-aa77-ad4c3a6576ac"
    ],
    "matchEndpoints": {
        "all": false,
        "filters": [
            {
                "hostnameContains": "ep01"
            }
        ]
    },
    "endpointCounts": {
        "total": 1,
        "types": {
            "server": 0,
            "computer": 1,
            "securityVm": 0
        },
        "platforms": {
            "linux": 0,
            "windows": 1,
            "macOS": 0
        },
        "statuses": {
            "pending": {
                "total": 0,
                "offline": 0,
                "online": 0
            },
            "started": {
                "total": 1,
                "withData": 0,
                "withoutData": 1
            },
            "finished": {
                "succeeded": {
                    "total": 0,
                    "withData": 0,
                    "withoutData": 0
                },
                "failed": {
                    "total": 0,
                    "withData": 0,
                    "withoutData": 0
                },
                "timedOut": {
                    "total": 0,
                    "withData": 0,
                    "withoutData": 0
                }
            }
        }
    }
}

This object is a query run. Each query run has a unique ID (id), contain the query SQL template (template), any variable substitutions in the template (variables), the filters used to select endpoints (matchEndpoints), the query performance metrics (performance) based on historical data, and the count of endpoints by the status of the query on the endpoint(endpointCounts).

You will need the ID of the query run ("f032fffb-472f-4aeb-8289-e993453a3320" in the example above) for the subsequent sections of this guide; pass this in place of <run-id> below.

4. Check the status of the query run

Call this API

curl -XGET -H 'Content-Type: application/json' \
           -H 'Authorization: Bearer <jwt>' \
           -H 'X-Tenant-ID: <tenant-id>' \
                   <data-region>/live-discover/v1/queries/runs/<run-id>

This should return the same response as in Step 3. Look at the status field to see if that changes from pending to started to finished.

You can monitor the status of the query run efficiently by passing the fields query parameter to the API call above:

curl -XGET -H 'Content-Type: application/json' \
           -H 'Authorization: Bearer <jwt>' \
           -H 'X-Tenant-ID: <tenant-id>' \
                   <data-region>/live-discover/v1/queries/runs/<run-id>?fields=status

This returns just the ID of the query run and its status.

{
    "id": "f032fffb-472f-4aeb-8289-e993453a3320",
    "status": "finished"
}

Call this as many times as needed to wait until the query run has completed.

5. Fetch query run results

Once the query run has finished execution, it is time to fetch the results. Make this API call:

curl -XGET -H 'Content-Type: application/json' \
           -H 'Authorization: Bearer <jwt>' \
           -H 'X-Tenant-ID: <tenant-id>' \
                   <data-region>/live-discover/v1/queries/runs/<run-id>/results?pageTotal=true&pageSize=1000

The response contains the first page of the results.

{
    "items": [
        {
            "endpointId": "90eceb1f-590e-458b-a5df-e3ecb0c9d827",
            "hostname": "ep01",
            "type": "Firewall",
            "name": "Windows Firewall",
            "state": "Off",
            "state_timestamp": "NULL",
            "remediation_path": "%windir%\\system32\\firewall.cpl",
            "signatures_up_to_date": 1
            // ... Other fields in the result row
        },
        {
            "endpointId": "90eceb1f-590e-458b-a5df-e3ecb0c9d827",
            "hostname": "ep01",
            "type": "Antivirus",
            "name": "Sophos Anti-Virus",
            "state": "On",
            "state_timestamp": "Wed, 11 Dec 2020 16:59:28 GMT",
            "remediation_path": "C:\\Program Files (x86)\\Sophos\\Sophos Anti-Virus\\WSCClient.exe",
            "signatures_up_to_date": 1
            // ... Other fields in the result row
        },
        {
            "endpointId": "90eceb1f-590e-458b-a5df-e3ecb0c9d827",
            "hostname": "ep01",
            "type": "Antivirus",
            "name": "Microsoft Defender Antivirus",
            "state": "Off",
            "state_timestamp": "Wed, 11 Dec 2020 16:59:23 GMT",
            "remediation_path": "windowsdefender://",
            "signatures_up_to_date": 1
            // ... Other fields in the result row
        }
        // ... Other row objects in the result
    ],
    "metadata": {
        "columns": [
            {
                "name": "endpointId",
                "type": "text"
            },
            {
                "name": "hostname",
                "type": "text"
            },
            {
                "name": "type",
                "type": "text"
            },
            {
                "name": "name",
                "type": "text"
            },
            {
                "name": "state",
                "type": "text"
            },
            {
                "name": "state_timestamp",
                "type": "text"
            },
            {
                "name": "remediation_path",
                "type": "text"
            },
            {
                "name": "signatures_up_to_date",
                "type": "integer"
            },
            // ... Other columns in the metadata
        ]
    },
    "pages": {
        "fromKey": "WyIxNjA2MTY4OTc0NjQ5fDAwMDAwMDAwMDAxNTAwMHwwMDAwMDEiXQ==",
        "size": 1000,
        "total": 1,
        "items": 3,
        "maxSize": 1000
    }
}

6. Run an ad hoc query

Create a file named "request2.json" and add the following content to it:

{
    "adHocQuery": {
        "template": "SELECT * from processes",
        "name": "List processes"
    },
    "matchEndpoints": {
       "filters": [
           {
               "hostnameContains": "ep01"
           }
       ]
    }
}

As in Step 3, call this API to run the query:

curl -XPOST -H 'Content-Type: application/json' \
            -H 'Authorization: Bearer <jwt>' \
            -H 'X-Tenant-ID: <tenant-id>' \
            --data request2.json  \
                   <data-region>/live-discover/v1/queries/runs

The response is a query run object. Monitor its status and fetch the results as in Steps 4 and 5.

7. List query runs

To see recent query runs, call this API:

curl -XGET -H 'Content-Type: application/json' \
           -H 'Authorization: Bearer <jwt>' \
           -H 'X-Tenant-ID: <tenant-id>' \
                   <data-region>/live-discover/v1/queries/runs

A successful response returns a page of recent query run objects, instead of just one as seen in Step 3. Query runs are deleted after a period of time.

Rate limits

Leave a few seconds between API calls to avoid being rate-limited. If you're rate-limited, your API calls fail with the HTTP status code 429 (Too Many Requests).

The Live Discover API imposes a rate limit on the frequency of query runs in addition to the global rate limits and quotas that apply to all our APIs. You must restrict yourself to no more than 10 query runs per minute and no more than 500 query runs per day. This additional rate limit applies per tenant.

Additional resources

You can download the Postman collection for this API here.

Conclusion

You can now call the EDR Live Discover API to issue a query, monitor its status, and fetch the results. You can also do other operations such as view queries and their categories. For further details, read the API reference.