Skip to content

Lineage API

You can manage lineage edges and query the lineage graph programmatically using the Qualytics API.

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).

Add-on required

All lineage endpoints require the Lineage add-on to be enabled for your deployment. Requests made when the add-on is disabled return 403 Forbidden.


Create an Edge

Creates a lineage edge connecting a source and a target. Both sides must be at the same granularity: either both containers or both fields.

Endpoint: POST /api/lineage/edges

Role: Manager

Container-level edge

Request:

curl -X POST "https://your-instance.qualytics.io/api/lineage/edges" \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "source_container_id": 42,
    "target_container_id": 55,
    "source_type": "manual"
  }'

Response (200):

{
  "id": 1,
  "created": "2026-05-01T10:00:00.000000Z",
  "source_container_id": 42,
  "source_field_id": null,
  "target_container_id": 55,
  "target_field_id": null,
  "source_external_fqn": null,
  "target_external_fqn": null,
  "description": null,
  "source_type": "manual",
  "evidence": [],
  "source_container": {
    "id": 42,
    "name": "raw_orders",
    "display_name": null,
    "container_type": "table",
    "datastore_id": 10,
    "datastore_name": "Production DB"
  },
  "source_field": null,
  "target_container": {
    "id": 55,
    "name": "orders_clean",
    "display_name": null,
    "container_type": "computed_table",
    "datastore_id": 10,
    "datastore_name": "Production DB"
  },
  "target_field": null
}
Field-level edge

Request:

curl -X POST "https://your-instance.qualytics.io/api/lineage/edges" \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "source_field_id": 100,
    "target_field_id": 200,
    "source_type": "manual"
  }'

Response (200): Same structure as the container-level example, with source_field and target_field populated instead.

Auto container-edge

When creating a field-level edge with source_type: "manual" and the source and target fields belong to different containers, Qualytics automatically creates a container-level edge between those containers if one does not already exist.

Request Body:

Field Type Required Description
source_container_id integer Conditional Source container ID. Mutually exclusive with source_field_id.
source_field_id integer Conditional Source field ID. Mutually exclusive with source_container_id.
target_container_id integer Conditional Target container ID. Mutually exclusive with target_field_id.
target_field_id integer Conditional Target field ID. Mutually exclusive with target_container_id.
description string No Optional transformation note (e.g. UPPER(email)).
source_type string No How the connection was created. One of manual, catalog_import, inferred. Defaults to manual.

Same-granularity rule

Exactly one of source_container_id or source_field_id must be set, and exactly one of target_container_id or target_field_id must be set. Mixing container and field on opposite sides (e.g. source_container_id + target_field_id) is not allowed and returns 422.


Fields on Every Edge Response

Every endpoint that returns an edge returns these alongside the endpoints and the description.

Field Type Description
source_type string The value stored on the edge. manual, inferred for Qualytics managed, the data catalog's name for an imported edge, or the datastore's type for a collected one.
source_external_fqn string Set instead of source_container_id when the upstream end is an asset that is not registered in Qualytics, spelled as the source names it. null otherwise.
target_external_fqn string The same for the downstream end.
evidence array What the edge was derived from. Empty for every category except lineage collected from a source datastore, and one entry per piece of evidence behind the relationship.

An edge always has at least one endpoint resolved to a container or a field. An edge with both ends unresolved is never stored.

The Evidence Object

Field Type Description
provider string The datastore type the evidence was read from, such as snowflake.
evidence_type string What was read: structured_query_history, parsed_query_history, parsed_view_definition, or native_snapshot.
authorship string Which side computed the relationship. provider when the source's own lineage reported it, qualytics when Qualytics derived it from statement text.
source_relation string The relation it was read from, such as SNOWFLAKE.ACCOUNT_USAGE.ACCESS_HISTORY.
observed_at string When the relationship was last observed, or null.
first_observed_operation_id integer The Sync operation that first observed it.
last_observed_operation_id integer The Sync operation that last observed it.

List Edges

Returns a paginated list of lineage edges with optional filters.

Endpoint: GET /api/lineage/edges

Role: Member

Query Parameters:

Parameter Type Description
container_id integer Filter to edges where this container is the source or target.
field_id integer Filter to edges where this field is the source or target.
source_type string Filter by the value stored on the edge. An edge created by a sync carries the data catalog's name or the datastore's type rather than catalog_import. See Lineage Sources for each category's value.
page integer Page number (default: 1).
size integer Page size (default: 50).
Example request and response

Request:

curl -X GET "https://your-instance.qualytics.io/api/lineage/edges?container_id=42" \
  -H "Authorization: Bearer YOUR_API_TOKEN"

