Skip to content

Anomalies API

This page documents the REST endpoints for working with anomalies in Qualytics. All endpoints share the base URL of your deployment (for example, https://your-instance.qualytics.io/api) and require a Bearer token.

Sections below are grouped by what you are trying to do. The same anomaly object is exchanged across reads and writes: see the Anomaly schema section for the field reference.

Tip

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

Permissions

All endpoints require the Member role. Most reads need the Viewer team permission on the anomaly's datastore (reading comments needs only the Reporter team permission); writes need the Author team permission (or higher), including description edits. Ticket-link writes need the Manager role plus the Author team permission on the datastore. Per-endpoint requirements are called out below.

Anomaly schema

Every read endpoint that returns an anomaly emits the same shape. The most relevant fields are:

Field Type Notes
id int Numeric primary identifier.
uuid string (UUID) Stable, globally unique identifier.
type "record" \| "shape" Anomaly category. See Types.
status string One of Active, Acknowledged, Resolved, Duplicate, Invalid, Discarded. See Status.
weight int Severity score.
anomalous_records_count int Number of records flagged for record anomalies.
description string User-editable business description.
created string (ISO 8601) When the anomaly was detected.
fingerprint int Used to deduplicate recurring anomalies. See Fingerprints.
global_tags Tag[] Tags applied to the anomaly.
assignees UserStub[] Users assigned to the anomaly. See Assignees.
failed_checks FailedCheck[] Checks that produced the anomaly.
datastore, container, partition_scan refs Source location of the anomaly.
Example response (abbreviated)
{
  "id": 12345,
  "uuid": "8e8b9f8b-1234-4abc-9def-012345678901",
  "type": "record",
  "status": "Active",
  "weight": 54,
  "anomalous_records_count": 1,
  "description": "Customer ID is missing for new orders",
  "created": "2026-05-15T17:22:08Z",
  "fingerprint": 9182736455,
  "global_tags": [{ "id": 7, "name": "High" }],
  "assignees": [
    { "id": 42, "name": "Alice Lee", "email": "alice@example.com" }
  ],
  "failed_checks": [{ "id": 778, "rule_type": "notNull", "message": "..." }]
}

List and retrieve

List anomalies

Returns a paginated list of anomalies sorted by creation timestamp and severity (most recent first).

Endpoint: GET /api/anomalies

Permission: Member. Results are scoped to datastores the caller can see.

Common query parameters (all optional, most accept multiple values):

Parameter Type Description
search string Substring match on the anomaly message or ID.
status AnomalyStatusType[] Filter by status (Active, Acknowledged, Resolved, Duplicate, Invalid, Discarded).
anomaly_type "record" \| "shape" Filter by anomaly type.
datastore, container int[] Scope to specific datastores or containers.
field, quality_check, rule_type varies Scope to specific fields, checks, or rule types.
tag string[] Filter by tag names.
assignee int[] Filter by anomaly assignee user IDs.
archived "include" \| "only" Include or restrict to archived anomalies.
timeframe, created_date, start_date, end_date varies Time-based filters.
related_to_id int Return anomalies that share a fingerprint with the given anomaly.
sort_id, sort_created, sort_weight, sort_anomalous_records_count "asc" \| "desc" Sort options.
Example request
curl -X GET "https://your-instance.qualytics.io/api/anomalies?status=Active&assignee=42&tag=High&sort_weight=desc" \
  -H "Authorization: Bearer YOUR_TOKEN"

Returns active anomalies assigned to user 42 with the tag High, sorted by severity descending.

Get a single anomaly

Endpoint: GET /api/anomalies/{id}

{id} accepts either the numeric ID or the UUID. Pass include_deleted=true to surface anomalies that have been soft-deleted.

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

Get failed checks for an anomaly

Returns the list of checks that produced the anomaly, with the message text and the rule type for each.

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

Update

Update a single anomaly

Updates the writable fields of an anomaly. Only the fields you include in the body are changed; omitted fields are left untouched.

Endpoint: PUT /api/anomalies/{id}

Permission: Member role + Author team permission on the anomaly's datastore. This covers status, tags, assignee, and description updates.

Body fields:

Field Type Description
status "Active" \| "Acknowledged" Change the open-state of the anomaly. Setting "Acknowledged" on an archived anomaly restores it to the Acknowledged state (direct restore to Active is not allowed). Use the delete endpoint to archive.
description string User-editable business description.
tags string[] Tag names. Replaces the current set; pass [] to clear.
assignee_ids int[] User IDs assigned to the anomaly. Replaces the current set; pass [] to unassign everyone. Each user must have at least the Viewer team permission on the datastore.
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]
  }'

