Skip to content

Anomalies API

You can list, read, update, archive, and delete anomalies programmatically, and work with their history, comments, source records, and tickets.

Tip

For complete API documentation, including request/response schemas, visit the API docs.

All endpoints use the base URL of your Qualytics deployment (e.g., https://your-instance.qualytics.io/api) and require a Personal API Token. Every endpoint requires the Member role. Reads also need the Reporter team permission on the anomaly's datastore, and changes need the Author team permission. Source records need the Viewer team permission, unmasked values the Editor team permission, and ticket links the Manager role on top of Author. Admins are exempt from team permissions. See Anomaly Permissions for the full matrix.


Error Responses

Status Code Description
400 Bad Request The change is not allowed in the anomaly's current status, such as moving an archived anomaly straight back to Active, or giving an archived anomaly a different archived status.
401 Unauthorized Missing or invalid API token.
403 Forbidden The user does not have the required role, or lacks the required team permission on the anomaly's datastore.
404 Not Found No anomaly, ticket link, or integration exists with the specified ID.
409 Conflict The anomaly has no source records, the ticket is already linked to the anomaly, or the anomaly is archived and its comments cannot change.
422 Unprocessable Entity Invalid request body or parameters, such as an unknown status or an invalid filters value.
Error response examples

404 Not Found:

{ "detail": "Anomaly id: 999 not found" }

400 Bad Request (restoring an archived anomaly to Active):

{ "detail": "Cannot change archived anomaly back to active" }

409 Conflict (reading the source records of an anomaly that has none):

{ "detail": "Anomaly id: 12345 does not have a source record" }

Anomaly Fields

Getting a single anomaly returns the full set of fields below. Items in the list response carry the same fields except operation and fingerprint_source, so get the anomaly by ID when you need them.

Field Type Notes
id int Numeric primary identifier.
uuid string (UUID) Stable, globally unique identifier.
type "record" \| "shape" Anomaly category. See Record Anomalies and Shape Anomalies.
status string One of Active, Acknowledged, Resolved, Duplicate, Invalid, Discarded. See Status.
impact_level "low" \| "medium" \| "high" \| "critical" \| null The anomaly's impact: the highest impact among its failed checks. See Impact.
impact_reason string \| null Why the anomaly has its impact, in one line (for example, High: across this scan, some of the checked records failed, on a High-importance check.).
weight float Deprecated: use impact_level. Still returned for existing integrations, and no longer used for sorting or Flow filters.
anomalous_records_count int \| null Number of records associated with the anomaly. Set to 1 for record anomalies. For shape anomalies it holds the number of affected rows when Qualytics can count them, otherwise null.
description string User-editable business description. The value is Markdown and has no length cap.
created string (ISO 8601) When the anomaly was detected.
fingerprint string Used to deduplicate recurring anomalies. Returned as a quoted string so that large values survive JavaScript clients. See Fingerprints.
fingerprint_source AnomalyStub \| null The earliest other anomaly that shares this anomaly's fingerprint, with its id, uuid, type, status, and fingerprint. null when the anomaly has no fingerprint or no other anomaly shares it. Single-anomaly response only.
global_tags Tag[] Tags applied to the anomaly. Each tag carries type, name, color, description, category, and weight_modifier. Tags have no id, so identify them by name.
assignees UserStub[] Users assigned to the anomaly. See Assignees.
failed_checks FailedCheck[] Checks that produced the anomaly. Each entry nests the check under quality_check (with its id and rule_type), next to the message, the suggested_field, suggested_value, and suggested_score fields, and parameters. Each entry also carries its levels: failure_level (how much failed, before the check's importance adjusts it), quality_check_importance_level (the check's importance that adjusted it), impact_level (the result), and impact_reason.
datastore, container, partition refs Source location of the anomaly. partition carries the partition's name and location.
operation ref The scan that produced the anomaly, with its type, start and end times, result, and who triggered it. Single-anomaly response only.
Example response (abbreviated)
{
  "id": 12345,
  "uuid": "8e8b9f8b-1234-4abc-9def-012345678901",
  "type": "record",
  "status": "Active",
  "impact_level": "high",
  "impact_reason": "High: across this scan, some of the checked records failed, on a High-importance check.",
  "weight": 54.0,
  "anomalous_records_count": 1,
  "description": "Customer ID is missing for new orders",
  "created": "2026-05-15T17:22:08Z",
  "fingerprint": "9182736455",
  "fingerprint_source": null,
  "global_tags": [
    { "type": "global", "name": "High", "color": "#E53935", "description": null, "category": null, "weight_modifier": 10 }
  ],
  "assignees": [
    { "id": 42, "name": "Alice Lee", "email": "alice@example.com" }
  ],
  "failed_checks": [
    {
      "quality_check": { "id": 778, "rule_type": "notNull" },
      "message": "...",
      "suggested_field": null,
      "suggested_value": null,
      "suggested_score": null,
      "parameters": null,
      "failure_level": "medium",
      "quality_check_importance_level": "high",
      "impact_level": "high",
      "impact_reason": "High: across this scan, some of the checked records failed, on a High-importance check."
    }
  ]
}

List Anomalies

Returns a paginated list of anomalies, scoped to the datastores the caller can see. Without a sort parameter, anomalies are returned by ID, oldest first. With sort_impact_level, anomalies at the same Impact are sorted by weight in the same direction, then by ID. Ties in the other sorts are broken by ID. To match the default order of the anomaly lists in the app, pass sort_impact_level=desc (highest Impact first).

Endpoint: GET /anomalies

Permission: Member role. Only the anomalies of datastores the caller has access to are returned.

Query Parameter Type Description
id list of integers or UUIDs Return the anomalies with these IDs or UUIDs.
search string Match the anomaly ID or UUID, or a piece of a failed check's message, ignoring case.
message string Only anomalies with a failed check whose message contains this text, ignoring case.
status list of strings Only anomalies with these statuses: Active, Acknowledged, Resolved, Duplicate, Invalid, Discarded.
archived include / only Include archived anomalies, or return only archived anomalies.
anomaly_type record / shape Only anomalies of this type.
datastore, container list of integers Only anomalies of these datastores or tables and files.
field, field_profile list of strings / list of integers Only anomalies of these field names or field profile IDs.
quality_check, template, rule_type list of integers / list of integers / list of strings Only anomalies produced by these checks, by checks built from these templates, or by checks of these rule types.
operation, container_scan, partition_scan list of integers Only anomalies found by these Scan operations, container scans, or partition scans.
resolved_by_operation, resolved_by_container_scan list of integers Only anomalies auto-resolved by these Scan operations or container scans.
tag list of strings Only anomalies with any of these tag names.
assignee list of integers Only anomalies assigned to any of these user IDs.
impact_level list of strings Only anomalies at these impact levels: low, medium, high, or critical. Pass it once per level, for example impact_level=critical&impact_level=high. Inside filters, it takes in and not_in. Any other value returns a 422.
related_to_id integer The ID of the earliest anomaly with a fingerprint. Returns the later anomalies that share that fingerprint. Passing the ID of a later anomaly returns no results.
segment integer Only anomalies of this segment.
source_enriched boolean Only anomalies with (true) or without (false) source records.
timeframe, created_date, start_date, end_date varies Only anomalies created in this period.
filters string (JSON) Narrows the list by one or more filters, written as a JSON object. See Filtering Lists.
sort_id, sort_created, sort_impact_level, sort_anomalous_records_count asc / desc Sort by ID, creation date, Impact, or anomalous record count. sort_weight is a deprecated alias of sort_impact_level.
Example request and response

Request:

curl -X GET "https://your-instance.qualytics.io/api/anomalies?status=Active&assignee=42&tag=High&sort_impact_level=desc" \
  -H "Authorization: Bearer YOUR_TOKEN"

Response (abbreviated). Each item carries the anomaly's fields, except operation and fingerprint_source:

{
  "items": [
    {
      "id": 12345,
      "type": "record",
      "status": "Active",
      "weight": 54.0,
      "anomalous_records_count": 1,
      "global_tags": [{ "name": "High", "weight_modifier": 10 }],
      "assignees": [{ "id": 42, "name": "Alice Lee" }]
    }
  ],
  "total": 1,
  "page": 1,
  "size": 50,
  "pages": 1
}

Filtering Lists

GET /api/anomalies accepts a filters parameter: a JSON object with one entry per filter, saying how that filter's values should match.

curl -G "https://your-instance.qualytics.io/api/anomalies" \
  --data-urlencode 'filters={"tag":{"not_in":["PII"]},"assignee":{"empty":true}}' \
  -H "Authorization: Bearer YOUR_TOKEN"

This returns the anomalies that aren't tagged PII and have no one assigned.

Key Returns
in The items that match at least one of the values.
not_in The items that match none of the values.
all_of The items that match every value, on filters an item can have several of, such as tags.
empty The items with no value (true) or at least one (false), on filters an item can lack.
  • Different filters combine with AND, and so do keys on the same filter.
  • empty can't be combined with other keys on the same filter.
  • A filter sent both as its own parameter and inside filters returns a 422. Put all of that filter's conditions in filters.
  • in, not_in and all_of each take between 1 and 20 values.
  • A filter or key the endpoint doesn't accept returns a 422 that names it. The API docs list what each endpoint accepts.

Get an Anomaly

Returns the details of one anomaly, including the Scan that found it and the earliest anomaly that shares its fingerprint.

Endpoint: GET /anomalies/{id}

Permission: Member role, with the Reporter team permission on the anomaly's datastore

Parameter Type Description
id integer or UUID The anomaly ID or UUID.
include_deleted boolean Also return the anomaly when it is archived. Defaults to false.
Example request
curl -X GET "https://your-instance.qualytics.io/api/anomalies/12345?include_deleted=true" \
  -H "Authorization: Bearer YOUR_TOKEN"

The response carries the fields listed in Anomaly Fields.


Get an Anomaly's Failed Checks

Returns the checks that failed in the anomaly, each with its message, as they were when the anomaly was created. Archived anomalies are included.

Endpoint: GET /anomalies/{id}/failed-checks

Permission: Member role, with the Reporter team permission on the anomaly's datastore

Parameter Type Description
id integer or UUID The anomaly ID or UUID.
Example request
curl -X GET "https://your-instance.qualytics.io/api/anomalies/12345/failed-checks" \
  -H "Authorization: Bearer YOUR_TOKEN"

Update an Anomaly

Changes the status, description, tags, or assignees of an anomaly. Only the fields you send are changed.

Endpoint: PUT /anomalies/{id}

Permission: Member role, with the Author team permission on the anomaly's datastore

Body Field Type Description
status Active / Acknowledged Acknowledge or reactivate an open anomaly. Sending Acknowledged for an archived anomaly restores it. An archived anomaly cannot go straight back to Active. To archive an anomaly, use Archive or Delete an Anomaly.
description string The anomaly's description, in Markdown, with no length limit.
tags list of strings Tag names. Replaces the current tags, and [] removes them all. Tags on the anomaly do not change its impact_level. Only the tags on its failed checks do, through the checks' Importance.
assignee_ids list of integers User IDs. Replaces the current assignees, and [] unassigns everyone. To add a user, read assignees first and send the full list. Each user needs at least the Viewer team permission on the datastore.

Assigning from a check's default assignee happens only when the anomaly is created, so it cannot be triggered through this endpoint. See Anomaly Assignees.

Example request
curl -X PUT "https://your-instance.qualytics.io/api/anomalies/12345" \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "status": "Acknowledged",
    "description": "Investigating with the data engineering team",
    "tags": ["High", "Customer-Impact"],
    "assignee_ids": [42, 57]
  }'

