Skip to main content
AnomalyArmor speaks the Open Data Contract Standard (ODCS) v3.1.0. Every asset’s monitoring config (freshness SLAs, validity rules, schema, metrics, drift monitors, alert rules, destinations, blackouts) exports as ODCS YAML and imports back on demand.
Export and import round-trip for ODCS contracts
Three things this gives you:
  1. Interoperability. ODCS is a Linux Foundation project. Soda, Great Expectations, and dbt speak it. Your contract is not a proprietary blob.
  2. No lock-in. Click Export, get a directory of YAML. Import it into whichever tool you pick next. No rewrite required.
  3. Version control. Contracts diff cleanly in Git. Reviewers see which validity rule changed in a PR, not a screenshot of a settings page.

ODCS-native vs ODCS-extended

ODCS covers the core concepts every tool agrees on: schema, column quality rules, freshness SLAs, ownership. We use its sanctioned customProperties extension mechanism for the AnomalyArmor-specific concepts ODCS does not have a native slot for.
Native ODCS (any tool reads these)Extended (under customProperties.anomalyarmor)
apiVersion, id, name, version, status, domain, tagsSchema drift monitoring (ODCS has no schema-change concept)
schema[] with properties[] + physicalTypeML distribution drift (PSI, KS, chi-squared)
quality[] metric library (nullValues, invalidValues, duplicateValues, rowCount)Row-count anomaly detection with rolling baselines
quality[] custom SQLAlert rules and routing
slaProperties[] (latency, frequency)Destinations (Slack, email, PagerDuty, Linear, webhooks)
team[]Operating schedules and blackout windows
Our exported YAML is fully ODCS-compliant: it passes the official v3.1.0 JSON Schema validator. Using customProperties is exactly how Soda and Great Expectations handle the same tension: industry-standard, not a workaround. ODCS models customProperties as a list of {property, value} entries, not a dict. Our AnomalyArmor extensions all ride inside a single entry whose property is anomalyarmor:
customProperties:
  - property: anomalyarmor
    value:
      freshness: [...]
      validity: [...]
      drift_monitors: [...]

Deviations from ODCS v3.1.0

Small, documented places where our exported YAML does more or less than the spec:
  • schema[].properties[].id (extension). ODCS does not define an id at the column level. We write a stable UUID5 per column so re-import can match columns across renames. odcs-pure mode strips it; other ODCS tools read the column by name and ignore id.
  • Unmodeled optional sections. These ODCS sections are recognized but we do not produce or consume them: servers, roles, price, support, authoritativeDefinitions, contractCreatedTs. They round-trip opaquely if present on import and are never emitted on export.

Portability modes

Two export modes control what gets written to the YAML.

extended (default)

Full round-trip fidelity. Includes customProperties.anomalyarmor.<domain> blocks with every AA-specific configuration knob (drift thresholds, monitoring modes, alert routing, etc.). Use this for version control, backup, and re-import into AnomalyArmor.

odcs-pure

Strips every customProperties.anomalyarmor block. The resulting YAML contains only the ODCS-native subset that any ODCS tool can read and act on: schema, quality rules, freshness SLAs, team. You lose the AA-extended features (drift, alert routing, blackouts) but gain maximum portability for the interop case. Pick odcs-pure when you want to hand the contract to a Soda or Great Expectations customer. Pick extended everywhere else.

CLI

pip install anomalyarmor-cli
armor auth login --key aa_live_your_key_here
The package installs two console scripts, armor and anomalyarmor. They are aliases, use whichever you prefer. Examples below use armor for brevity.

Export: one asset to stdout

armor contract pull --asset <asset-uuid>
Writes ODCS YAML to stdout. Pipe to yq or redirect to a file.

Export: one asset to a file

armor contract pull --asset <asset-uuid> -o orders.yaml

Export: whole warehouse as a zip

armor contract pull --warehouse analytics -o analytics.zip
Submits an async job, polls to completion, and writes the zip. Layout inside the zip:
analytics/
  contract/
    orders.yaml
    customers.yaml
    invoices.yaml

Export: every contract in your account

armor contract pull --all -o everything.zip

Filtering (export or import)

Include or exclude specific config domains:
# Only freshness + validity
armor contract pull --asset <uuid> --include freshness,validity -o partial.yaml