Bulk update anomalies

Applies the same changes to many anomalies in one call.

Endpoint: PATCH /api/anomalies

Permission: Member role + Author team permission on each anomaly's datastore. This covers status, tags, assignee, and description updates.

Body: A list of bulk-update entries, each with the anomaly identifier plus the fields to change. Per-entry fields match the single-update body, with the addition of:

Field Type Description
id int or UUID The anomaly to update.
Example
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": [] }
  ]'

Manage assignees

Assignees are managed via the standard update endpoints. The assignee_ids field is replace-only, so pass the full target set on every write rather than the changes you want to apply.

Endpoints:

  • PUT /api/anomalies/{id} with assignee_ids in the body for a single anomaly.
  • PATCH /api/anomalies with assignee_ids per entry for many anomalies.
  • GET /api/anomalies?assignee=<id> to filter the list by assignee.

Behavior:

  • Each user passed in assignee_ids must have at least the Viewer team permission on the anomaly's datastore. Users without access are rejected.
  • Pass assignee_ids: [] to unassign everyone.
  • To add a user to an existing set, read assignees first and submit the union. The API does not support append.
  • Auto-assignment from a check's default assignee runs only at anomaly creation and is not reachable through this endpoint. See Deep Dive · Anomaly Assignees · Inheritance.
Set or replace the assignee list
curl -X PUT "https://your-instance.qualytics.io/api/anomalies/12345" \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "assignee_ids": [42, 57] }'
Clear all assignees
curl -X PUT "https://your-instance.qualytics.io/api/anomalies/12345" \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "assignee_ids": [] }'
Bulk-replace assignees across many anomalies
curl -X PATCH "https://your-instance.qualytics.io/api/anomalies" \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '[
    { "id": 12345, "assignee_ids": [42, 57] },
    { "id": 12346, "assignee_ids": [42] },
    { "id": 12347, "assignee_ids": [] }
  ]'

See Deep Dive · Anomaly Assignees for inheritance rules, notification behavior, and history tracking.

Archive and delete

The single-anomaly endpoint covers both the archive flow (mark with a resolution status and keep for audit) and the hard-delete flow (remove the record entirely). Bulk versions follow the same pattern.

Archive or delete a single anomaly

Endpoint: DELETE /api/anomalies/{id}

Permission: Member role + Author team permission on the anomaly's datastore.

Query parameters:

Parameter Type Description
status "Resolved" \| "Duplicate" \| "Invalid" \| "Discarded" The archived status to apply. Required when archive=true.
archive bool true (default) archives the anomaly with the resolution status; false permanently deletes the anomaly. The hard-delete is irreversible.

Body (optional): Lets you attach a comment and tag teammates when archiving.

Field Type Description
status ArchivedAnomalyStatusType Same as the query parameter; either may be used.
comment string Comment posted to the anomaly's history when archiving.
mentioned_user_ids int[] Users to notify via @mention in the comment.
Example request: archive with a resolution 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]
  }'
Example request: hard-delete an archived anomaly
curl -X DELETE "https://your-instance.qualytics.io/api/anomalies/12345?archive=false" \
  -H "Authorization: Bearer YOUR_TOKEN"

Bulk archive or delete

Endpoint: DELETE /api/anomalies

Permission: Member role + Author team permission on each anomaly's datastore.

Body: A list of entries combining the anomaly ID with the same fields used in the single-anomaly delete (status, comment, mentioned_user_ids, archive).

Example
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 }
  ]'

Timeline and comments

Get the anomaly history

Returns the paginated change log behind the anomaly's Timeline section: status changes, description edits, tag updates, assignee changes, and external ticket links and unlinks. Each entry carries the user who made the change and a timestamp.

Endpoint: GET /api/anomalies/{id}/history

Query parameters:

Parameter Type Description
page int (default 1) Page number to return, starting at 1.
size int (default 50, max 100) Number of entries per page.
include_deleted bool (default false) When true, also returns history for soft-deleted anomalies.
Example 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
}

Each changeset field inside items[].changeset is a [before, after] tuple, so you can reconstruct the audit trail offline. The transaction object on each item carries the id, the issued_at timestamp, and the user who made the change. The operation field reports whether the entry is an insert, update, or delete. To load successive windows, increment page until it reaches pages.

Get comments

