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, parsed_stored_code, 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": false
},
{
"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": false,
"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 are true only when that container has connections in that direction that are not already in the response. A true flag marks a node you can expand by querying it directly or with a larger depth. A false flag means every container it connects to in that direction is already in the response, not that every edge is. A one-sided query (side=upstream or side=downstream) leaves out edges between two returned containers that are not on the traversal path, so query without side to get those edges too.
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 |