# Everything except alert routing and destinations
armor contract pull --asset <uuid> --exclude alert_rules,destinations -o no-alerts.yaml
Registered domain names: schema, freshness, validity, metrics, row_count, drift_monitors, schema_drift, alert_rules, destinations, blackouts.

Export: ODCS-pure mode

armor contract pull --asset <uuid> --mode odcs-pure -o orders.odcs.yaml

Validate a YAML file (no DB, no auth required beyond the API key)

armor contract validate -f orders.yaml
Exits 0 on a valid document, non-zero on parse or schema errors. Errors are printed one-per-line with file path, YAML path, and line number, so editors and CI can parse the output. Cheap: a single HTTP round-trip regardless of contract size.

Plan an import (dry-run diff)

armor contract plan --asset <asset-uuid> -f orders.yaml
Shows a per-domain diff without applying. Output:
Plan for asset 53581432-...:
  freshness         +2 ~1 -0
  validity          +0 ~3 -1
  drift_monitors    +0 ~0 -0
+ is additions, ~ is modifications, - is deletions.

Apply an import

armor contract apply --asset <asset-uuid> -f orders.yaml
Transactional per asset. Any domain failure rolls back the whole contract. By default, configs present in the live asset but missing from the YAML are not deleted (warn-only). To prune, pass --prune:
armor contract apply --asset <asset-uuid> -f orders.yaml --prune
--prune deletes configs that are absent from the YAML. Treat this the way you would treat rm -rf, review the plan output first.

Diff two local YAML files

armor contract diff baseline.yaml pr.yaml
Pure client-side, no server call. Uses the same identity-matching logic as the server-side plan, so the summary line-up is identical. Useful in CI to compare contracts/main.yaml to contracts/pr.yaml before calling plan or apply.

In-product UI

The same primitives ship in the app.
  • Export. Every asset detail page has an Export as YAML action. Opens a modal with three controls: asset scope (this asset / schema / warehouse / all), feature-domain checkboxes, and the extended / odcs-pure toggle. Multi-asset scopes download as a zip.
  • Import. Same page, Import YAML action next to Export. Drag-drop or click-to-browse a .yaml file. The modal runs server-side validation, then shows a plan preview (+2 freshness, ~1 validity, -0 drift). Apply button is disabled until the plan is clean. Prune has an explicit confirmation dialog that lists what will be removed.
  • Bulk import. Asset list supports checkbox selection; the bulk action bar exposes Import YAML which applies one contract to many selected assets (async job, polled in the modal).

REST API

All endpoints authenticated via your API key. Single-asset endpoints are synchronous; bulk endpoints run as async jobs.

Export, single asset (synchronous)

GET /api/v1/contracts/{asset_public_id}/export
    ?mode=extended|odcs-pure     (default: extended)
    &include=freshness,validity  (comma-separated, default: all)
    &exclude=alert_rules         (applied after include)
Returns application/x-yaml with a Content-Disposition: attachment header.

Export, bulk (asynchronous)

POST /api/v1/contracts/export-jobs
  { "scope": "warehouse"|"all",
    "scope_name": "analytics",        // required for scope=warehouse
    "mode": "extended",
    "include": ["freshness"],
    "exclude": ["alert_rules"] }
Returns {"job_id": "<uuid>"}. Poll with:
GET /api/v1/contracts/export-jobs/{job_id}
Response includes status, progress_percent, and asset_count. When status is completed, download:
GET /api/v1/contracts/export-jobs/{job_id}/download
Returns application/zip with the laid-out {warehouse}/contract/{table}.yaml archive.

Validate (no DB, no asset context)

POST /api/v1/contracts/validate
Body: raw ODCS YAML
Returns { valid: bool, errors: [{line, yaml_path, code, message}], contract_summary: {...} }.

Plan, single asset (synchronous)

POST /api/v1/contracts/{asset_public_id}/plan
Body: raw ODCS YAML
Returns { valid, errors, diffs: { <domain>: { added, modified, removed } } }. Read-only, no mutations.

Apply, single asset (synchronous, transactional)

POST /api/v1/contracts/{asset_public_id}/apply?prune=false
Body: raw ODCS YAML
Returns { valid, applied: { <domain>: {added, modified, removed} }, unsupported_domains: [], pruned: bool }. Partial failures roll back the entire contract.

Apply, bulk (asynchronous)