Returns the comments on an anomaly, newest first, as a plain array rather than a paginated envelope. Replies are returned together with the comment that starts their thread, and a comment that was deleted while it still had replies is returned with an empty message and its mentions cleared.

Endpoint: GET /api/anomalies/{id}/comments

Permission: Reporter team permission on the anomaly's datastore or above.

Query parameters:

Parameter Type Description
start_date datetime (ISO 8601, inclusive) Lower bound on created. Applies to comments that are not anchored to a Timeline entry.
end_date datetime (ISO 8601, exclusive) Upper bound on created. Lets you fetch back-to-back ranges without overlap.
transaction_id int (repeatable) Returns the comments anchored to these Timeline entries, regardless of the date window.

Each comment carries the following fields alongside its message and created timestamp:

Field Type Description
entity_type string The kind of asset the comment belongs to: anomaly, container, quality_check, or partition.
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.

Response change

Comment responses now identify the asset with entity_type and entity_id. The previous anomaly_id and partition_id fields are no longer returned by GET /api/anomalies/{id}/comments, GET /api/comments, or GET /api/comments/{id}. Update any integration that reads those fields. Requests are unaffected: the previous anomaly_id and partition_id payload shapes are still accepted when creating comments.

Post a comment

Endpoint: POST /api/comments

Permission: Viewer team permission on the anomaly's datastore or above.

Body fields:

Field Type Description
entity_type string anomaly, container, quality_check (also used for check templates), or partition. Partitions have no Timeline, so their comments cannot use transaction_id.
entity_id int Identifier of the asset.
message string Comment text. Use @[Full Name](user_id) to mention a user.
transaction_id int (optional) Anchors the comment to a Timeline entry of that same asset.
parent_id int (optional) Posts the comment as a reply to an existing comment on that same asset.
mentioned_user_ids array[int] (optional) Users to notify. Must be active users.
Example
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]
  }'

Use PUT /api/comments/{id} to update a comment (author only) and DELETE /api/comments/{id} to delete it (author, or any user with the Admin role). Deleting a comment that still has replies keeps it as a placeholder so the replies survive; a comment with no replies is removed outright. Deleting the last remaining reply also removes the placeholder it hung from, so placeholders do not pile up in the thread.

The create and update responses carry the comment's message, author, and mentions only; to read entity_type, entity_id, transaction_id, parent_id, or deleted_at back, fetch the comment again through one of the GET endpoints.

Rejected writes come back with a status code that tells you 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.

Reading comments on an archived anomaly still works. Only writes are blocked while it stays archived.

The same comment endpoints serve containers, quality checks, and check templates. Container comments are read through GET /api/containers/{id}/comments. A check template is a quality check for commenting purposes, so both quality checks and check templates are read through GET /api/quality-checks/{id}/comments and posted with entity_type: quality_check. Both endpoints accept the same query parameters as the anomaly endpoint above. A check template does not belong to a datastore, so its comments are not gated by team permissions: any user with the Member role can read and post them. The paginated GET /api/comments feed spans every asset type only for users with the Admin role; for everyone else it returns anomaly comments alone.

Source records

Source records are the raw rows from the source data that contributed to a record anomaly. Two formats are available: JSON for inline display and CSV for export.

Get source records as JSON

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

Query parameters:

Parameter Type Description
limit int (≥1, default 10) Maximum number of rows returned.
include_masked bool (default false) When true, returns raw values for masked fields. Requires the Editor team permission on the container, and the platform writes an audit-log entry naming the masked fields you accessed.

Download source records as CSV

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

Streams a CSV file (source_records_<id>.csv). Same query parameters as the JSON endpoint.

Note

Source records are cached for up to 8 hours. If you need fresher data, see the Refresh Source Records section.

Tickets

Manage the link between an anomaly and an external ticket (Jira, ServiceNow). Linking does not call the external system; it just records the association so the UI can render the linked ticket.

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

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

Permission: Manager role + Author team permission on the anomaly's datastore.

Body fields:

Field Type Description
integration_id int The ticketing integration that owns this ticket.
ticket_id string External ticket identifier (e.g., the ServiceNow sys_id).
ticket_number string Human-readable ticket number (e.g., INC0010001).
ticket_url string (optional) Direct URL to the ticket.
status string Current external status of the ticket.
ticket_metadata object (optional) Additional metadata to store alongside the link.
Example
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"
  }'

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

Permission: Manager role + Author team permission on the anomaly's datastore. Removes the association only; the external ticket is left untouched.