Introduction
Manual lineage lets you add lineage that Foundational can't discover automatically. Foundational automatically builds lineage from your connected sources (dbt, Airflow, Snowflake, BigQuery, and 30+ others).
When some lineage lives outside those sources (a hand-built pipeline, a legacy system, or a vendor export), you can add it by uploading a spreadsheet (XLSX workbook) that describes your entities and the edges between them.
Foundational ingests the file through a dedicated Manual Upload connector and merges your entities and edges into the lineage graph. It supports tables, columns, dashboards, files, and warehouse tasks.
A blank template workbook (all sheets with headers) and a filled sample workbook (orders/customers/daily_revenue, with table-level and column-level lineage) are available. Ask your Foundational contact, or start from the worked example below.
How it works
① Describe your entities: Each entity type (tables, columns, dashboards, files, warehouse tasks) has its own sheet in the workbook. Each row is one entity.
② Assign ref_ids: Give each entity you need to reference elsewhere a short, unique identifier. Columns link to their parent table via parent_ref_id; edges link any two entities via source_ref_id and dest_ref_id.
③ Define edges: The edges sheet connects entities using those ref_id values, one row per lineage relationship.
④ Upload via API: POST the workbook to the Foundational API. Foundational creates (or updates) a Manual Upload connector, runs a scan, and adds your lineage to the graph.
Prepare the XLSX file
The workbook contains one sheet per entity type plus an edges sheet. The first row of every sheet is the header row. Each following row is one entity or edge.
Foundational ignores blank rows. Column order does not matter; only header names are used.
To get started, download foundational_manual_lineage_template.xlsx at the end of this article.
The ref_id system
ref_id is how rows reference each other:
Set a
ref_idon any entity you need to reference elsewhere: every table that has columns, and every entity that appears in an edge. Put a short, human-friendly value in it (e.g.orders,orders.order_id,dash_revenue).
ref_idsmust be unique across the whole workbook.
Columns link to their table via
parent_ref_id. Edges link entities viasource_ref_idanddest_ref_id. These values must exactly match aref_idyou defined.
If you omit
ref_id, the entity is still imported, but you won't be able to reference it from a column or an edge.
Tables sheet
One row per table.
Column | Required | Notes |
| Yes | The table's platform, e.g. |
| Yes | Database / catalog name |
| Yes | Schema name |
| Yes | Table name |
| No | Identifier to reference this table from columns / edges |
| No | Account / host identifier |
| No | Free text |
| No | Comma-separated, e.g. |
| No | Owner name or email |
| No | Comma-separated |
Example
|
|
|
|
|
SNOWFLAKE | analytics | public | orders | orders |
SNOWFLAKE | analytics | public | customers | customers |
Columns sheet
One row per column. Each column attaches to its parent table via parent_ref_id.
Column | Required | Notes |
| Yes | Must equal the |
| Yes | Column name |
| No | Identifier to reference this column from edges (needed for column-level lineage and any edge referencing this column) |
| No | e.g. |
| No | Same meaning as on tables |
Example
|
|
|
|
orders | order_id | integer | orders.order_id |
customers | customer_id | integer | customers.customer_id |
Dashboards sheet
One row per dashboard.
Column | Required | Notes |
| Yes | BI platform, e.g. |
| Yes | Project / workspace name |
| Yes | Dashboard name |
| No | Identifier for use in edges |
| No | Optional metadata |
Files sheet
One row per file. Foundational builds the file URI as platform://bucket/dir/name.
Column | Required | Notes |
| Yes | Storage platform, e.g. See the section Allowed values. |
| Yes | Bucket name |
| Yes | File / object name |
| No | Folder path within the bucket |
| No | Identifier for use in edges |
| No | Optional metadata |
Example (produces s3://my-bucket/raw/orders.parquet)
|
|
|
|
|
s3 | my-bucket | raw | orders.parquet | raw_orders_file |
Warehouse tasks sheet
One row per warehouse job or task (e.g., a scheduled Snowflake task).
Column | Required | Notes |
| Yes | The task's platform, e.g. See the section Allowed values. |
| Yes | Warehouse host / account identifier |
| Yes | Task name |
| No | Identifier for use in edges |
| No | Optional metadata |
Edges sheet
One row per lineage relationship. Connect two entities by their ref_id values.
Column | Required | Notes |
| Yes |
|
| Yes |
|
| No |
|
| No | Free-text expression describing the transformation. Use a single |
| No | Comma-separated |
| No | Comma-separated |
| No | Free text |
Example (table-level star edge and a column-level edge)
|
|
|
|
orders | customers | LINEAGE | * |
orders.order_id | customers.customer_id | LINEAGE |
|
Allowed values
Foundational matches platform values case-insensitively. Foundational rejects any value not on this list with an "Unsupported platform value" error.
Table and warehouse task platforms |
|
Dashboard platforms |
|
File storage platforms |
|
Edge types |
|
List fields | comma-separated values |
Worked example
The example workbook below models orders to customers lineage at both table level and column levels.
To see the worked example, download: foundational_manual_lineage_example.xlsx at the end of this article.
Workbook contents
tables sheet
|
|
|
|
|
SNOWFLAKE | analytics | public | orders | orders |
SNOWFLAKE | analytics | public | customers | customers |
columns sheet
|
|
|
|
orders | order_id | integer | orders.order_id |
customers | customer_id | integer | customers.customer_id |
edges sheet
|
|
|
|
orders | customers | LINEAGE | * |
orders.order_id | customers.customer_id | LINEAGE |
|
Upload the file
Manual lineage uploads go through the Foundational public API at https://api.foundational.io, where you can find the Foundational API specifications.
Authentication: All endpoints require a personal API token with the Admin role. Pass the token as Authorization: Bearer <token>. To generate a token, see the article Create API tokens.
If you prefer not to run the upload yourself, your Foundational representative can do it for you.
Create a new manual-upload connector
POST /api/v1/manual-lineage/snapshot (multipart/form-data)
Field | Required | Description |
| Yes | Display name for the new connector |
| Yes | The |
The response contains the connector's connectorId and a snapshotUploadId. Foundational starts a scan to process the file. Save the connectorId. You will need it to upload future versions.
Upload a new version to an existing connector
POST /api/v1/manual-lineage/connectors/{connectorId}/snapshot (multipart/form-data)
Set {connectorId} to the connectorId from a previous response.
Field | Required | Description |
| Yes | The |
The response contains the connectorId and a snapshotUploadId. Foundational starts a scan to process the file.
After uploading
Foundational parses the workbook and merges your entities and edges into the lineage graph. Once the scan completes, the new lineage appears in the Lineage and Catalog views, attributed to your Manual Upload connector.
It generally takes about 15 minutes for the lineage to load, but on occasions it can take up to two hours.
Validation and troubleshooting
If Foundational rejects the file, the error names the sheet, row, and column. Common issues:
Error | Fix |
Only .xlsx files are supported | Save the workbook as |
Missing required columns: … | Add the listed header(s) to that sheet's header row |
Unsupported columns: … | Remove or rename headers not listed in the reference above |
Unknown | The referenced |
Duplicate |
|
Unsupported | Use one of the allowed values listed above |
Uploaded file is empty | The file has no content. Re-export it. |
TIP: It’s best to start from the smallest possible workbook. Use a couple of tables and one edge, confirm they import correctly, and then expand from there.

