Skip to content

Migrating from the Detections REST API

Audience and scope

This guide is for developers with an integration against the Detections REST API who need to move it to the Detections GraphQL API.

If you are building a new GraphQL integration, start with Getting started with the Detections GraphQL API.

This is not a drop-in replacement. The REST API starts a query run, waits for completion, and then returns a separate result page. The GraphQL API sends one search request and returns a selection of fields in its response.

The GraphQL schema uses detection names for the current API surface. Some schema descriptions and older operation aliases still use alert. Treat those names as detections.

This guide covers:

  • The equivalent GraphQL operation for each REST workflow.
  • Request and response shape changes.
  • Filter and pagination changes.
  • REST fields without a direct GraphQL equivalent.
  • GraphQL operations that add capabilities to the migration.

Mappings are based on the published Detections REST API v1 specification and the vendored Detections GraphQL schema.

What changed

Aspect REST v1 GraphQL
Query flow POST a query, poll its status, then get results Send detectionSearch with a query and select the fields to return
Filtering JSON properties such as detectionRule, severity, type, and from One cql_query string using QL
Response shape REST objects use camelCase fields and inline nested data GraphQL returns only selected fields and uses schema field names such as metadata and sensor_types
Counts GET /queries/detections/counts detectionAggregatesBySeverity or detectionCountByTenant, depending on the result needed
Pagination page and pageSize on a completed query run offset and limit, or a returned search_id for subsequent parts
Detection details Full REST detection object Request details with detectionRetrieveById, detectionRetrieveByHost, or detectionRetrieveByEntity
Authentication Bearer JWT and X-Tenant-ID The same bearer JWT and X-Tenant-ID headers

Authentication stays the same. Send the token and tenant ID to your configured GraphQL endpoint instead of a regional REST host. See Getting started as a Partner, as an Organization, or as a Tenant.

Endpoint mapping

Run a detections query

REST: POST /detections/v1/queries/detections

The REST request accepts separate properties for each filter. It returns a run ID, which you use in later requests.

{
  "severity": [4, 8, 9],
  "from": "2021-10-02T14:53:22.017Z",
  "to": "2021-11-01T15:53:22.017Z"
}

GraphQL: Query.detectionSearch

query SearchDetections($in: SearchRequestInput) {
  detectionSearch(in: $in) {
    status
    reason
    alerts {
      total_results
      list {
        id
        status
        metadata {
          title
          severity
          created_at { seconds nanos }
        }
      }
    }
  }
}
{
  "variables": {
    "in": {
      "cql_query": "from alert where severity >= 0.4 and severity <= 0.9 earliest='2021-10-02T14:53:22.017Z' latest='2021-11-01T15:53:22.017Z'",
      "limit": 100,
      "offset": 0
    }
  }
}

GraphQL returns the fields in the selection set. It does not return a full detection unless you request the full selection set. Keep the selection set small for list requests.

Check query status

REST: GET /detections/v1/queries/detections/{runId}

GraphQL: No direct equivalent is required. detectionSearch returns the result status in the same response. Check data.detectionSearch.status and the GraphQL errors array.

For large searches, the response can include a search_id. Pass that value to Query.detectionPoll to fetch the next result part.

Get query results

REST: GET /detections/v1/queries/detections/{runId}/results?page=1&pageSize=3

GraphQL: Read detectionSearch.alerts.list from the initial response. The GraphQL response contains total_results, which is the number of detections that match the query, not only the number in the current page.

For searches below 10,000 detections, request another page with offset and limit:

query SearchDetections($in: SearchRequestInput) {
  detectionSearch(in: $in) {
    status
    alerts {
      total_results
      list { id status metadata { title severity } }
    }
  }
}
{
  "variables": {
    "in": {
      "cql_query": "from alert where status = 'OPEN' earliest=-1d",
      "limit": 500,
      "offset": 500
    }
  }
}

For a large result set, use the search_id returned by the previous response. The API advances the result part for you. Keep fetching until the response indicates that no next part remains, and confirm that the number of detections received matches total_results.

Get a detection by ID

REST: The REST API returns full detection objects in the query result list. It does not expose a separate get-by-ID endpoint.

GraphQL: Query.detectionRetrieveById

query GetDetections($in: GetByIDRequestInput) {
  detectionRetrieveById(in: $in) {
    alerts {
      list {
        id
        status
        metadata {
          title
          description
          severity
          created_at { seconds nanos }
          first_seen_at { seconds nanos }
        }
        sensor_types
        attack_technique_ids
        source_entities { display_name subtype }
        target_entities { display_name subtype }
        event_ids { id }
      }
    }
  }
}
{
  "variables": {
    "in": {
      "iDs": ["<detection-id>"]
    }
  }
}

The input name is iDs, including its capitalization. You can pass more than one detection ID in the same request.

Find detections by host or entity

REST: Filter the query request with deviceName or other supported REST properties.

GraphQL: Use Query.detectionRetrieveByHost or Query.detectionRetrieveByEntity.

Both operations use GetByIDRequestInput. Pass the host ID or entity identifier in iDs, then select the detection fields you need from the returned list.

Find detections by group key

REST: POST /detections/v1/queries/detection-groups, followed by the group status and result requests.

GraphQL: Query.detectionRetrieveByGroupKey

The GraphQL operation accepts group-key values in iDs. Use it only when your integration needs the deduplication group. For general searches, use detectionSearch instead.

