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):
403 Forbidden (add-on disabled by admin):
404 Not Found:
409 Conflict:
422 Unprocessable Entity (mixed granularity):
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 |