The response carries the updated anomaly, with the fields listed in Anomaly Fields.


Update Anomalies in Bulk

Applies changes to many anomalies in one call. Each entry names an anomaly and the fields to change on it.

Endpoint: PATCH /anomalies

Permission: Member role, with the Author team permission on the datastore of every anomaly

The body is a list of entries. Each entry accepts the body fields of Update an Anomaly, plus:

Body Field Type Description
id integer or UUID The anomaly to update.
tags_mode replace / add How tags is applied. replace (the default) makes tags the anomaly's only tags. add keeps the current tags and adds the given ones, without repeating a tag.
Example request
curl -X PATCH "https://your-instance.qualytics.io/api/anomalies" \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '[
    { "id": 12345, "status": "Acknowledged" },
    { "id": 12346, "assignee_ids": [42] },
    { "id": 12347, "tags": [] },
    { "id": 12348, "tags": ["Critical"], "tags_mode": "add" }
  ]'

The response carries the list of updated anomalies.


Archive or Delete an Anomaly

Archives an anomaly with a resolution, or deletes it permanently. Returns 204 No Content.

Endpoint: DELETE /anomalies/{id}

Permission: Member role, with the Author team permission on the anomaly's datastore

Query Parameter Type Description
archive boolean true (the default) archives the anomaly. false deletes it permanently, whatever its status, and cannot be undone.
status Resolved / Duplicate / Invalid / Discarded The resolution to archive with. When it is left out, an Active anomaly is archived as Resolved. An archived anomaly cannot be given a different resolution.
Body Field Type Description
status string The resolution, as an alternative to the query parameter.
comment string A comment added to the anomaly's Timeline when it is archived.
mentioned_user_ids list of integers Users mentioned in the comment, who receive a Mention notification.
Example requests