Get detection counts

REST: GET /detections/v1/queries/detections/counts

GraphQL: Use Query.detectionAggregatesBySeverity for severity aggregates. Use Query.detectionCountByTenant for a count across a tenant scope.

For severity aggregates, provide the time range as TimestampInput values and select the grouping fields you need:

query DetectionCounts($in: AggregateAlertsBySeverityInputInput) {
  detectionAggregatesBySeverity(in: $in) {
    aggregation {
      key
      count
      severities { info low medium high critical }
    }
  }
}

The GraphQL schema does not provide the REST response shape of a time series grouped by day, hour, or minute. If your integration needs that exact breakdown, use detectionSearch with a QL time range and aggregate the returned results in your application, or confirm the aggregate fields in the generated reference before implementation.

Request and field mapping

Filters

REST filters become predicates in cql_query. The QL source keyword remains alert for this API. Combine predicates with and. Use or when you need more than one value.

REST field GraphQL equivalent Notes
detectionRule QL predicate for the rule field Confirm the supported QL field name for your tenant's data.
deviceName QL host predicate, or detectionRetrieveByHost The GraphQL host lookup takes an ID, not a device name.
severity severity predicate and metadata.severity REST severity values use integers from 0 to 10. GraphQL QL severity values and metadata.severity use a Float32 range from 0 to 1. Do not copy a REST value such as 4 into a GraphQL query. Rewrite the filter with a 0-to-1 threshold and validate that threshold against your data.
type QL predicate REST values include process, threat, and vulnerability.
category QL predicate or sensor_types selection REST categories include cloud, endpoint, email, firewall, iam, network, compound, and backupAndRecovery.
source QL predicate or entity fields The GraphQL schema exposes source and target entities instead of the REST source object.
from / to QL time expression Use earliest and latest in the QL query.

Do not assume that every REST filter has the same QL field name. Validate each predicate against the Detections GraphQL API reference and test it with a small time range.

Response fields

REST field GraphQL field Notes
id id The identifier remains a string value.
severity metadata.severity These values use different scales. Keep them as separate values during migration.
type No direct equivalent REST type is a high-level category with the values process, threat, and vulnerability. The GraphQL Alert2 type has no field that reproduces this category; metadata.title and detector metadata provide descriptive details, not the REST type value.
detectionRule alerting_rules.id or metadata.creator.rule.rule_id The rule can appear in rule references and creator metadata.
sensor sensor_types and entity fields Sensor data is represented across several GraphQL fields.
device source_entities or target_entities Select the structured entity fields that your integration needs.
entities entities, source_entities, and target_entities GraphQL can return relationships and structured entities.
geolocation enrichment_details.geo_ip GraphQL returns IP and location values, including latitude, longitude, and ISO country code.
mitreAttacks attack_technique_ids and enrichment_details.mitre_attack_info REST groups techniques under tactics. GraphQL returns technique IDs and technique details with tactic names.
rawData @raw for filtering, or Event.additionalData.originalData for the response @raw is the QL field for the unaltered source data of a related event. Use it to filter detections. To return the data, get IDs from event_ids, then pass them to events(input: { rns: [...] }) at the federated endpoint. Select additionalData { originalData }.
intelixFileReputation No documented equivalent Do not migrate this field without confirming a supported replacement for your integration.
time metadata.created_at or metadata.first_seen_at GraphQL timestamps use { seconds, nanos }.
suppressed suppressed GraphQL returns a Boolean rather than the REST string value.

Get raw data for a detection event

event_ids returns event resource names. It does not return an event payload. Pass each id unchanged to the Events API to get event details.

For a worked example, including the normalized values and MITRE ATT&CK details, see Get event details from cases and detections.

query DetectionEventData($input: EventsInput!) {
  events(input: $input) {
    rn
    values
  }
}
{
  "variables": {
    "input": {
      "rns": ["<event-id-from-detection>"]
    }
  }
}

Mutations, subscriptions, and new capabilities

The REST Detections API is read-only. The GraphQL API adds mutations for common detection workflows:

  • Mutation.detectionUpdateResolutionInfo adds or updates resolution information for detection IDs.
  • Subscription: detectionBulkResolutionProcessor applies a resolution to detections selected by a QL query. The generated reference does not publish Subscription operation pages.

The schema also exposes investigation operations. Use them only when your integration manages investigations and verify the required input fields in the generated reference.

Migration checklist

  • [ ] Replace the regional REST URL with https://api.taegis.sophos.com/graphql.
  • [ ] Keep the bearer token and X-Tenant-ID header.
  • [ ] Replace the REST query-run, poll, and results sequence with detectionSearch.
  • [ ] Translate each REST filter into a tested QL predicate.
  • [ ] Reconcile the REST severity scale with GraphQL metadata.severity. Do not convert between them.
  • [ ] Replace REST pagination with offset and limit, or continue with the returned search_id.
  • [ ] Update response parsing for nested GraphQL fields and { seconds, nanos } timestamps.
  • [ ] Replace group queries with detectionRetrieveByGroupKey when you need group-key lookup.
  • [ ] Choose the correct GraphQL count operation for your reporting use case.
  • [ ] Add GraphQL error handling. A query error can return HTTP 200 with an errors array.
  • [ ] Request only the fields your integration uses, especially for nested entities and enrichment data.