Skip to main content

OpenDQV — Open Data Quality Validation

CI License: MIT Python 3.11+ PyPI Docker Platforms OpenSSF Scorecard Coverage Ruff OpenSSF Best Practices

Quickstart Rules Contracts MCP API Security FAQ

"Trust is easier to build than to repair." That is why OpenDQV exists. A 422 at the point of write is cheaper than a data incident three weeks later.

Beta (v2.x). Public API surface (REST, contract YAML, MCP tools, Python SDK) is stable. Breaking changes follow a one-release deprecation cycle. Security fixes backported to the latest 2.x line. See API Stability for commitments.

OpenDQV is a write-time data validation service. Source systems call it before writing data. Bad records return a 422 with per-field errors. Good records pass through. No payload is stored.

OpenDQV demo — define a contract, send a bad record (get a 422), fix it (get a 200)

flowchart LR
    subgraph Callers
        direction TB
        SF[Salesforce]
        SAP[SAP]
        DYN[Dynamics]
        ORA[Oracle]
        WEB[Web forms]
        ETL1[ETL pipelines]

        DJ[Django clean]
        PY[Python scripts]
        PD[Pandas / ETL]

        CD[Claude Desktop]
        CUR[Cursor]
        LLM[LLM agents]
    end

    subgraph OpenDQV
        direction TB
        API[Validation API\nREST / batch]
        SDK[LocalValidator\nin-process SDK]
        MCP[MCP Server\nAI-native]
        API & SDK & MCP --> CON[Contracts · YAML\nGovernance · RBAC\nAudit trail]
        API & SDK & MCP --> GEN[Code Generator\nApex · JS · SQL]
    end

    subgraph Results
        direction TB
        R1[valid: true / false]
        R2[per-field errors]
        R3[severity levels]
        R4[webhooks on events]
    end

    SF & SAP & DYN & ORA & WEB & ETL1 --> API
    DJ & PY & PD --> SDK
    CD & CUR & LLM --> MCP

    API & SDK & MCP --> R1

    subgraph Importers
        IMP[dbt schema · GX suites\nSoda checks · ODCS · CSV]
    end
    IMP --> CON

    style API fill:#0d3b5e,stroke:#092a44,color:#fff
    style SDK fill:#0d3b5e,stroke:#092a44,color:#fff
    style MCP fill:#0d3b5e,stroke:#092a44,color:#fff
    style CON fill:#1a8aad,stroke:#14708d,color:#fff
    style GEN fill:#1a8aad,stroke:#14708d,color:#fff
    style R1 fill:#2ec4e6,stroke:#1a8aad,color:#0d3b5e
    style R2 fill:#2ec4e6,stroke:#1a8aad,color:#0d3b5e
    style R3 fill:#2ec4e6,stroke:#1a8aad,color:#0d3b5e
    style R4 fill:#2ec4e6,stroke:#1a8aad,color:#0d3b5e
    style IMP fill:#1a8aad,stroke:#14708d,color:#fff

A 422 at the point of write closes the feedback loop — producers see failures immediately and fix them at source. Rejection rates drop over time because the tool changes the incentive, not just the outcome.

For post-landing monitoring use Great Expectations, Soda, or dbt tests — they're complementary, not competing. OpenDQV owns layer one (write-time enforcement); those tools own layer three (post-ingestion observability).


AI Agents — first-class via MCP

OpenDQV ships a built-in Model Context Protocol server, so Claude Desktop, Cursor, and any other MCP-compatible agent can discover contracts, validate records, and explain failures through tool calls the agent explicitly declares — no hallucinated compliance, no invented rules.

Watch the 4-minute MCP demo

4-minute demo: Claude Desktop uses two MCP servers — OpenDQV for validation, Marmot for catalog lineage — to check a menu item against ppds_menu_item for Natasha's Law allergen compliance, stating which tool calls it makes and why. (Backup: download the MP4 from the repo)

For tool reference, write guardrails, remote/enterprise mode, and the Marmot composition pattern, see docs/mcp.md.

Reserved agent_id prefix. The prefix OpenDQV_SA_ is reserved for OpenDQV-owned system traffic — smoke probes, demos, MCP self-tests, perf harnesses. The pattern is OpenDQV_SA_[Category]_[Scope] (e.g. OpenDQV_SA_smoke_v240, OpenDQV_SA_probe_persona_b). Customer-facing metrics endpoints (/api/v1/stats, /api/v1/agents, MCP get_quality_metrics, MCP list_agents) suppress these by default so tenant views stay clean of dev/test traffic. Pass include_system=true to surface them for diagnostics — each row carries an is_system_agent flag.