Archive with a resolution and a comment:

curl -X DELETE "https://your-instance.qualytics.io/api/anomalies/12345?archive=true&status=Resolved" \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "comment": "Resolved after backfill, see @[Alice Lee](42)",
    "mentioned_user_ids": [42]
  }'

Delete permanently:

curl -X DELETE "https://your-instance.qualytics.io/api/anomalies/12345?archive=false" \
  -H "Authorization: Bearer YOUR_TOKEN"

Archive or Delete Anomalies in Bulk

Archives or deletes many anomalies in one call. Returns 204 No Content.

Endpoint: DELETE /anomalies

Permission: Member role, with the Author team permission on the datastore of every anomaly

The body is a list of entries. Each entry takes the anomaly's id and the same fields as Archive or Delete an Anomaly: archive, status, comment, and mentioned_user_ids.

Example request
curl -X DELETE "https://your-instance.qualytics.io/api/anomalies" \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '[
    { "id": 12345, "archive": true, "status": "Resolved" },
    { "id": 12346, "archive": true, "status": "Duplicate" },
    { "id": 12347, "archive": false }
  ]'

Get an Anomaly's Change History

Returns the paginated change log behind the anomaly's Timeline: status changes, description edits, tag and assignee changes, and ticket links and unlinks, each with the user who made it and when.

