Skip to content

Computed Join API

The Computed Join API is exposed through the /containers endpoints. All paths in this page are relative to https://<your-instance>.qualytics.io/api. Authentication uses a Bearer token in the Authorization header.

Complete API Reference

For the full interactive API schema with all request and response fields, see demo.qualytics.io/api/docs. Replace <YOUR_API_TOKEN> in the examples below with a personal access token from your user profile.

Permissions

To create, edit, or delete a Computed Join you need:

  • Editor on the parent datastore, or Author on the parent datastore plus ownership of the Computed Join.
  • At least Viewer on every other datastore an input reads from.

The parent datastore is the left container's datastore in pairwise mode, or the first source's datastore in SQL mode. Reading requires at least Reporter on the parent datastore. Permission on the other input datastores is checked only when creating or editing the join.

Schema

A Computed Join has two mutually exclusive shapes. Pairwise mode uses the left_* and right_* fields plus select_clause. SQL mode uses sources and query instead, and must omit every pairwise field. The container has the following fields:

Field Type Required on create Description
name string Yes Unique name on the parent datastore (max 255 chars)
container_type string Yes Always "computed_join"
query string SQL mode Spark SQL query over the source aliases. Its presence selects SQL mode; when set, all pairwise fields must be omitted.
sources array SQL mode Two or more aliased input containers, each { "container_id": <int>, "alias": "<name>", "where_clause": "<optional Spark SQL WHERE>" }. Aliases must be unique, valid identifiers. Up to a configurable maximum (10 by default). The join is created under the first source's datastore.
left_container_id integer Pairwise ID of the left input container
right_container_id integer Pairwise ID of the right input container
left_join_field_name string Pairwise Field name to join on (left side, max 255 chars)
right_join_field_name string Pairwise Field name to join on (right side, max 255 chars)
left_prefix string No (default "left") Pairwise only. Prefix applied to columns from the left side (max 100 chars)
right_prefix string No (default "right") Pairwise only. Prefix applied to columns from the right side (max 100 chars)
join_type string No (default "inner") Pairwise only. One of "inner", "left", "right", "outer". The API also accepts "left_semi" and "left_anti", but the UI does not expose them.
select_clause string Pairwise SQL Select Expression (pairwise only)
where_clause string No Pairwise only. SQL Filter Clause (ignored in SQL mode)
group_by_clause string No Pairwise only. SQL Group By Clause (ignored in SQL mode)
description string No Free-text description (max 255 chars)
owner_id integer No ID of the owning user. Defaults to the user who creates the join. Authors can omit owner_id or set it to their own user ID only. Editors can set it to any user on Create, and only Editors can transfer ownership on Update.
additional_metadata object No Free-form key-value metadata stored alongside the container.
force_drop_fields boolean No PUT only. Set to true to bypass the field-drop conflict when an edit removes fields with active quality checks or anomalies. Ignored on POST (the field-drop conflict cannot arise during create). See Update a Computed Join.

Note

In pairwise mode, unlike the UI, the API does not auto-generate the Select Expression; you must provide select_clause explicitly on every create. In SQL mode there is no Select Expression: the query defines the output columns.

Create a Computed Join

Endpoint: POST /api/containers

Permission: Editor on the parent datastore, or Author with owner_id set to your own ID (or omitted).

Expected response: 200 OK with the created container.

cURL
curl -X POST https://your-instance.qualytics.io/api/containers \
  -H "Authorization: Bearer <YOUR_API_TOKEN>" \
  -H "Content-Type: application/json" \
  -d '{
    "container_type": "computed_join",
    "name": "customer_orders_join",
    "left_container_id": 101,
    "right_container_id": 202,
    "left_join_field_name": "customer_id",
    "right_join_field_name": "customer_id",
    "left_prefix": "cust",
    "right_prefix": "order",
    "join_type": "left",
    "select_clause": "cust_customer_id, cust_name, cust_city, order_order_id, order_product"
  }'
cURL (SQL mode)
curl -X POST https://your-instance.qualytics.io/api/containers \
  -H "Authorization: Bearer <YOUR_API_TOKEN>" \
  -H "Content-Type: application/json" \
  -d '{
    "container_type": "computed_join",
    "name": "customer_order_summary",
    "sources": [
      { "container_id": 101, "alias": "orders", "where_clause": "order_date >= '\''2026-01-01'\''" },
      { "container_id": 202, "alias": "customers" }
    ],
    "query": "SELECT c.customer_id, c.name, COUNT(o.order_id) AS order_count FROM customers c JOIN orders o ON o.customer_id = c.customer_id GROUP BY c.customer_id, c.name"
  }'

Validate a Computed Join

Endpoint: POST /api/containers/validate

Permission: Same as Create.

Use this endpoint to test the join configuration without persisting it. On success the endpoint returns no body; validation errors are returned as standard error responses.

Expected response: 204 No Content (nothing is persisted).

Query parameters:

Name Type Default Description
timeout_seconds integer 150 Maximum seconds to wait for the synchronous validation step. Minimum 30, maximum 300.

The Validate endpoint expects the container payload wrapped in {"container": {...}, "container_id": <existing-id-or-null>}. Pass container_id when validating an edit to an existing Computed Join; omit it (or send null) when validating a new one. For a SQL-mode join, the wrapped container object carries sources and query instead of the pairwise fields.