Install

I have... Command
Python 3.11+ git clone https://github.com/OpenDQV/OpenDQV.git && cd OpenDQV && bash install.sh
Docker git clone https://github.com/OpenDQV/OpenDQV.git && cd OpenDQV && cp .env.example .env && docker compose up -d
Just the SDK/CLI pip install opendqv then opendqv init to bootstrap contracts
None of the above Beginner setup guide →

install.sh creates a virtual environment, installs dependencies, and launches the onboarding wizard. Docker pulls ghcr.io/opendqv/opendqv:latest — no build step required.

⚠️ AUTH_MODE=open (the default) has no authentication. Set AUTH_MODE=token and a strong SECRET_KEY in .env before any non-local deployment. See SECURITY.md.


Your First Validation

1. Write a contract — drop a YAML file in your contracts directory (run opendqv init --all to copy every bundled contract, or opendqv init for a single starter):

contract:
  name: order
  version: "1.0"
  owner: "Data Governance"
  status: active
  rules:
    - name: valid_email
      type: regex
      field: email
      pattern: "^[^@\\s]+@[^@\\s]+\\.[^@\\s]+$"
      severity: error
      error_message: "Invalid email format"
    - name: amount_positive
      type: min
      field: amount
      min: 0.01
      severity: error
      error_message: "Order amount must be positive"
    - name: status_valid
      type: allowed_values
      field: status
      allowed_values: [pending, confirmed, shipped, cancelled]
      severity: error
      error_message: "Invalid order status"

2. Reload contracts:

curl -X POST http://localhost:8000/api/v1/contracts/reload

3. Send a bad record — OpenDQV rejects it:

curl -s -X POST http://localhost:8000/api/v1/validate \
  -H "Content-Type: application/json" \
  -d '{"contract": "order", "record": {"email": "not-an-email", "amount": -5, "status": "unknown"}}'
{
  "valid": false,
  "errors": [
    {"field": "email",  "rule": "valid_email",    "message": "Invalid email format",        "severity": "error", "error_code": "OPENDQV_REGEX_VALID_EMAIL"},
    {"field": "amount", "rule": "amount_positive", "message": "Order amount must be positive", "severity": "error", "error_code": "OPENDQV_MIN_AMOUNT_POSITIVE"},
    {"field": "status", "rule": "status_valid",    "message": "Invalid order status",        "severity": "error", "error_code": "OPENDQV_ALLOWED_VALUES_STATUS_VALID"}
  ],
  "contract": "order",
  "version": "1.0"
}

(Abridged — the real response also carries contract_hash, engine_version, validated_at, and per-error suggested_fix.)

4. Fix the record — it passes:

curl -s -X POST http://localhost:8000/api/v1/validate \
  -H "Content-Type: application/json" \
  -d '{"contract": "order", "record": {"email": "alice@example.com", "amount": 49.99, "status": "pending"}}'
{"valid": true, "errors": [], "warnings": [], "contract": "order", "version": "1.0"}

The customer contract ships pre-seeded if you want to skip step 1. The quickstart guide walks through authoring, lifecycle, and batch validation.


Rules

Type What it checks
not_empty Field is present and non-empty
not_empty_string Field is present, a string, and non-empty (non-strings rejected as a type mismatch)
regex Field matches (or does not match) a pattern. Built-ins: builtin:email, builtin:uuid, builtin:ipv4, builtin:url
min / max / range Numeric bounds
min_length / max_length String length
min_age / max_age Keys on a date_format rule (not rule types): age derived from the date within bounds
date_format Parseable date/datetime. Falls back through common formats if no explicit format is set
allowed_values Value must be in a fixed list
forbidden_values Value must not be in a fixed list — placeholder junk (N/A, test@test.com)
lookup Value must appear in a local file or HTTP endpoint (with TTL cache)
compare Cross-field: field op compare_to — supports gt, lt, gte, lte, eq, neq, and today/now sentinels
required_if / forbidden_if Conditional: required or forbidden when another field equals a value
conditional_value Field must hold a given value when another field holds a given value
checksum Check-digit integrity: IBAN, GTIN/GS1, NHS, ISIN, LEI, VIN, CPF, ISRC
unique No duplicates within a batch (batch mode only)
cross_field_range Value must be between two other fields in the same record
field_sum Sum of named fields must equal a target (within optional tolerance)
geospatial_bounds Lat/lon pair within a bounding box
date_diff Difference between two date fields within a range
ratio_check Ratio of two numeric fields within a range
age_match Declared age consistent with date-of-birth field