Endpoint: GET /anomalies/{id}/history

Permission: Member role, with the Reporter team permission on the anomaly's datastore

Query Parameter Type Description
include_deleted boolean Also return the history of an archived anomaly. Defaults to false.
page integer The page to return, starting at 1.
size integer Entries per page. Defaults to 50, up to 100.

Each field in changeset is a [before, after] pair. transaction holds the change's id, issued_at time, and user, and operation is insert, update, or delete. Increase page until it reaches pages to read the whole log.

Example response

Response (abbreviated):

{
  "items": [
    {
      "changeset": {
        "status": ["Active", "Acknowledged"],
        "assignees": [
          [{ "id": 42, "name": "Alice Lee" }],
          [{ "id": 42, "name": "Alice Lee" }, { "id": 57, "name": "Bob Patel" }]
        ],
        "ticket_link": [
          null,
          {
            "ticket_number": "INC0010001",
            "ticket_url": "https://example.service-now.com/incident.do?sys_id=abc123sys",
            "integration_type": "servicenow"
          }
        ]
      },
      "transaction": {
        "id": 9876,
        "issued_at": "2026-05-15T17:22:08Z",
        "user": { "id": 1, "name": "Alice Lee" }
      },
      "operation": "update"
    }
  ],
  "total": 124,
  "page": 1,
  "size": 50,
  "pages": 3
}

