# Computed Tables API

Source: https://userguide.qualytics.io/container/computed-tables/api/

Programmatic access to Computed Tables uses the generic **Container** endpoints on the Qualytics REST API. The requests below cover creation, update, deletion, validation, and the Timeline with its comments. Requests share the same base URL and Bearer token authentication as the rest of the platform API.

- **Base URL.** Your deployment's platform host (for example, `https://<your-tenant>.qualytics.io/api`).
- **Auth.** Bearer token in the `Authorization` header. See [Access Tokens](https://userguide.qualytics.io/settings/access-tokens/getting-started/) to create one.

> **Tip: Complete API Reference**
>
> The endpoints below are illustrative. For the full request and response schemas, live examples, and every field that each endpoint accepts, see the interactive API reference at [demo.qualytics.io/api/docs](https://demo.qualytics.io/api/docs).

> **Note: Permissions**
>
> - **Create, edit, delete**: requires the **Editor** team permission on the parent datastore, or the **Author** team permission when the user is the current owner (for Edit and Delete) or will be its owner (for Create, when `owner_id` is omitted or set to the caller's own user id).
> - **View, list, history**: requires the **Reporter** team permission or above.
> - **Validate**: requires the **Editor** team permission on the datastore, or **Author** when the caller will be the owner of the Computed Table.
> - **Reassign owner**: requires the **Editor** team permission.
> - **Comments**: reading requires the **Reporter** team permission; posting and replying require the **Viewer** team permission. Only the author can edit their own comment, and deleting another user's comment requires the **Admin** role.

## Payload Shape

The Computed Table is a specialization of the generic Container payload. Common fields:

| Property | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `container_type` | string | Yes | - | Discriminator identifying the container as a Computed Table. Set to the literal `"computed_table"`. |
| `datastore_id` | integer | Yes (Create) | - | The JDBC or native source datastore the Computed Table lives in. Set at creation and cannot be changed on Update. |
| `name` | string | Yes | - | Max 255 characters. Spaces are replaced with underscores. Must be unique inside the datastore. |
| `query` | string | Yes | - | The SQL body. Must be valid in the parent datastore's dialect and reference only base containers (physical tables and views) inside the same datastore. |
| `description` | string | No | `null` | Max 8000 characters. |
| `owner_id` | integer | No | Caller's user id | Reassignment (on Update) requires the **Editor** team permission. |
| `additional_metadata` | object | No | `null` | Freeform key-value pairs. Keys also act as default values for [runtime variables](https://userguide.qualytics.io/operations/scan/how-tos/use-runtime-variables/) in this container's checks. |
| `force_drop_fields` | boolean | No (Update) | `false` | Set to `true` to save an update whose new query drops fields with attached quality checks or anomalies. |
| `incremental_field_name` | string | No (Update) | `null` | Enables incremental scanning on the specified field. Profiles still read every row. |
| `incremental_identifier_type` | enum | No (Update) | `null` | One of `"last-modified"` or `"batch-value"`. Used with `incremental_field_name`. |

## Create

**Endpoint:** `POST /containers`

**Permission:** **Editor** team permission on the datastore, or **Author** creating a Computed Table they will own.

> **Example: Create with curl**
>
> ```bash
> curl -X POST "https://<your-tenant>.qualytics.io/api/containers" \
>   -H "Authorization: Bearer $QUALYTICS_TOKEN" \
>   -H "Content-Type: application/json" \
>   -d '{
>     "container_type": "computed_table",
>     "datastore_id": 12,
>     "name": "customer_order_statistics",
>     "query": "SELECT o.customer_id, COUNT(*) AS order_count, SUM(o.total) AS total_amount FROM public.orders o WHERE o.status = '\''placed'\'' GROUP BY o.customer_id",
>     "description": "Order counts and totals per customer for placed orders.",
>     "owner_id": 42
>   }'
> ```

Returns the created Container object with its assigned `id`. Qualytics runs a basic profile that samples up to 1000 records per partition immediately after save.

## Update

**Endpoint:** `PUT /containers/{id}`

**Permission:** **Editor** team permission on the datastore, or **Author** owner of the Computed Table.

> **Example: Update with curl**
>
> ```bash
> curl -X PUT "https://<your-tenant>.qualytics.io/api/containers/34" \
>   -H "Authorization: Bearer $QUALYTICS_TOKEN" \
>   -H "Content-Type: application/json" \
>   -d '{
>     "container_type": "computed_table",
>     "name": "customer_order_statistics",
>     "query": "SELECT o.customer_id, COUNT(*) AS order_count, SUM(o.total) AS total_amount FROM public.orders o WHERE o.status = '\''placed'\'' AND o.year = 2026 GROUP BY o.customer_id",
>     "description": "Order counts and totals per customer for placed orders in the current year.",
>     "owner_id": 42
>   }'
> ```

A field left out of the body keeps its saved value, so an update that only renames the Computed Table or changes its description keeps its incremental strategy. Send `incremental_field_name` and `incremental_identifier_type` as `null` to clear the strategy.

The `datastore_id` cannot be changed. Changing `owner_id` (reassigning ownership) requires the **Editor** team permission; Authors can edit Computed Tables they own but cannot reassign them.

If the new query drops a field that has attached quality checks or anomalies, the update returns `409 Conflict` with a `dropped_fields` payload. Add `force_drop_fields: true` to the body to save anyway; the affected fields are marked as missing while their checks and anomalies are preserved.

Editing the query causes Qualytics to re-profile the Computed Table immediately after save: a synchronous slim profile refreshes field statistics right away, and a full asynchronous profile follows.

## Delete

**Endpoint:** `DELETE /containers/{id}`

**Permission:** **Editor** team permission on the datastore, or **Author** owner of the Computed Table.

> **Example: Delete with curl**
>
> ```bash
> curl -X DELETE "https://<your-tenant>.qualytics.io/api/containers/34" \
>   -H "Authorization: Bearer $QUALYTICS_TOKEN"
> ```

Returns `204 No Content` on success. The container, its quality checks, anomalies, profile history, and scan history are removed. The delete is blocked with `409 Conflict` when quality checks in other containers reference this Computed Table (for example, an `existsIn` or `dataDiff` check pointing at it). Delete or repoint those checks first. Archived checks normally do not block the delete, and an archived check that references this Computed Table is permanently deleted together with the container. If an archived check is still listed in the `409` error, delete it first.

## Validate a Query Before Save

**Endpoint:** `POST /containers/validate`

**Permission:** **Editor** team permission on the datastore, or **Author** when the caller will be the owner of the Computed Table (Hybrid Gate). Validate uses the same permission check as Create.

Validates the SQL without persisting anything. The request body carries the same payload you would send to Create or Update, wrapped in a `container` object:

> **Example: Validate with curl**
>
> ```bash
> curl -X POST "https://<your-tenant>.qualytics.io/api/containers/validate" \
>   -H "Authorization: Bearer $QUALYTICS_TOKEN" \
>   -H "Content-Type: application/json" \
>   -d '{
>     "container": {
>       "container_type": "computed_table",
>       "datastore_id": 12,
>       "name": "customer_order_statistics",
>       "query": "SELECT o.customer_id, COUNT(*) AS order_count FROM public.orders o GROUP BY o.customer_id"
>     }
>   }'
> ```

Returns `204 No Content` on success. On failure, the response body carries a `detail` field with the underlying error message from the warehouse.

## Timeline

**Endpoint:** `GET /containers/{id}/history`

**Permission:** **Reporter** team permission on the datastore or above.

Returns the change log behind the container's **Timeline** section, including the query text and metadata as they were at each revision. Use this to review previous versions of the SQL or attribute changes to specific users.

## Timeline comments

**Endpoint:** `GET /containers/{id}/comments`

**Permission:** **Reporter** team permission on the datastore or above.

Returns the comments posted on the container's Timeline, newest first. Accepts `start_date` and `end_date` to bound comments that are not anchored to a change, and a repeatable `transaction_id` to fetch the comments anchored to specific Timeline entries.

Comments are created with `POST /comments` (**Viewer** team permission or above), updated with `PUT /comments/{id}` (author only), and deleted with `DELETE /comments/{id}` (author, or any user with the **Admin** role). See [Anomalies API](https://userguide.qualytics.io/anomalies/api/#list-an-anomalys-comments) for the payload fields, which are the same for every asset type.

## Read

**Endpoint:** `GET /containers/{id}` for a single container, `GET /containers?container_type=computed_table&datastore={id}` to list Computed Tables inside a datastore.

**Permission:** **Reporter** team permission on the datastore or above.

## Error Responses

| Status | Meaning |
| --- | --- |
| `400 Bad Request` | Payload validation failed: missing required field or query syntactically invalid. |
| `403 Forbidden` | Caller lacks the required team permission for the operation. |
| `404 Not Found` | Datastore or container does not exist, or is not visible to the caller. |
| `409 Conflict` | The requested change is blocked by the current state, most often: a Computed Table with the same name already exists in the datastore, the update would drop fields that have attached quality checks or anomalies (use `force_drop_fields: true` to confirm), or the delete is blocked by dependents. The response body carries a `detail` describing the blocker and any `dropped_fields` or dependent IDs. |
| `422 Unprocessable Entity` | Payload does not match the schema: unknown container type, wrong field types, or a required top-level field is missing. |
| `408 Request Timeout` | The warehouse did not respond within the validation window. Retry after simplifying the query or verifying the connection. |

*Last updated: November 9, 2026.*
