# Unique Check API

Source: https://userguide.qualytics.io/data-quality-checks/unique/api/

This page documents how to create and manage a Unique check via the REST API. The check uses the standard Quality Checks endpoints: set `rule` to `unique` and list the field, or the fields forming the composite key, under `fields`. All endpoints share the base URL of your deployment (for example, `https://your-instance.qualytics.io/api`) and require a Bearer token.

> **Tip**
>
> For complete API documentation, including request and response schemas, visit the [API docs](https://userguide.qualytics.io/api/docs).

> **Note: Permissions**
>
> All endpoints require the **Member** role plus a team permission on the check's datastore: **Reporter** to read, **Author** to create or update an Active check, archive, or delete, and **Drafter** to create or update a Draft. See [Permissions](https://userguide.qualytics.io/data-quality-checks/unique/deep-dive/permissions/) for the full matrix.

## Create

**Endpoint:** `POST /api/quality-checks`

**Permission:** Member role + **Author** team permission on the container's datastore (**Drafter** when `status` is `"Draft"`).

> **Example: Create a Unique check (every accepted field)**
>
> ```bash
> curl -X POST "https://your-instance.qualytics.io/api/quality-checks" \
>   -H "Authorization: Bearer YOUR_TOKEN" \
>   -H "Content-Type: application/json" \
>   -d '{
>     "description": "Ensure that each combination of C_NAME and C_ADDRESS in the CUSTOMER table is unique",
>     "rule": "unique",
>     "fields": ["C_NAME", "C_ADDRESS"],
>     "container_id": 145,
>     "coverage": 1,
>     "filter": null,
>     "properties": null,
>     "tags": ["primary-key", "pii"],
>     "additional_metadata": {"jira": "DATA-1234"},
>     "template_id": null,
>     "status": "Active",
>     "owner_id": 7,
>     "default_anomaly_assignee_id": 12
>   }'
> ```

### Field notes

| Field | Required | Notes |
| --- | --- | --- |
| `description` | Yes | Free-text description shown in the UI. |
| `rule` | Yes | Must be `"unique"`. |
| `fields` | Yes | Array of field names. One entry for a simple key; two or more for a composite key, where uniqueness applies to the combination. Every field type is accepted. |
| `container_id` | Yes | ID of the container (table or file) the check runs against. |
| `coverage` | No | Fractional value between `0` and `1` that defines the minimum fraction of evaluated rows that must pass. Defaults to `1`, which requires every row to pass. |
| `filter` | No | SQL `WHERE` expression. Applied **before** the comparison, so only filtered rows are evaluated. Send `null` for no filter. |
| `properties` | No | Unique has no rule-specific properties. Send `null` or omit. On an array field the check compares whole arrays across rows; `{"is_element_context": true}` is refused, because Unique does not check the elements within an array. |
| `tags` | No | List of tag names applied to the check for filtering and organization. |
| `additional_metadata` | No | Free-form key-value pairs (typically links to catalog entries, tickets, governance records). |
| `anomaly_message_field` | No | Ignored by Unique. The field replaces the message on Record Anomalies, and Unique emits a Shape Anomaly only. Send `null`. |
| `template_id` | No | ID of a Check Template to associate the check with. `null` if not using a template. |
| `status` | No | `"Active"` (default) or `"Draft"`. Draft checks are not evaluated by Scans. |
| `owner_id` | No | ID of the user who owns the check. Defaults to the user creating the check when omitted. |
| `default_anomaly_assignee_id` | No | ID of the user automatically assigned to anomalies produced by the check. |

One payload field exists for other rule types but does not apply here: `anomaly_message_field` is ignored, because Unique emits a Shape Anomaly only and Shape Anomalies always use the fixed message.

## Read

**Endpoint:** `GET /api/quality-checks/{id}`

**Permission:** Member role + **Reporter** team permission on the check's datastore.

> **Example: Retrieve a check by ID**
>
> ```bash
> curl -X GET "https://your-instance.qualytics.io/api/quality-checks/101" \
>   -H "Authorization: Bearer YOUR_TOKEN"
> ```

## Update

**Endpoint:** `PUT /api/quality-checks/{id}`

**Permission:** Member role + **Author** team permission on the check's datastore (**Drafter** when the check stays Draft).

The `PUT` endpoint requires `description`, plus `fields` unless the request also sends `"status": "Draft"` (this also makes the check a Draft; a Draft update that omits `fields` keeps the stored ones). Include the check's current values (or new ones), along with anything else you want to change. A `PUT` also replaces `properties` with what you send. A check saved with `{"is_element_context": true}` before Unique stopped offering element context keeps that value when you resend it, but it is not asserted on scans; send `null` to clear it.

> **Example: Change the composite key on an existing check**
>
> ```bash
> curl -X PUT "https://your-instance.qualytics.io/api/quality-checks/101" \
>   -H "Authorization: Bearer YOUR_TOKEN" \
>   -H "Content-Type: application/json" \
>   -d '{
>     "description": "Ensure that each combination of C_NAME, C_ADDRESS, and C_NATIONKEY is unique",
>     "fields": ["C_NAME", "C_ADDRESS", "C_NATIONKEY"]
>   }'
> ```

A `PUT` can update most fields on an existing Unique check. Three values stay immutable: the rule type, the container the check is attached to, and the associated Check Template. If any of these needs to change, delete the check and create a new one.

> **Info: Editable vs Immutable on PUT**
>
> **Editable**
>
> - `description`
> - `coverage`
> - `filter`
> - `tags`
> - `additional_metadata`
> - `fields` (the field or composite key)
> - `status`
> - `owner_id`
> - `default_anomaly_assignee_id`
>
> **Immutable**
>
> - `rule`
> - `container_id`
> - `template_id`

## Delete

**Endpoint:** `DELETE /api/quality-checks/{id}`

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

Three query parameters control what happens, mirroring the two-stage flow in the UI:

| Parameter | Default | Description |
| --- | --- | --- |
| `archive` | `true` | When `true`, the check is archived and can be restored later. When `false`, the check is permanently deleted. |
| `status` | `Discarded` | The archive resolution: `Discarded` (no longer relevant) or `Invalid` (wrong and suppressed from future AI generation). Only used when `archive=true`. |
| `delete_anomalies` | `true` | Whether to permanently delete the anomalies the check produced. A permanent delete (`archive=false`) always removes them. |

> **Example: Archive a check, keeping its anomalies**
>
> ```bash
> curl -X DELETE "https://your-instance.qualytics.io/api/quality-checks/101?archive=true&status=Discarded&delete_anomalies=false" \
>   -H "Authorization: Bearer YOUR_TOKEN"
> ```

> **Example: Delete a check permanently**
>
> ```bash
> curl -X DELETE "https://your-instance.qualytics.io/api/quality-checks/101?archive=false" \
>   -H "Authorization: Bearer YOUR_TOKEN"
> ```

## Error responses

| Status | Description |
| --- | --- |
| `400 Bad Request` | A business-logic conflict prevents the operation (for example, changing properties while the check is archived, or switching an archived check between `Discarded` and `Invalid`). |
| `401 Unauthorized` | Missing or invalid API token. |
| `403 Forbidden` | The caller lacks the required team permission on the check's datastore. |
| `404 Not Found` | The check ID does not exist, or `fields` references a column that is not on the container. |
| `422 Unprocessable Entity` | The payload fails schema validation (for example, `description` or `fields` missing on `PUT`), or sets `is_element_context` (`Check type 'unique' does not support element context`). |

*Last updated: November 10, 2026.*