List an Anomaly's Comments

Returns the comments on an anomaly, newest first, as a list. Replies are returned together with the comment that starts their thread. A comment deleted while it still had replies is returned with an empty message.

Endpoint: GET /anomalies/{id}/comments

Permission: Member role, with the Reporter team permission on the anomaly's datastore

Query Parameter Type Description
start_date datetime (ISO 8601) Inclusive lower bound on created, for comments not attached to a Timeline entry.
end_date datetime (ISO 8601) Exclusive upper bound on created, so back-to-back ranges do not overlap.
transaction_id list of integers Return the comments attached to these Timeline entries, whatever the dates.

Besides its message and created time, each comment carries:

Field Type Description
entity_type string The kind of asset the comment belongs to: anomaly, container, quality_check, partition, datastore, or field.
entity_id int Identifier of that asset.
transaction_id int (nullable) The Timeline entry the comment is anchored to. null when the comment is not anchored to a change.
parent_id int (nullable) The comment this one replies to. null when the comment starts a thread.
deleted_at datetime (nullable) Set when the comment was deleted but kept as a placeholder because it still has replies.
Example request
curl -X GET "https://your-instance.qualytics.io/api/anomalies/12345/comments?start_date=2026-05-01T00:00:00Z" \
  -H "Authorization: Bearer YOUR_TOKEN"

Response change

Comments identify their asset with entity_type and entity_id. The previous anomaly_id and partition_id fields are no longer returned. Requests are unaffected: the previous payload shapes are still accepted when creating comments.


Post a Comment

Adds a comment to an anomaly, as a new comment, a reply, or a comment on one Timeline entry.

Endpoint: POST /comments

Permission: Member role, with the Viewer team permission on the anomaly's datastore

Body Field Type Description
entity_type string anomaly for an anomaly comment. The same endpoint also takes container, quality_check (for checks and check templates), partition, datastore, and field.
entity_id integer The anomaly ID.
message string The comment text. Use @[Full Name](user_id) to mention a user.
transaction_id integer Optional. Attaches the comment to this Timeline entry of the same anomaly.
parent_id integer Optional. Posts the comment as a reply to this comment of the same anomaly.
mentioned_user_ids list of integers Optional. Users to notify. They must be active users.
Example request
curl -X POST "https://your-instance.qualytics.io/api/comments" \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "entity_type": "anomaly",
    "entity_id": 12345,
    "transaction_id": 9876,
    "message": "Expected during the migration window. @[Alice Lee](42) please confirm.",
    "mentioned_user_ids": [42]
  }'

The response carries the comment's message, author, and mentions. To read entity_type, entity_id, transaction_id, parent_id, or deleted_at, get the comment again with one of the GET endpoints.

A rejected comment returns a status code that says what to correct:

Status Returned when
422 The comment replies to a reply (threads are one level deep), the transaction_id is not a Timeline entry of that same asset, the parent_id belongs to a different asset, a mentioned user does not exist or is no longer active, or commenting is not supported for that asset type.
409 The asset is archived, so comments cannot be added, edited, or deleted until it is unarchived. Also returned when replying to a deleted comment or editing a deleted comment.
403 You lack the required team permission on the asset's datastore, or you are trying to edit another user's comment. Only the author can edit a comment.
404 The comment or the asset does not exist.