Response:

{
  "items": [
    {
      "id": 1,
      "created": "2026-05-01T10:00:00.000000Z",
      "source_container_id": 42,
      "source_field_id": null,
      "target_container_id": 55,
      "target_field_id": null,
      "description": "Raw orders → cleaned orders",
      "source_type": "manual",
      "source_container": { "id": 42, "name": "raw_orders", "display_name": null, "container_type": "table", "datastore_id": 10, "datastore_name": "Production DB" },
      "source_field": null,
      "target_container": { "id": 55, "name": "orders_clean", "display_name": null, "container_type": "computed_table", "datastore_id": 10, "datastore_name": "Production DB" },
      "target_field": null
    }
  ],
  "total": 1,
  "page": 1,
  "size": 50,
  "pages": 1
}

Get an Edge

Returns a single lineage edge by ID.

Endpoint: GET /api/lineage/edges/{id}

Role: Member

Example request and response

Request:

curl -X GET "https://your-instance.qualytics.io/api/lineage/edges/1" \
  -H "Authorization: Bearer YOUR_API_TOKEN"

Response (200): Same structure as Create an Edge.


Update an Edge

Updates the description and/or source_type of an existing edge. The source and target cannot be changed. Delete and recreate the edge if you need to rewire it.

Endpoint: PUT /api/lineage/edges/{id}

Role: Manager

Example request and response

Request:

curl -X PUT "https://your-instance.qualytics.io/api/lineage/edges/1" \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "description": "ETL pipeline v2",
    "source_type": "catalog_import"
  }'

Response (200): Returns the updated edge object.

Request Body:

Field Type Required Description
description string No New transformation description. Omit the field to leave the current value unchanged.
source_type string No Updated source type: manual, catalog_import, or inferred.

Delete an Edge

Deletes a lineage edge. This does not affect the underlying containers or fields.

Endpoint: DELETE /api/lineage/edges/{id}

Role: Manager

Example request
curl -X DELETE "https://your-instance.qualytics.io/api/lineage/edges/1" \
  -H "Authorization: Bearer YOUR_API_TOKEN"

Response: 204 No Content


Get the Lineage Graph

Returns the lineage graph for a container, including all nodes and connections up to the specified depth.

Endpoint: GET /api/lineage/graph

Role: Member

Query Parameters:

Parameter Type Required Description
container_id integer Yes The container to center the graph on.
depth integer No How many hops to include (1–20). Defaults to 2.
side string No Restrict direction: upstream, downstream, or omit for both.
Example request and response

Request:

curl -X GET "https://your-instance.qualytics.io/api/lineage/graph?container_id=55&depth=2&side=upstream" \
  -H "Authorization: Bearer YOUR_API_TOKEN"

Response:

{
  "nodes": [
    {
      "node_type": "container",
      "id": 42,
      "name": "raw_orders",
      "display_name": null,
      "container_type": "table",
      "table_type": "table",
      "datastore": { "id": 10, "name": "Production DB", "store_type": "jdbc", "type": "snowflake", "enrichment_only": false },
      "field_count": 12,
      "linked_field_count": 3,
      "is_target": false,
      "active_anomaly_count": 0,
      "purpose": null,
      "has_upstream": false,
      "has_downstream": true
    },
    {
      "node_type": "container",
      "id": 55,
      "name": "orders_clean",
      "display_name": null,
      "container_type": "computed_table",
      "table_type": null,
      "datastore": { "id": 10, "name": "Production DB", "store_type": "jdbc", "type": "snowflake", "enrichment_only": false },
      "field_count": 10,
      "linked_field_count": 3,
      "is_target": true,
      "active_anomaly_count": 2,
      "purpose": null,
      "has_upstream": true,
      "has_downstream": false
    }
  ],
  "edges": [
    {
      "id": 1,
      "source_container_id": 42,
      "source_field_id": null,
      "target_container_id": 55,
      "target_field_id": null,
      "description": "Raw orders → cleaned orders",
      "source_type": "manual",
      "evidence": []
    }
  ],
  "external_nodes": [
    {
      "node_type": "external",
      "id": "ext:snowflake:SALES.PUBLIC.ORDERS_STAGING",
      "fqn": "SALES.PUBLIC.ORDERS_STAGING",
      "source_type": "snowflake"
    }
  ],
  "external_edges": [
    {
      "id": 2,
      "source_container_id": null,
      "source_field_id": null,
      "target_container_id": 42,
      "target_field_id": null,
      "source_node_id": "ext:snowflake:SALES.PUBLIC.ORDERS_STAGING",
      "target_node_id": null,
      "description": null,
      "source_type": "snowflake",
      "evidence": [
        {
          "provider": "snowflake",
          "evidence_type": "structured_query_history",
          "authorship": "provider",
          "source_relation": "SNOWFLAKE.ACCOUNT_USAGE.ACCESS_HISTORY",
          "observed_at": "2026-09-18T02:14:00.000000Z",
          "first_observed_operation_id": 901,
          "last_observed_operation_id": 944
        }
      ]
    }
  ]
}

