REST API¶
The plugin mounts a FastAPI app under /dq on the Airflow API server — the same JSON API the
built-in UI consumes. Routes below are relative to http://<airflow>/dq. Interactive docs
(/dq/docs, /dq/openapi.json) are deliberately disabled.
Auth — every route except GET /api/health requires one of: an Airflow auth-manager JWT
(Authorization: Bearer <jwt>), the legacy shared secret (Authorization: Bearer $DQ_API_TOKEN),
or the Airflow UI session cookie. Read routes accept any authenticated caller; write routes
(marked below) additionally consult the auth manager's authorization — a read-only user gets 403.
See Configuration → Authentication.
Errors — uniform JSON {"detail": "..."}: 400 invalid input, 401 bad/missing
credentials, 403 not authorized to write, 404 unknown contract/version, 409 contract
version conflict, 503 no auth scheme configured.
TOKEN="..." # DQ_API_TOKEN or an Airflow JWT
DQ="http://localhost:8080/dq/api"
curl -s -H "Authorization: Bearer $TOKEN" "$DQ/overview"
Health¶
GET /api/health¶
Unauthenticated liveness probe.
Results, trends, overview¶
GET /api/results¶
Check results, newest first.
| Query param | Type | Meaning |
|---|---|---|
contract |
str | filter by contract ref, e.g. payments@v1 |
dag_id |
str | filter by DAG (separate gates from monitors) |
status |
str | pass | warn | fail | error |
since / until |
ISO datetime | executed_at window |
limit / offset |
int | pagination (default limit 100) |
Response: {"items": [...], "total": n}. Legacy shape: when only contract is passed (no
pagination/filter params) the response is a bare list (kept for one release).
Each item: contract_id, contract_version, dataset, check_name, dimension, severity,
status, observed_value, expected, rows_scanned, rows_failed, owner, sample_json
(the bad-row sample), sample_ref, dag_id, run_id, task_id, executed_at, duration_ms,
attempt, alerted.
GET /api/trends¶
Per-run quality aggregates over time (the trend chart), chronological.
| Query param | Type | Meaning |
|---|---|---|
contract |
str | contract ref |
limit |
int | number of runs (default 60) |
Each point carries per-run pass/warn/fail/error counts, rows_failed, and an anomalous flag
(server-side metric anomaly, from dq.dq_metric_anomalies).
GET /api/overview¶
Per-contract rollup powering the Overview page: latest-run status counts, pass rate, SLA check statuses, anomaly count, last run coordinates. One object per contract.
Quarantine¶
GET /api/quarantine¶
| Query param | Type | Meaning |
|---|---|---|
dataset |
str | filter by dataset |
check |
str | filter by check name |
resolved |
bool | false = open items only |
limit / offset |
int | pagination (default limit 50) |
Response: {"items": [...], "total": n}; items carry the quarantined row_json payload plus
dataset/check/dag/run coordinates and resolution audit fields.
POST /api/quarantine/resolve (write)¶
Bulk-resolve quarantined rows.
Response: {"resolved": 3}.
Contracts¶
GET /api/contracts¶
All available contract refs: ["orders@v3", "payments@v1", ...].
GET /api/contract?ref=payments@v1¶
One contract in the editor's draft shape (id, dataset, owner, columns, checks) — for editing / re-versioning.
GET /api/contracts/{contract_id}/versions¶
Version timeline from the registry (version, created_at, is_breaking, diff_summary);
falls back to the filesystem store when the registry has no rows. 404 for unknown ids.
GET /api/contracts/diff?id=orders&from=2&to=3¶
Classify changes between two existing versions: breaking (column removed, type-family changed, new required column, uniqueness added, rule/severity tightened) vs info.
POST /api/contracts (write)¶
Persist an authored draft as a new immutable contract version.
{
"id": "payments",
"dataset": "payments",
"version": 2,
"owner": "payments-team@example.com",
"columns": [{"name": "payment_id", "logicalType": "string", "required": true, "unique": true}],
"checks": [{"rule": "valueRange", "column": "amount", "severity": "blocking",
"params": {"mustBeGreaterThan": 0}}],
"propose": false
}
Returns the saved path/ref plus the classified diff against the previous version. Saving an
existing version number returns 409 (versions are immutable). With "propose": true (and
DQ_CONTRACTS_GIT=true) the version is committed on a dq/contract-<id>-v<N> branch instead;
the response's branch field says which.
POST /api/contracts/yaml (write)¶
Same body as save; returns {"yaml": "..."} — the ODCS document for the draft without persisting
(the editor's YAML preview).
POST /api/contracts/import/dbt (write)¶
Converts dbt tests into a contract draft (not_null → required+notNull, unique,
accepted_values, relationships → referentialIntegrity). Unmappable tests are listed in
skipped_tests, never dropped silently. Nothing is saved.
Low-code authoring¶
GET /api/columns?dataset=payments&backend=postgres_dwh¶
Live column names + types of the target table (editor dropdowns), via information_schema on the
resolved backend.
POST /api/preview (write)¶
Run one authored check against the live table — the editor's Test button. Same execution
path as a DAG run (tolerances, where, samples included).
{"dataset": "payments", "rule": "valueRange", "backend": "postgres_dwh",
"column": "amount", "params": {"mustBeGreaterThan": 0}}
Response: a check-result dict (status, rows_scanned, rows_failed, observed_value,
expected, sample_json). Execution errors come back as {"status": "error", ...} rather than
HTTP failures.
GET /api/profile?dataset=payments&backend=postgres_dwh¶
Profile a live table (count/distinct per column, min/max for numerics, low-cardinality values) and return a suggested starter contract draft. Runs synchronously — expect it to take a moment on wide/large tables.
UI¶
GET /app/¶
Serves the built React app (Overview, Quality Dashboard, Quarantine, Contract Editor). The page
itself contains no secrets; it authenticates via the Airflow session cookie or a caller-provided
header. Returns 503 with build instructions when no UI bundle is found (source checkouts before
make ui-build).