Update a Comment

Changes the text of a comment. Only its author can update it.

Endpoint: PUT /comments/{id}

Permission: Member role, as the comment's author

Body Field Type Description
message string The new comment text.
mentioned_user_ids list of integers Users mentioned in the comment. Only users newly added are notified.
Example request
curl -X PUT "https://your-instance.qualytics.io/api/comments/555" \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "message": "Confirmed with the source team.", "mentioned_user_ids": [] }'

Delete a Comment

Deletes a comment. A comment that still has replies is kept as a placeholder, so the replies stay in the thread. Deleting the last reply also removes the placeholder.

Endpoint: DELETE /comments/{id}

Permission: Member role, as the comment's author, or the Admin role

Example request
curl -X DELETE "https://your-instance.qualytics.io/api/comments/555" \
  -H "Authorization: Bearer YOUR_TOKEN"

Get Source Records

Returns the rows behind an anomaly, as stored by the Scan in the enrichment datastore.

Endpoint: GET /anomalies/{id}/source-record

Permission: Member role, with the Viewer team permission on the anomaly's datastore. include_masked=true also needs the Editor team permission.

Query Parameter Type Description
limit integer The most rows to return. Defaults to 10.
include_masked boolean Return the real values of masked fields. Each call is recorded in the Masking Audit Log with the masked fields. Defaults to false.

An anomaly without source records returns 409 Conflict. See Source Records.

Example request and response

Request:

curl -X GET "https://your-instance.qualytics.io/api/anomalies/12345/source-record?limit=5" \
  -H "Authorization: Bearer YOUR_TOKEN"

Response: source_record holds the rows as a JSON string, so parse it to read them.

{
  "created": "2026-05-15T17:22:08Z",
  "source_record": "[{\"emp_id\": 102, \"name\": \"Bob Johnson\", \"salary\": 30000}]"
}

Download Source Records

Downloads the rows behind an anomaly as a CSV file named source_records_<id>.csv.

Endpoint: GET /anomalies/{id}/source-record/download

Permission: Member role, with the Viewer team permission on the anomaly's datastore. include_masked=true also needs the Editor team permission.

Query Parameter Type Description
limit integer The most rows to return. With no limit, the file holds every row the Scan kept.
include_masked boolean Return the real values of masked fields, recorded in the Masking Audit Log. Defaults to false.
Example request
curl -X GET "https://your-instance.qualytics.io/api/anomalies/12345/source-record/download" \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -o source_records_12345.csv

Reading source records from the enrichment datastore

Real values of masked fields are only returned by endpoints that accept include_masked: these two, the container scan equivalents (see Get source records of a container scan), and GET /containers/{id}/source-records. All of them check the Editor team permission and write to the Masking Audit Log. Reading the _source_records output with GET /datastores/{id}/source-records always returns ***MASKED*** in the RECORD column. See Read Source Records.


Search Tickets

Searches the connected ticketing system for tickets whose key or summary matches, to find an existing ticket before linking it.

Endpoint: GET /integrations/ticketing/tickets/search

Permission: Manager role

Query Parameter Type Description
query string Text to match against the ticket key or summary.
status string Only tickets with this status.
priority string Only tickets with this priority.
project_key string Jira only. Only tickets of this project. Without it, every configured project is searched.
Example request and response

Request:

curl -X GET "https://your-instance.qualytics.io/api/integrations/ticketing/tickets/search?query=customer" \
  -H "Authorization: Bearer YOUR_TOKEN"

Response:

{
  "items": [
    {
      "ticket_id": "10002",
      "ticket_number": "SCRUM-3",
      "title": "Task 3",
      "description": null,
      "status": "To Do",
      "priority": null,
      "created_at": "2025-12-02T12:34:20",
      "updated_at": "2025-12-02T12:34:21",
      "assigned_to": null,
      "url": "https://your-domain.atlassian.net/browse/SCRUM-3"
    }
  ],
  "total": 1,
  "page": 1,
  "size": 50,
  "pages": 1
}