Graph depth and flags

The has_upstream and has_downstream flags on each node indicate whether that container has any connections in that direction anywhere in your deployment, regardless of the requested depth. A true flag on an edge node means connections may exist beyond the current result and you can re-query with a larger depth to explore them.

Assets that are not registered in Qualytics

Lineage collected from a source datastore reaches assets Qualytics does not manage. Those never appear in nodes or edges, so a client that only reads those two arrays keeps working unchanged. They arrive in external_nodes and external_edges instead.

An external node's id is the string ext:<source_type>:<fqn>, and an external edge references it through source_node_id or target_node_id rather than a container id. Exactly one end of an external edge is external, and the other is a container.


List Container Fields

Returns a paginated list of fields for a container, including whether each field has any lineage connections.

Endpoint: GET /api/lineage/containers/{container_id}/fields

Role: Member

Query Parameters:

Parameter Type Description
name string Filter fields by name (partial match).
page integer Page number (default: 1).
size integer Page size (default: 50).
Example request and response

Request:

curl -X GET "https://your-instance.qualytics.io/api/lineage/containers/55/fields" \
  -H "Authorization: Bearer YOUR_API_TOKEN"

Response:

{
  "items": [
    {
      "id": 200,
      "name": "email_upper",
      "display_name": null,
      "status": "active",
      "latest_profile_id": 301,
      "type": "string",
      "is_computed_field": true,
      "active_anomaly_count": 0,
      "has_lineage": true
    },
    {
      "id": 201,
      "name": "order_date",
      "display_name": null,
      "status": "active",
      "latest_profile_id": 302,
      "type": "date",
      "is_computed_field": false,
      "active_anomaly_count": 1,
      "has_lineage": false
    }
  ],
  "total": 10,
  "page": 1,
  "size": 50,
  "pages": 1
}

Get Field Connections

Returns all fields connected to a given field through field-level connections, along with the edges between them. Optionally restrict results to fields within specific containers.

Endpoint: GET /api/lineage/fields/{field_id}/connections

Role: Member

Query Parameters:

Parameter Type Description
container_ids list of integers Restrict results to fields within these containers. Can be repeated: ?container_ids=42&container_ids=55.
Example request and response

Request:

curl -X GET "https://your-instance.qualytics.io/api/lineage/fields/100/connections" \
  -H "Authorization: Bearer YOUR_API_TOKEN"

Response:

{
  "fields": [
    {
      "id": 200,
      "name": "email_upper",
      "display_name": null,
      "status": "active",
      "latest_profile_id": 301,
      "type": "string",
      "container_id": 55
    }
  ],
  "edges": [
    {
      "id": 2,
      "source_container_id": null,
      "source_field_id": 100,
      "target_container_id": null,
      "target_field_id": 200,
      "description": "UPPER(email)",
      "source_type": "manual"
    }
  ]
}

Error Responses

Status Code Description
401 Unauthorized Missing or invalid API token.
403 Forbidden User does not have the required role, or the Lineage add-on is not available/disabled.
404 Not Found Edge, container, or field with the specified ID does not exist.
409 Conflict An edge with the same source, target, and source_type already exists. Uniqueness is scoped to source_type, so the same pair can carry one edge per origin.
422 Unprocessable Entity Invalid request body (e.g., mixed-granularity edge, missing source/target).
Error response examples

403 Forbidden (add-on not available for deployment):

{ "detail": "'lineage' is not available for your deployment. Contact support to unlock it" }

403 Forbidden (add-on disabled by admin):

{ "detail": "'lineage' is currently disabled. Ask your admin to enable it" }

404 Not Found:

{ "detail": "Lineage edge 999 not found" }

409 Conflict:

{ "detail": "A lineage edge with the same source and target already exists" }

422 Unprocessable Entity (mixed granularity):

{
  "detail": [
    {
      "type": "value_error",
      "msg": "Value error, source and target must be at the same granularity (both containers or both fields)"
    }
  ]
}

Role Summary

Operation Minimum Role
List edges Member
Get an edge Member
Get the lineage graph Member
List container fields Member
Get field connections Member
Create an edge Manager
Update an edge Manager
Delete an edge Manager