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.detectionUpdateResolutionInfoadds or updates resolution information for detection IDs.- Subscription:
detectionBulkResolutionProcessorapplies 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-IDheader. - [ ] 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
offsetandlimit, or continue with the returnedsearch_id. - [ ] Update response parsing for nested GraphQL fields and
{ seconds, nanos }timestamps. - [ ] Replace group queries with
detectionRetrieveByGroupKeywhen 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
errorsarray. - [ ] Request only the fields your integration uses, especially for nested entities and enrichment data.