Search Tickets One Page at a Time

Runs the same search a page at a time, with a token for the next page. This is what the Link Ticket dialog uses for its Load more tickets button. It does not count every match first, so it stays quick on large projects.

Endpoint: GET /integrations/ticketing/tickets/search-page

Permission: Manager role

Query Parameter Type Description
query string Optional. On Jira, matches a ticket key, the key or name of an allowed project, or words in the summary, description, or comments. On ServiceNow, matches words in the short description or description, not the incident number. Leave it empty to list the most recently updated tickets first.
project_key string Optional. Jira only. Limits the search to one of the allowed projects. Any other key returns 400. Without it, every allowed project is searched.
status string Optional. Only tickets with this status.
priority string Optional. Only tickets with this priority.
limit integer The most tickets in the page, from 1 to 100. Defaults to 50. A value above 100 returns 422.
next_page_token string The token from the previous page. Leave it out for the first page.
Example request and response

Request:

curl -X GET "https://your-instance.qualytics.io/api/integrations/ticketing/tickets/search-page?project_key=SCRUM&limit=2" \
  -H "Authorization: Bearer YOUR_TOKEN"

Response: pass next_page_token back unchanged to read the next page. It is null on the last page.

{
  "items": [
    {
      "ticket_id": "10431",
      "ticket_number": "SCRUM-43",
      "title": "Null customer IDs in orders",
      "description": null,
      "status": "To Do",
      "priority": "Medium",
      "created_at": "2026-10-20T14:02:11",
      "updated_at": "2026-10-26T09:15:40",
      "assigned_to": null,
      "url": "https://your-domain.atlassian.net/browse/SCRUM-43"
    },
    {
      "ticket_id": "10430",
      "ticket_number": "SCRUM-42",
      "title": "Late shipments feed",
      "description": null,
      "status": "In Progress",
      "priority": "Medium",
      "created_at": "2026-10-19T11:47:03",
      "updated_at": "2026-10-25T16:30:12",
      "assigned_to": null,
      "url": "https://your-domain.atlassian.net/browse/SCRUM-42"
    }
  ],
  "next_page_token": "CAEaAggD"
}

Get a Ticket

Returns one ticket from the connected ticketing system.

Endpoint: GET /integrations/ticketing/tickets/{ticket_id}

Permission: Manager role

Parameter Type Description
ticket_id string The ticket's ID or key, such as the sys_id in ServiceNow or PROJ-123 in Jira.
Example request
curl -X GET "https://your-instance.qualytics.io/api/integrations/ticketing/tickets/SCRUM-3" \
  -H "Authorization: Bearer YOUR_TOKEN"

Create a Ticket for an Anomaly

Creates a ticket in the connected ticketing system and links it to the anomaly in one call. The anomaly's details are added to the description, with a link back to Qualytics. On Jira, the issue is created with the type set for that project on the integration, Task unless another was picked. A type Jira refuses returns 400, naming the type and the project.

Endpoint: POST /integrations/ticketing/anomalies/{anomaly_id}/tickets

Permission: Manager role, with the Author team permission on the anomaly's datastore

Body Field Type Description
title string The ticket's summary.
description string Optional. The ticket's description. The anomaly's details are added to it.
status string Optional. The ticket's initial status.
priority string Optional. The ticket's priority.
urgency, impact string Optional. ServiceNow only.
category, subcategory string Optional. ServiceNow only.
assigned_to, assigned_group string Optional. The user and the group responsible for the ticket.
project_key string Optional. Jira only. The project to create the ticket in. Without it, the integration's default project is used.
additional_fields object Optional. Other fields to send to the ticketing system.

For the values each field accepts, see Create a Jira Ticket and Create a ServiceNow Ticket.

Example request and response

Request:

