Skip to content

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.

{"status": "ok", "plugin": "airflow_dq"}

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.

{"ids": [1, 2, 3], "resolved_by": "you@example.com"}

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)

{"schema_yml": "<contents of a dbt schema.yml>", "dataset": "optional_model_name"}

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).