Rules have severity: error (blocks the record) or severity: warning (flags but allows). Any rule can include a condition block so it applies only when a condition on another field holds — value, not_value, or present: true|false.

Full reference: docs/rules/


How it compares

A mature data governance programme operates across three layers, each with a distinct job:

Layer Purpose Tools
1. Write-time enforcement Prevent bad data from entering any system OpenDQV
2. Catalog / governance / stewardship Ownership, glossary, lineage, policy, stewardship workflows Alation, Atlan, Collibra, Purview, DataHub, Marmot
3. Pipeline testing / observability Detect drift, freshness issues, residual quality after ingestion Great Expectations, Soda Core, dbt tests, Monte Carlo

OpenDQV Core owns layer one. Your catalog handles layer two, your pipeline tools handle layer three.

Great Expectations / Soda / dbt OpenDQV
When After data lands (in warehouse/lake) Before data is written (at the door)
Where Data pipelines, batch jobs Source system integration points
Model Scan data at rest Validate data in flight
Latency Minutes to hours (batch) Milliseconds (API call)
Who calls it Data engineers Data engineers, developers, CRM admins

They're complementary. Use Great Expectations to monitor your warehouse. Use OpenDQV to stop bad data from getting there in the first place.


Contracts

41 production-ready contracts ship inside the opendqv package covering GDPR, HIPAA, SOX, MiFID II, UK Building Safety Act, Martyn's Law, Natasha's Law, Ofcom Online Safety Act, EU DORA, and 20+ other regulatory frameworks across UK, EU, and US. pip install opendqv gives you all of them — opendqv list works with zero configuration.

See docs/compliance-contracts.md for the full list with regulatory context, or browse opendqv/contracts/ directly. 17 minimal starter templates are in examples/starter_contracts/.


Performance

EC2 c6i.large, 2 workers, 12-rule contract, mixed 50/50 workload: ~482 req/s, p99 ~182 ms. Sizing rule: WEB_CONCURRENCY = number of vCPUs.

See docs/benchmark_throughput.md for full platform comparison, methodology, and monthly volume extrapolation.


Documentation

Quickstart Build your first contract in 15 minutes
Rules Reference All rule types with parameters and examples
Compliance Contracts Regulatory context for the compliance-critical bundled contracts
API Reference REST endpoints, SDK, GraphQL, webhooks
Security Deployment checklist, threat model, RBAC
Production Deployment Token auth, TLS, Docker Compose, hardening
Integrations Salesforce, Kafka, Snowflake, dbt, Databricks, MCP, and more
All docs → The full documentation set

API Stability

OpenDQV is in Beta as of 2.0.0. The following stability commitments apply to the v2.x series:

  • REST API endpoints — paths, request bodies, and response shapes are stable within v2.x. Backwards-incompatible changes require a major version bump and follow a deprecation cycle (one minor release of warnings before removal).
  • YAML contract format — the contract schema (rules, fields, types) is stable within v2.x. New rule types may be added; existing rules will not change semantics without a deprecation cycle.
  • Python SDK — OpenDQVClient, AsyncOpenDQVClient, and LocalValidator public method signatures are stable within v2.x. Internal helpers (prefixed _) are not covered.
  • MCP tools — tool names and parameters are stable within v2.x.
  • Security fixes — backported to the latest 2.x line on a best-effort basis.

Presence and null handling (v2.5+)

  • Presence is explicit. A format rule (regex, min, max_length, date_format, …) never implies that a field must exist — add not_empty / not_empty_string when it must. Every non-presence rule treats a missing, null, or blank value as absent and passes. A cross-field rule (compare, field_sum, ratio_check, date_diff, …) whose counterpart is absent or blank fails, and the error entry carries counterpart_missing: true. See docs/contract_conformance.md.
  • Unknown rule types are refused at load (v2.8+). A typo in type: (e.g. min_lenght) is a load error naming the known types — on the REST rule endpoints (422), the MCP draft tool, every importer, and a stored YAML file (the contract does not load; the refusal is logged; opendqv lint catches it before deploy). Until 2.7 such a rule loaded as a disabled rule that passed everything. Both engines refuse the same day.