curl -X POST "https://your-instance.qualytics.io/api/integrations/ticketing/anomalies/1/tickets" \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Investigate CUSTOMER_NAME similarity anomaly",
    "description": "Follow-up on an anomaly detected during the daily scan.",
    "priority": "Medium"
  }'

Response:

{
  "ticket": {
    "ticket_id": "10428",
    "ticket_number": "SCRUM-40",
    "title": "Investigate CUSTOMER_NAME similarity anomaly",
    "status": "Idea",
    "priority": "Medium",
    "category": "Task",
    "created_at": "2026-01-23T15:22:05",
    "url": "https://your-domain.atlassian.net/browse/SCRUM-40",
    "metadata": { "project_key": "SCRUM", "issue_type": "Task" }
  },
  "anomaly_id": 1,
  "integration_id": 2,
  "message": "Ticket SCRUM-40 created and linked to anomaly 1"
}

Returns the tickets linked to an anomaly. Each call also asks Qualytics to refresh those tickets from the ticketing system in the background, so the next call returns their latest status.

Endpoint: GET /anomalies/{anomaly_id}/ticket-links

Permission: Member role, with the Reporter team permission on the anomaly's datastore

Example request and response

Request:

curl -X GET "https://your-instance.qualytics.io/api/anomalies/12345/ticket-links" \
  -H "Authorization: Bearer YOUR_TOKEN"

Response:

[
  {
    "id": 678,
    "anomaly_id": 12345,
    "integration_id": 3,
    "ticket_id": "abc123sys",
    "ticket_number": "INC0010001",
    "ticket_url": "https://example.service-now.com/incident.do?sys_id=abc123sys",
    "status": "New",
    "last_synced": "2026-05-15T18:02:11Z",
    "ticket_metadata": null,
    "post_updates": true,
    "created": "2026-05-15T17:40:00Z"
  }
]

Links an existing ticket to an anomaly. Only the link is recorded in Qualytics: nothing is sent to the ticketing system.

Endpoint: POST /anomalies/{anomaly_id}/ticket-links

Permission: Manager role, with the Author team permission on the anomaly's datastore

Body Field Type Description
integration_id integer The ticketing integration that holds the ticket.
ticket_id string The ticket's ID in the ticketing system, such as the ServiceNow sys_id.
ticket_number string The ticket's number, such as INC0010001.
ticket_url string Optional. A direct link to the ticket.
status string The ticket's current status.
ticket_metadata object Optional. More data to keep with the link.

Linking a ticket that is already linked to the anomaly returns 409 Conflict.

Example request
curl -X POST "https://your-instance.qualytics.io/api/anomalies/12345/ticket-links" \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "integration_id": 3,
    "ticket_id": "abc123sys",
    "ticket_number": "INC0010001",
    "ticket_url": "https://example.service-now.com/incident.do?sys_id=abc123sys",
    "status": "New"
  }'

The response carries the new link, with the fields shown in List Ticket Links.


Turns Qualytics writes on or off for one linked ticket, which is what the Two-way / Read only button on the ticket card does. The ticket's status, title, and comments are still read back either way.

Endpoint: PATCH /anomalies/{anomaly_id}/ticket-links/{link_id}

Permission: Manager role, with the Author team permission on the anomaly's datastore

Body Field Type Description
post_updates boolean Whether Qualytics posts comments and status notes to this ticket.
Example request
curl -X PATCH "https://your-instance.qualytics.io/api/anomalies/12345/ticket-links/678" \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "post_updates": false }'

Removes the link between a ticket and an anomaly. The ticket stays in the ticketing system. Returns 204 No Content.

Endpoint: DELETE /anomalies/{anomaly_id}/ticket-links/{link_id}

Permission: Manager role, with the Author team permission on the anomaly's datastore

Example request
curl -X DELETE "https://your-instance.qualytics.io/api/anomalies/12345/ticket-links/678" \
  -H "Authorization: Bearer YOUR_TOKEN"