OpenLineage
OpenLineage is an open standard for collecting data lineage. This guide explains how to send OpenLineage RunEvent, JobEvent, and DatasetEvent data to Guance from Airflow, Spark, dbt, or an application using an OpenLineage client.
Prerequisites¶
Before you begin, prepare the following:
- The data ingestion URL for your site, such as
https://openway.guance.com. Use the actual URL for other sites or private deployments. - The token for the target workspace. Treat the token as a secret; do not hard-code it or commit it to version control.
- A data job or client that supports the OpenLineage HTTP transport.
- HTTPS connectivity from the job runtime to the ingestion URL.
The examples use these placeholders:
| Placeholder | Description |
|---|---|
<OPENLINEAGE_BASE_URL> |
Data ingestion URL without a path or query parameters |
<WORKSPACE_TOKEN> |
Token for the target workspace |
Ingestion Protocol¶
| Item | Requirement |
|---|---|
| Method | POST |
| Path | /v1/write/openlineage |
| Content-Type | application/json; media type parameters such as charset are allowed |
| Content-Encoding | Omit for an uncompressed body; use gzip for compression |
| Request body | One OpenLineage RunEvent, JobEvent, or DatasetEvent JSON object |
| Default body limit | 64 MiB; for gzip requests, the received compressed size is counted; deployments may override this value; each decoded event must be no larger than 8 MiB |
The complete URL is:
Each request accepts one event. JSON arrays and the OpenLineage batch endpoint are not supported. Keep each event well below the limit so that large facets do not interfere with delivery or retries.
Authentication¶
The endpoint supports these authentication methods:
| Method | Example | Recommendation |
|---|---|---|
| Bearer | Authorization: Bearer <WORKSPACE_TOKEN> |
Recommended; compatible with the OpenLineage HTTP transport api_key authentication |
| Header | X-Token: <WORKSPACE_TOKEN> |
Use for clients that can set a custom header |
| Query | ?token=<WORKSPACE_TOKEN> |
Compatibility only; URLs may be captured by proxies or access logs |
If a request includes more than one token, the precedence is query token, X-Token, then Bearer. Do not configure different tokens in the same request.
Event Requirements¶
Events must follow the OpenLineage specification. All three types require eventTime (an ISO 8601 timestamp), producer (a producer URI), and schemaURL (the specification version URL). Guance identifies the type from the top-level run, job, and dataset objects:
| Event type | Required identity fields | Purpose |
|---|---|---|
RunEvent |
run.runId, job.namespace, job.name, eventType |
Describes a run and its inputs/outputs. Supported eventType values are START, RUNNING, COMPLETE, ABORT, FAIL, and OTHER. |
JobEvent |
job.namespace and job.name; no run or dataset |
Describes Job metadata; may include inputs and outputs. |
DatasetEvent |
dataset.namespace and dataset.name; no run or job |
Describes standalone Dataset metadata and schema/column-lineage facets. |
Include inputs and outputs when a RunEvent or JobEvent has input or output datasets; each dataset should have a namespace and name. Events for the same run must keep the same runId, job.namespace, and job.name. A run normally emits START followed by a terminal COMPLETE, FAIL, or ABORT.
Event Sources and Record Types¶
After ingestion, source identifies the original OpenLineage event type; lineage_record_type identifies the record Guance projected from that event. These are different dimensions: one RunEvent can produce multiple records, all with source=run_event.
source |
Original event | Possible lineage_record_type values |
|---|---|---|
run_event |
RunEvent: run state, inputs, and outputs |
event, dataset_edge; also schema_version or field_edge when relevant facets exist |
job_event |
JobEvent: static or changed Job metadata |
event, job, dataset_edge; also schema_version or field_edge when relevant facets exist |
dataset_event |
DatasetEvent: standalone Dataset metadata |
event, dataset; also schema_version or field_edge when relevant facets exist |
An event record stores the complete, redacted event JSON in lineage_message. job and dataset are entity records; dataset_edge links a Job/Run to an input or output Dataset; schema_version stores a schema summary; and field_edge represents column-level lineage. Without relevant inputs, outputs, or facets, the corresponding derived records are not created. Rejected-event audit records may use source=unknown_event and lineage_record_type=ingest_error; this is not a fourth supported input type.
Guance reserves source for event classification. If the original top-level JSON contains a string source, projected records preserve it as src_source instead of replacing Guance's source. It also remains in the event record's lineage_message.
Queryable Field Inventory¶
The following are top-level fields extracted by Guance, not every field in the original OpenLineage JSON. Presence depends on the event type, record type, and available facets; an absent field is not necessarily an empty value. For the complete event, inspect lineage_message on a lineage_record_type=event record (sensitive values may be redacted). Use event_id to relate records projected from the same event; run_id, job_id, and dataset_id relate runs, jobs, and datasets respectively.
Common Fields and Event Payload¶
| Field | Meaning |
|---|---|
__namespace |
Always lineage; corresponds to the LN DQL namespace |
source |
Event classification: run_event, job_event, or dataset_event; unknown_event for rejected-event records |
src_source |
Original top-level string source, if present |
lineage_record_type |
Projected record type: event, job, dataset, dataset_edge, schema_version, field_edge, or ingest_error |
event_id |
Event identifier derived from the original content within a workspace; shared by records projected from the same event |
date, date_ns |
Original eventTime as Unix milliseconds and nanoseconds; queries use this time |
create_time |
When Guance received the event, in Unix milliseconds |
__docid |
Storage ID for an individual record; not an entity deduplication key |
projection_version, normalization_version |
Guance projection format and Dataset identity normalization versions |
lineage_message |
Complete redacted event JSON; only on event records |
producer, schema_url |
Original producer and schemaURL; primarily on event, also on job and dataset records |
ingest_delay_ms |
Difference between receipt time and eventTime, in milliseconds; only on event |
facet_names |
Names of facets included in the event; only on event |
redaction_policy_version, redacted_count |
Redaction policy version and number of redactions; only on event |
ingest_warning |
Degradation warning codes for optional facet parsing or projection limits; only on event |
Run and Job Fields¶
Identity fields propagate to records derived from the same event. Job names, state, and facet-extracted fields are mainly on event; Job metadata from a JobEvent is also on the job record.
| Field | Meaning |
|---|---|
event_type, run_id |
RunEvent status and original run.runId |
job_id |
Stable Job identifier Guance derives from the workspace, job.namespace, and job.name |
job_namespace, job_name |
Original Job identity; only on event and job records |
parent_run_id, root_run_id |
Parent and root run IDs from the parent Run facet |
parent_job_id, parent_job_namespace, parent_job_name |
Parent Job identity and name |
root_job_id, root_job_namespace, root_job_name |
Root Job identity and name |
is_terminal, is_success, has_error |
Whether terminal, COMPLETE, or failed/aborted/contains an error facet |
error_type |
Recognizable explicit error type from an error facet; not the full error message |
error_message, error_language, stacktrace_hash |
Redacted error message, programming language, and stack trace digest |
input_count, output_count |
Counts of original input and output datasets |
integration, job_type, processing_type |
Job jobType facet |
engine_name, engine_version, openlineage_adapter_version |
Run processing_engine facet |
nominal_start_time, nominal_end_time |
Nominal run start and end times, in Unix milliseconds |
has_sql, sql_hash |
Whether an SQL facet exists and its digest; full SQL is not exposed here |
has_config, config_hash |
Whether a configuration facet exists and its digest |
application_id, spark_master, deploy_mode, driver_host, spark_user |
Spark application details when the relevant facet exists |
trace_ids, trace_links |
Optional trace association IDs and link details |
Dataset, Schema, and Lineage Edge Fields¶
Only DatasetEvent produces a dataset record. RunEvent and JobEvent produce dataset_edge records from their inputs and outputs. schema_version holds a schema summary; the complete schema remains in the original facet inside lineage_message.
| Field | Meaning |
|---|---|
dataset_id |
Stable identifier Guance derives from the workspace and normalized Dataset identity |
dataset_namespace, dataset_name |
Original Dataset namespace and name |
dataset_key |
Normalized Dataset identity key |
dataset_type, data_platform |
Inferred Dataset type and data platform |
account_name, database_name, schema_name, table_name |
Hierarchy names parsed from the Dataset identity, when available |
data_source_name, data_source_uri |
Dataset dataSource facet: the specific data source, distinct from data_platform |
lifecycle_state_change |
Dataset lifecycle-change state |
io_type |
Direction of a dataset_edge or related schema_version: INPUT / OUTPUT |
row_count, size_bytes, file_count |
Row, byte, and file counts from input/output statistics facets |
schema_hash, schema_field_count |
Schema field-list digest and count; may appear on Dataset, edge, and schema-version records |
run_dataset_edge_id, job_dataset_edge_id |
Stable identifiers for Run/Job-to-Dataset input/output relationships |
observed_at |
Observation time on job, dataset, and schema_version records, in Unix milliseconds |
input_dataset_id, input_field |
Upstream Dataset and column on a field_edge |
output_dataset_id, output_field |
Downstream Dataset and column on a field_edge |
field_edge_id |
Stable identifier of a column-lineage relationship |
transformation_type, transformation_subtype, transformation_hash |
Column transformation type, subtype, and digest |
is_masking |
Whether the column transformation masks data |
ingest_error records additionally have ingest_error_reason_code (rejection reason), ingest_error_payload_hash (payload digest), and ingest_error_payload_size (payload size in bytes). They never store the rejected raw payload.
Quick Verification¶
Save the following as event.json, and generate a new runId for each test:
{
"eventType": "START",
"eventTime": "2026-10-21T08:00:00Z",
"run": {
"runId": "4a8f90a7-79c0-4f80-89e7-6b6c5d4fc79a"
},
"job": {
"namespace": "example.scheduler",
"name": "daily_orders"
},
"inputs": [
{
"namespace": "postgresql://warehouse.example:5432",
"name": "sales.orders"
}
],
"outputs": [
{
"namespace": "postgresql://warehouse.example:5432",
"name": "analytics.daily_orders"
}
],
"producer": "https://example.com/openlineage/integration",
"schemaURL": "https://openlineage.io/spec/2-0-2/OpenLineage.json#/$defs/RunEvent"
}
Send the event with a Bearer token:
curl --fail-with-body --silent --show-error \
-X POST '<OPENLINEAGE_BASE_URL>/v1/write/openlineage' \
-H 'Authorization: Bearer <WORKSPACE_TOKEN>' \
-H 'Content-Type: application/json' \
--data-binary @event.json
Send a gzip-compressed event:
gzip -c event.json | curl --fail-with-body --silent --show-error \
-X POST '<OPENLINEAGE_BASE_URL>/v1/write/openlineage' \
-H 'Authorization: Bearer <WORKSPACE_TOKEN>' \
-H 'Content-Type: application/json' \
-H 'Content-Encoding: gzip' \
--data-binary @-
After a successful request, send a COMPLETE event with the same runId and job identity to verify a complete run lifecycle.
Client Configuration¶
OpenLineage Python Client¶
The OpenLineage Python Client uses the api_key setting to generate a Bearer header. The following configuration also enables gzip and retries for transient errors:
export OPENLINEAGE__TRANSPORT__TYPE=http
export OPENLINEAGE__TRANSPORT__URL='<OPENLINEAGE_BASE_URL>'
export OPENLINEAGE__TRANSPORT__ENDPOINT='/v1/write/openlineage'
export OPENLINEAGE__TRANSPORT__AUTH__TYPE=api_key
export OPENLINEAGE__TRANSPORT__AUTH__APIKEY='<WORKSPACE_TOKEN>'
export OPENLINEAGE__TRANSPORT__COMPRESSION=gzip
export OPENLINEAGE__TRANSPORT__RETRY='{"total":5,"connect":5,"read":5,"backoff_factor":0.3,"status_forcelist":[429,500,502,503,504],"allowed_methods":["POST"]}'
Configuration support varies between client versions. Check the Python Client configuration reference before upgrading.
Apache Airflow¶
Install an OpenLineage Provider version compatible with your Airflow release, then configure the HTTP transport:
pip install apache-airflow-providers-openlineage
export AIRFLOW__OPENLINEAGE__NAMESPACE='production-airflow'
export AIRFLOW__OPENLINEAGE__TRANSPORT='{"type":"http","url":"<OPENLINEAGE_BASE_URL>","endpoint":"/v1/write/openlineage","auth":{"type":"api_key","apiKey":"<WORKSPACE_TOKEN>"},"compression":"gzip"}'
The Provider has specific Airflow version requirements. See the Airflow OpenLineage Provider documentation before installing or upgrading it.
Apache Spark¶
Load an OpenLineage Spark integration package compatible with your Spark release, then set these properties:
spark.extraListeners=io.openlineage.spark.agent.OpenLineageSparkListener
spark.openlineage.transport.type=http
spark.openlineage.transport.url=<OPENLINEAGE_BASE_URL>
spark.openlineage.transport.endpoint=/v1/write/openlineage
spark.openlineage.transport.auth.type=api_key
spark.openlineage.transport.auth.apiKey=<WORKSPACE_TOKEN>
spark.openlineage.transport.compression=gzip
spark.openlineage.namespace=production-spark
For package loading and version compatibility, see the OpenLineage Spark installation guide.
dbt¶
Install openlineage-dbt, reuse the Python Client transport settings, and run dbt-ol instead of dbt:
pip install openlineage-dbt
export OPENLINEAGE__TRANSPORT__TYPE=http
export OPENLINEAGE__TRANSPORT__URL='<OPENLINEAGE_BASE_URL>'
export OPENLINEAGE__TRANSPORT__ENDPOINT='/v1/write/openlineage'
export OPENLINEAGE__TRANSPORT__AUTH__TYPE=api_key
export OPENLINEAGE__TRANSPORT__AUTH__APIKEY='<WORKSPACE_TOKEN>'
export OPENLINEAGE__TRANSPORT__COMPRESSION=gzip
export OPENLINEAGE_NAMESPACE='production-dbt'
dbt-ol run
Do not set only OPENLINEAGE_URL: clients normally default to /api/v1/lineage, while this integration requires an explicit /v1/write/openlineage endpoint. See the OpenLineage dbt integration guide for execution modes and supported adapters.
Responses and Retries¶
| Response | Meaning | Recommended action |
|---|---|---|
2xx |
The request was accepted | Do not retry |
200 with error_code set to dataway.cached |
The request was accepted for later delivery; it may not be queryable yet | Do not retry immediately; check visibility later |
400 or 422 |
The JSON or OpenLineage event is invalid | Correct the event before sending it again |
401 or 403 |
The token is missing, invalid, or unauthorized | Correct authentication before sending it again |
413 |
The request body exceeds the limit | Reduce the facet or event size |
415 |
Content-Type or Content-Encoding is unsupported | Correct the headers or compression method |
429, 500, 502, 503, or 504 |
Temporary unavailability, throttling, or a service error | Honor Retry-After; otherwise use exponential backoff with jitter |
Senders must tolerate duplicate delivery and keep a stable runId for the same run. Do not retry deterministic 4xx errors, because invalid events can otherwise remain in the sending queue indefinitely.
Verification and Troubleshooting¶
After configuration, verify the integration in this order:
- Send a minimal
STARTevent with curl and confirm a2xxresponse. - Send a terminal event with the same run and job identity.
- In Guance, search for the record by job namespace, job name, or runId.
- Check producer logs for repeated retries, authentication errors, or serialization errors.
Common issues:
| Symptom | What to check |
|---|---|
401 or 403 |
The token belongs to the target workspace; the authorization value starts with Bearer followed by one space; environment variables are injected correctly |
404 |
The endpoint is explicitly /v1/write/openlineage, not the client default /api/v1/lineage |
413 |
The event or a facet is too large; gzip reduces transfer size, but the event should still be reduced |
415 |
Content-Type is JSON; compressed requests also set Content-Encoding: gzip |
429 or 503 |
The sender honors Retry-After; reduce concurrency and add exponential backoff with jitter |
| The request succeeds but data is not immediately visible | Check for dataway.cached; verify the job namespace/name and runId; wait and check again |
| A run has START but no terminal state | The producer emits COMPLETE, FAIL, or ABORT on its success, failure, and cancellation paths |
Security Recommendations¶
- Use HTTPS in production and keep certificate verification enabled.
- Prefer Bearer or
X-Token; avoid putting tokens in URLs. - Inject tokens through a Secret, secret manager, or runtime environment. Do not put them in images, configuration templates, shell history, or logs.
- Rotate tokens regularly and revoke an exposed token immediately.
- Facets can contain SQL, schemas, or business metadata. Review sensitive content and collect only what is necessary.