POST /api/v1/contracts/apply-jobs
  { "asset_public_ids": ["...", "..."],
    "yaml": "apiVersion: v3.1.0\n...",
    "prune": false }
Returns {"job_id": "<uuid>"}. Poll GET /api/v1/contracts/apply-jobs/{job_id} for per-asset status.

Round-trip example

# Export
armor contract pull --asset <uuid> -o orders.yaml

# Edit the freshness threshold in orders.yaml
vim orders.yaml

# Commit to Git
git add orders.yaml && git commit -m "tighten freshness SLA on orders"

# Preview the change without applying
armor contract plan --asset <uuid> -f orders.yaml

# Apply once the diff looks right
armor contract apply --asset <uuid> -f orders.yaml

Adapter coverage at a glance

Three adapters ship today. The grid shows which kinds of rules each adapter converts automatically, which convert with caveats, and which you rewrite as custom rules in AnomalyArmor.
Coverage matrix across Soda, dbt, and Great Expectations adapters
Every skipped check surfaces a warning, never a silent drop. See the per-adapter guides below for the exact mapping tables.

Coming from Soda?

Soda ships ODCS export as of 2024. Run soda export --odcs on your existing Soda project and the resulting YAML imports directly into AnomalyArmor:
soda export --odcs > contracts/
armor contract apply --asset <uuid> -f contracts/orders.yaml
Two-command Soda to AnomalyArmor migration
If you want to preview the migration before signing up, paste your Soda YAML into anomalyarmor.ai/migrate for a structural summary without an account.

Coming from dbt or Great Expectations?

Those tools do not emit ODCS directly. We ship adapters that translate their config into ODCS YAML, which then feeds the same contract apply pipeline:
# dbt: translate schema.yml tests and contracts into ODCS
armor migrate-from dbt ./my-dbt-project/ > contracts/

# Great Expectations: translate expectation suites into ODCS
armor migrate-from great-expectations ./great_expectations/ > contracts/
See the per-adapter guides for mapping rules, unsupported expectations, and CLI options:

Limits and current gaps

  • No S3 artifact storage for huge bulk exports. Jobs today inline the zip bytes in the status response, which is fine for hundreds of tables. Company-wide exports with thousands of tables should use multiple --warehouse jobs.
  • Single-asset apply only in contract apply. Multi-asset apply ships through the bulk-apply REST/UI path, not through the CLI flag surface yet.
  • Some domains are unsupported on import. The apply response lists any domains whose import_ is not yet implemented in unsupported_domains. Pair with plan first to see whether they would have changed before committing.

Common Questions

What is ODCS and why does AnomalyArmor use it?

ODCS is the Open Data Contract Standard, a Linux Foundation project that defines a vendor-neutral YAML format for data contracts. Using it means your AnomalyArmor config is portable: Soda, Great Expectations, and dbt also speak it, so you keep optionality if you ever switch tools.

What is the difference between extended and odcs-pure export modes?

extended (default) includes the full customProperties.anomalyarmor block so the contract round-trips with every AA-specific feature intact (drift, alert routing, blackouts). odcs-pure strips those blocks and emits only the ODCS-native subset (schema, quality, freshness, team). Use odcs-pure when handing the contract to a Soda or Great Expectations user, extended everywhere else.

How do I preview contract changes before applying them?

Run armor contract plan --asset <uuid> -f contract.yaml. It returns a per-domain diff showing additions, modifications, and deletions without mutating anything. Apply with armor contract apply once the diff looks right. See Plan an import.

Does contract apply delete configs that aren’t in my YAML?

No, not by default. Configs present in the live asset but missing from the YAML are warn-only, so you won’t accidentally wipe rules by applying a partial contract. Pass --prune to explicitly delete them, and treat that flag the way you would rm -rf, always review the plan output first.

Can I put AnomalyArmor contracts in Git?

Yes, that’s one of the main reasons to use them. The YAML diffs cleanly in PRs so reviewers see which validity rule or freshness SLA changed, not a screenshot of a settings page. Export with armor contract pull, commit the file, and use armor contract diff in CI to compare branches.

Can I validate a contract YAML without applying it?

Yes. armor contract validate -f contract.yaml runs the document against the official ODCS v3.1.0 JSON Schema and exits non-zero on parse or schema errors. Errors print with file path, YAML path, and line number so editors and CI can surface them inline. No DB connection required.