cURL
curl -X POST https://your-instance.qualytics.io/api/containers/validate \
  -H "Authorization: Bearer <YOUR_API_TOKEN>" \
  -H "Content-Type: application/json" \
  -d '{
    "container": {
      "container_type": "computed_join",
      "name": "customer_orders_join",
      "left_container_id": 101,
      "right_container_id": 202,
      "left_join_field_name": "customer_id",
      "right_join_field_name": "customer_id",
      "left_prefix": "cust",
      "right_prefix": "order",
      "join_type": "left",
      "select_clause": "cust_customer_id, cust_name, cust_city, order_order_id, order_product"
    },
    "container_id": null
  }'

Update a Computed Join

Endpoint: PUT /api/containers/{id}

Permission: Editor on the parent datastore, or Author who is the current owner. Editors do not need to own the join to update it. Only an Editor can reassign owner_id to another user; an Author cannot transfer ownership.

Updates the editable fields of the Computed Join. PUT requires container_type and name on every call (even when neither is changing); other fields are optional and unchanged when omitted. In pairwise mode, the left and right container_id are immutable after creation; to change them, delete and recreate the join, and sending left_container_id or right_container_id in an update body is silently ignored. In SQL mode, sending query (with its sources) replaces the query and the full set of aliased sources; the container-level where_clause and group_by_clause are ignored in SQL mode. Switching a pairwise join to SQL mode is supported by sending query and sources; switching a SQL-mode join back to pairwise is not supported through update. Sending an empty or whitespace-only description clears any existing description.

Note

PUT also accepts an incremental_identifier object to configure incremental scans on the joined output. The object has identifier_type (one of "last-modified", "batch-value", "postgresql") and an optional field_name.

cURL
curl -X PUT https://your-instance.qualytics.io/api/containers/123 \
  -H "Authorization: Bearer <YOUR_API_TOKEN>" \
  -H "Content-Type: application/json" \
  -d '{
    "container_type": "computed_join",
    "name": "customer_orders_join",
    "select_clause": "cust_customer_id, cust_name, order_product",
    "where_clause": "order_product IS NOT NULL"
  }'

If the new Select Expression (pairwise) or query (SQL mode) drops fields that have active quality checks or anomalies, the request returns 409 Conflict with a list of affected fields. Re-submit with "force_drop_fields": true in the request body to apply the change; the dropped fields are marked missing and their checks and anomalies are preserved until the fields reappear (see How It Works).

cURL with force_drop_fields
curl -X PUT https://your-instance.qualytics.io/api/containers/123 \
  -H "Authorization: Bearer <YOUR_API_TOKEN>" \
  -H "Content-Type: application/json" \
  -d '{
    "container_type": "computed_join",
    "name": "customer_orders_join",
    "select_clause": "cust_customer_id, cust_name",
    "force_drop_fields": true
  }'

Get a Computed Join

Endpoint: GET /api/containers/{id}

Permission: Reporter (or higher) on the datastore.

cURL
curl -X GET https://your-instance.qualytics.io/api/containers/123 \
  -H "Authorization: Bearer <YOUR_API_TOKEN>"

List Computed Joins

Endpoint: GET /api/containers

Permission: Returns containers on datastores where you have at least Reporter.

Returns containers visible to the caller. Filter by container_type=computed_join to list only Computed Joins, optionally scoped to one or more datastores with datastore.

Query parameters:

Name Type Description
container_type string Filter by container type. Use computed_join to list only Computed Joins. Repeat the parameter to filter by multiple types.
datastore integer Filter by parent datastore ID. Repeat the parameter (?datastore=42&datastore=43) to filter by multiple datastores.
name string Case-insensitive name search.
page integer 1-based page number.
size integer Page size (max 100).
cURL
curl -X GET "https://your-instance.qualytics.io/api/containers?container_type=computed_join&datastore=42" \
  -H "Authorization: Bearer <YOUR_API_TOKEN>"

Delete a Computed Join

Endpoint: DELETE /api/containers/{id}

Permission: Editor on the parent datastore, or Author who is the current owner. Editors do not need to own the join to delete it.

Expected response: 204 No Content on success.

Deletes the Computed Join and all its quality checks, anomalies, profile data, and derived computed fields. The operation is irreversible.

If the Computed Join is referenced by quality checks elsewhere, the delete fails with 409 Conflict.

cURL
curl -X DELETE https://your-instance.qualytics.io/api/containers/123 \
  -H "Authorization: Bearer <YOUR_API_TOKEN>"

Error responses

Status Trigger
400 Pairwise: DISTINCT used in the Select Expression; join field not found in the left or right container; incompatible join field types. SQL mode: fewer than 2 sources or more than the maximum; a source alias is not a valid identifier or is duplicated
401 Missing, invalid, or expired Bearer token
403 Caller lacks the required permission for the action
404 The container {id} in the path does not exist (Get, Update, Delete); a referenced input container does not exist (Create, Validate) - left_container_id/right_container_id in pairwise mode, or a sources[].container_id in SQL mode
408 The synchronous validation step timed out. On POST /containers/validate you can retry with a higher timeout_seconds (up to 300). On Create and Update, the timeout is fixed by the deployment; retry the request, or narrow each input with a filtered Computed Table or Computed File (pairwise) or a per-source Filter Clause (SQL mode).
409 Duplicate container name on the same datastore; an input container is a Computed Join (not supported, both modes); an input container has not been profiled; dropping fields with dependent checks or anomalies (use force_drop_fields=true); referenced by quality checks (delete only)
422 Request body validation failure (missing required field, field exceeds max length); SQL parse error returned by the synchronous validation. Pairwise: multi-statement SQL, unresolved column in Select or Where, GROUP BY validation failure, duplicate column names after prefixing. SQL mode: query references an unknown alias or column, uses a qualified or catalog table name, or calls a disallowed function such as reflect or java_method