Contributing

See CONTRIBUTING.md for setup instructions, coding guidelines, and how to submit changes.

License

MIT — see LICENSE.

Acknowledgements

Led by Sunny Sharma, BGMS Consultants Ltd. The vision, the architecture, every contract, and every design decision in this repository are directed by a human who believes data quality is a write-time responsibility.

OpenDQV is built with a hybrid team. Sunny leads — carbon and silicon. Three AI collaborators execute: Claude Sonnet 4.6 (primary developer), Claude Opus 4.6 (strategic auditor), and Grok (market intelligence). All answer to the same ethos: trust is easier to build than to repair.

Release files for opendqv 2.10.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for opendqv 2.10.0
File Size Uploaded
opendqv-2.10.0.tar.gz 364.6 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for opendqv 2.10.0
File Interpreter ABI Platform
opendqv-2.10.0-py3-none-any.whl Python 3 none any Details

Total release size: 799.7 kB

Release files / opendqv-2.10.0.tar.gz

Download URL opendqv-2.10.0.tar.gz
Size 364.6 kB
Tags Source
SHA-256 checksum
How to use checksums
e2effc2e31853029009e94912e779f85037bc433852ccd4983cdcd66e9a6bb61
BLAKE2b-256 checksum
How to use checksums
fe8ebb759b9303698d636985c7ef8462b43cabf0976f97e707190f7a6b4632aa
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 12, 2026.

Transparency log

Release files / opendqv-2.10.0-py3-none-any.whl

Download URL opendqv-2.10.0-py3-none-any.whl
Size 435.1 kB
Tags Python 3
SHA-256 checksum
How to use checksums
de3051f4dbf520aaef923bb310785510094a19b8ce091acac58bb92939f84f72
BLAKE2b-256 checksum
How to use checksums
995b6064edb77269e5a07d0af4887178f0145d166c66267ac494597981a956b8
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 12, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

2.10.0 This release

2 release files

2.9.1

2 release files

2.9.0

2 release files

2.8.0

2 release files

2.7.0

2 release files

2.6.0

2 release files

2.5.1

2 release files

2.5.0

2 release files

2.4.0

2 release files

2.3.28

2 release files

2.3.26

2 release files

2.3.25

2 release files

2.3.24

2 release files

2.3.23

2 release files

2.3.22

2 release files

2.3.21

2 release files

2.3.20

2 release files

2.3.19

2 release files

2.3.18

2 release files

2.3.17

2 release files

2.3.16

2 release files

2.3.15

2 release files

2.3.14

2 release files

2.3.13

2 release files

2.3.12

2 release files

2.3.11

2 release files

2.3.9

2 release files

2.3.8

2 release files

2.3.7

2 release files

2.3.6

2 release files

2.3.5

2 release files

2.3.4

2 release files

2.3.3

2 release files

2.3.2

2 release files

2.3.1

2 release files

2.3.0

2 release files

2.2.8

2 release files

2.2.7

2 release files

2.2.6

2 release files

2.2.5

2 release files

2.2.4

2 release files

2.2.3

2 release files

2.2.2

2 release files

2.2.1

2 release files

2.1.0

2 release files

2.0.0

2 release files

1.9.8

2 release files

1.9.7

2 release files

1.9.6

2 release files

1.9.5

2 release files

1.9.4

2 release files

1.9.3

2 release files

1.9.2

2 release files

1.9.1

2 release files

1.9.0

2 release files

1.8.9

2 release files

1.8.8

2 release files

1.8.7

2 release files

1.8.6

2 release files

1.8.5

2 release files

1.8.4

2 release files

1.8.3

2 release files

1.8.2

2 release files

1.8.1

2 release files

1.8.0

2 release files

1.7.1

2 release files

1.7.0

2 release files

1.6.0

2 release files

1.5.2

2 release files

1.5.1

2 release files

1.5.0

2 release files

1.4.0

2 release files

1.3.3

2 release files

1.3.2

2 release files

1.3.1

2 release files

1.3.0

2 release files

1.2.3

2 release files

1.2.2

2 release files

1.2.1

2 release files

1.2.0

2 release files

1.1.0

2 release files

1.0.7

2 release files

1.0.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page