Skip to content
BeryX

Developers

Your data quality, over an API.

Read BeryX (formerly DataVault) scores, issues, contracts and verdicts from your pipelines, your CI or an agent. REST, JSON, OpenAPI 3.1 — and tokens that can only read.

150 endpoints · version 1.78.0 · updated October 13, 2026

Get started

From account to first request in three steps.

  1. Create a token

    A workspace admin creates it in Settings → API tokens (Team plan and above). The dvk_… value is shown only once — keep it in a secrets vault.

  2. Call the API

    Send the token in the Authorization: Bearer header. Every response is JSON (except the .yml, .sql and .md exports).

  3. Automate

    Wire a dataset verdict into your CI, feed dashboards or generate a typed client from the OpenAPI contract.

export DATAVAULT_TOKEN="dvk_…"

curl -s "https://api.praxis.lbostuff.tech/api/v1/datasets?limit=20" \
  -H "Authorization: Bearer $DATAVAULT_TOKEN"

Authentication

API tokens: read-only by design.

Each token belongs to a workspace and inherits its read permissions. It cannot open the Console, change data or see source credentials.

  • Format dvk_ followed by 48 hex characters. We only store the SHA-256 hash — if you lose it, create a new one.
  • GET, HEAD and OPTIONS only, plus the few writes marked in the reference (RAG gate, triggering scans, orchestrator runs, data-diff and batch trust decisions). Any other method returns 403, even when the endpoint exists.
  • Instant revocation in Settings → API tokens; last use is visible for auditing.
  • Creation and revocation are recorded in the workspace activity log.

Pagination

Lists accept limit and offset. The catalog (GET /datasets) returns the total in the X-Total-Count header.

Authentication header

Authorization: Bearer dvk_3f9c…

Error codes

401
Missing, invalid or revoked token.
402
The feature is not included in the workspace plan (e.g. the AI Act dossier).
403
Write method with a token, or suspended workspace.
404
The resource does not exist or belongs to another workspace.
422
Invalid parameters — the body names the field and the reason.

Every response carries the X-Request-ID header. 4xx errors carry detail (the reason, in Portuguese), code (stable, for your code to branch on; null when the error has none) and request_id, equal to the header. Quote the request_id to support so we can find the request in our logs.

HTTP/1.1 403 Forbidden
Content-Type: application/json
X-Request-ID: 7b0c1e4e-3f7a-4c1b-9a51-2d0f7f1e9c11

{
  "detail": "Tokens de API são read-only — usa uma sessão de utilizador para mutações.",
  "code": "token_read_only",
  "request_id": "7b0c1e4e-3f7a-4c1b-9a51-2d0f7f1e9c11"
}

Use case

A quality gate in your CI.

GET /datasets/{id}/verdict returns pass, warn, fail or unknown with the failing rules. Fail the deploy when a critical rule fails or the data contract is no longer valid.

SDKs generated from the contract

openapi.json is generated from the code on every release, so a generated client never drifts from the API. The examples above use openapi-typescript and openapi-python-client; any OpenAPI 3.1 generator works.

# .github/workflows/data-gate.yml → passo antes do deploy
verdict=$(curl -sf "https://api.praxis.lbostuff.tech/api/v1/datasets/$DATASET_ID/verdict" \
  -H "Authorization: Bearer $DATAVAULT_TOKEN" | jq -r .verdict)

echo "BeryX: $verdict"
[ "$verdict" != "fail" ] || exit 1

Stability

What happens to your integration when the API changes.

The contract is what this portal shows. A minor version only adds; a breaking change only ships in a major, after an announced deprecation. Summary of the versioning policy — the version in force is 1.78.0.

  • 180 days

    minimum notice before a deprecated operation stops responding

  • 30 days

    notice before we reduce rate limits or quotas

  • 2 minors

    of the Python SDK supported at the same time, with fixes

  • What the contract is

    Everything in this portal: the /api/v1 operations an API token can call, with their parameters, fields and status codes. The rest of the API (the web app, account and token management, internal areas) is not covered and may change without notice.

  • Compatible changes (minor)

    A minor version only adds: operations, optional parameters, response fields, new enum values and new codes. Your client should ignore fields it does not know and handle unknown enum values. The code of reasons and blockers (trust decision, readiness) are stable; envelopes with schema_version (trust facts, BI feeds) only change schema_version in a breaking change.

  • What a breaking change is (major)

    Removing or renaming an operation, a parameter or a field; changing its type or format; making something optional required or rejecting a request that used to be accepted; changing the meaning of a field, a code or an HTTP status. A breaking change only ships in one of two ways: under a new path version (/api/v2), leaving /api/v1 unchanged, or in a major version announced in the changelog after a deprecation. Our CI compares every change with the published contract and rejects a breaking change that is not announced.

  • Deprecation

    Announced in the changelog (type deprecated) at least 180 days before the end, with direct communication to the admins of workspaces with active API tokens. During the window the operation keeps responding, with the Deprecation (RFC 9745 — since when), Sunset (RFC 8594 — when it stops responding) and Link with rel="deprecation" (the migration note) headers. Exception: a security fix may shorten the window and is logged in the changelog with type security.

  • SDK

    The Python SDK (beryx-sdk) has its own versions. We support the two most recent minors (N and N-1): they get fixes and track the API. Older versions keep working while the API has no breaking changes, but get no fixes.

  • Rate limits and quotas

    Request limits (per token and per IP) and plan quotas may change; a reduction is announced at least 30 days in advance, except anti-abuse measures. The values in force come with the responses (X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset); above the limit the API responds 429 with Retry-After and, above a plan quota, 402.

  • Reporting a problem

    A response that contradicts this contract is our bug. Write to suporte@praxis.lbostuff.tech with the method and path, the HTTP status, the time (UTC), the X-Request-ID of the response (errors also carry it in request_id) and the response of GET /version (version, build_sha, api_version) — never the token. Vulnerabilities: the contact in /.well-known/security.txt.

How you know something is going away

A deprecated operation keeps responding during the window, but starts sending these headers — and shows up in the changelog below with type “Deprecated.” Watch for them in your clients: CORS exposes them in the browser too.

Read the full policy

The policy is in Portuguese, with an English version at the end.

Example response from a deprecated operation

HTTP/1.1 200 OK
Deprecation: @1790812800
Sunset: Thu, 01 Apr 2027 00:00:00 GMT
Link: <https://beryx.lbostuff.tech/developers#estabilidade>; rel="deprecation"; type="text/html"

API changelog

What changed in the contract.

Breaking changes are announced here before they reach production. Product news in general: see the BeryX changelog.

  1. v1.78.0

    • Added

      access_role in GET /api/v1/datasets/{dataset_id}: the caller's role on that dataset, viewer, editor or owner. It is the same role the API uses to decide requests on that dataset: team grants on the dataset's domains raise the workspace role, and a restricted-access domain only counts with a grant. A read-only dvk_* token stays read-only whatever the role. Card #586.

  2. v1.77.0

    • Deprecated

      The product is now called “BeryX”. The “DataVault” names in contracts already in production keep working until 31 May 2027. Webhooks and the SIEM export send X-BeryX-Event, X-BeryX-Delivery, X-BeryX-Timestamp and X-BeryX-Signature and, until that date, also the X-DataVault-* headers with the same value; the signature is the same in both. The environment variables of the server, the MCP server, the Python SDK and the Terraform provider read BERYX_<NAME> and DATAVAULT_<NAME>; when both are set the new one wins, and the API and the worker warn at startup with the old names in use. The MCP server is named beryx, and the configurations generated in “Connect your agent” use beryx and BERYX_TOKEN. GET /workspace/config downloads beryx.yaml; beryx config plan|apply and scripts/workspace_config.py import without a path read beryx.yaml and, if it does not exist, datavault.yaml. In the dbt export (the schema.yml and the singular tests in rules.dbt-tests.zip), the rule goes in config.meta.beryx, and the GX suite carries the table and unsupported in meta.beryx; the import and the run_results.json matching read meta.beryx and meta.datavault, with the new one winning. The webhook User-Agent becomes BeryX-Webhooks/1 DataVault-Webhooks/1: a filter that looks for DataVault-Webhooks still accepts the deliveries. Unchanged: asset names in Collibra, the OpenLineage namespace, the dvk_ token prefix, the event types (datavault.alerts…) and the names of the exported files (tests/datavault_*.sql and the <table>.datavault suite), so the tests are not duplicated in your repository.

  3. v1.76.0

    • Security

      POST /synthetic-packs runs the utility × privacy evaluation of GET /datasets/{dataset_id}/synthetic on each table's rows before writing the file. With privacy_verdict = fail in a table, it answers 409 and sends no file. The detail carries code = synthetic_privacy_fail, the message in pt-PT and, in tables, the name, dataset_id, key and params of each refused table. Each table's evaluation in the manifest is no longer null and carries the scorecard. Until now, the pack could carry rows that the single-dataset export refused. The generated keys of a referenced table also stop using the values that the FK columns pointing to it cannot carry (real samples, extremes, real PII): a pack with such values in the profile changes bytes. Card #561.

  4. v1.75.0

    • Added

      language in GET /reports and GET /reports/{report_id}: the language of the content_md text (pt, en or es). The LLM writes in the workspace language (workspace.default_locale) and the offline template writes in pt; older reports come with null and are in pt. Section headings stay in Portuguese. Card #355.

  5. v1.74.0

    • Added

      GET /api/v1/suggestions: the category filter accepts enrichment, the category of enrichment proposals (values from reference sources with the source, date and confidence, applied through a PR or SQL once approved). They are only created when someone asks for a proposal on a dataset; the AI does not create them. Card #389.

  6. v1.73.0

    • Added

      Outbound webhooks: code (a stable code in English and snake_case, e.g. alerts_new, escalation_level, digest_weekly) and params (only numbers and identifiers) in every WebhookPayload event, and title/body in the channel's language: the locale in the channel's config, otherwise workspace.default_locale, otherwise pt-PT. On email channels, each recipient gets the message in their own language (the account's locale, otherwise the workspace's) and an external address in the channel's. The codes are listed in docs/WEBHOOKS.md. Card #519.

    • Added

      Errors in the request's language: detail follows the person's language (the account locale), otherwise the workspace default, otherwise the Accept-Language header (pt, en or es; de and fr get en), otherwise pt-PT. Each catalogued error carries its stable code, including those that had a null code until now. This also covers the plan 402/403, the rate-limit and scan-queue 429, the body-size 413 and the message of a structured detail ({code, message}), whose code stays the same. With no known language, the text is the same as before. Card #519.

  7. v1.72.0

    • Added

      GET /api/v1/datasets/{dataset_id}: each profile column's semantic gains region. For the phone type it is the country of the numbering plan (ISO 3166-1 alpha-2), taken from the data country; for every other type and for phone_pt (which is PT) it is null. Card #516.

  8. v1.71.0

    • Added

      Language, region and time zone: locale (pt-PT, pt-BR, en-GB, en-US, es-ES, de-CH or fr-CH) and timezone (IANA) in GET /auth/me, plus workspace.default_locale and workspace.timezone. PATCH /auth/me saves the person's own (only the keys sent change; null falls back to the workspace default) and GET/PUT /workspace/locale manage the workspace defaults (admin). Card #518.

    • Added

      Schedules with a time zone: timezone (IANA) in the body of PATCH /connections/{connection_id}/schedule and scan_timezone in GET /connections; timezone in POST/PATCH /report-schedules and in the response. The hour (hour, scan_hour) is read in that time zone and the next UTC instant is computed for each occurrence, so the local hour survives daylight saving changes. Existing schedules stay in UTC and keep their hour; without timezone, the PATCH keeps the connection's time zone. Card #518.

  9. v1.70.0

    • Added

      GET /api/v1/domains/suggestions: the suggested domain for datasets without one, by the majority of their lineage links to already curated datasets (at least half of the links, with no tie). Each suggestion carries the votes, the total links, up to 3 via columns, the edge kinds, inferred_only and the evidence signature; ties come in ambiguous, with the candidates. Admins see all of them; domain managers only those for their domains; everyone else gets 403. An invisible neighbour counts towards the total and comes as “Dataset restrito” (restricted dataset). has_lineage says whether any lineage link touches a visible dataset, because counts.with_lineage only counts datasets without a domain. Card #468.

    • Added

      Metadata quality: the missing_domain issue gains a suggestion with channel: "lineage" when datasets without a domain have a lineage suggestion (“Juntar pela linhagem — N com sugestão”, with link to /datasets?vista=dominios and endpoint GET /api/v1/domains/suggestions). New value in the channel enum of MetadataSuggestionOut. Card #468.

  10. v1.69.0

    • Added

      Data country (ISO 3166-1 alpha-2): data_country in GET /connections and GET /connections/{connection_id} (the source's; null = the workspace's), data_country in GET /datasets and GET /datasets/{dataset_id} (the dataset's own; null = inherited) and country in the dataset detail, with the effective country the scans use (dataset → connection → workspace) and the source it comes from. GET /datasets/{dataset_id}/data-country returns the same object. Workspaces default to PT, so nothing changes until someone picks another country; with another country, 9-digit phone numbers without a calling code and postal codes are no longer read as Portuguese. Card #513.

  11. v1.68.0

    • Added

      Response schema for 30 operations that returned an object without a schema. The fields are the ones already sent and the JSON does not change. They are the verdict (GET /datasets/{dataset_id}/verdict and GET /domains/{domain_id}/verdict), the trust decision and trust facts (GET /datasets/{dataset_id}/trust-decision, POST /trust-decisions, GET /datasets/{dataset_id}/trust-facts and GET /domains/{domain_id}/trust-facts), readiness (GET /datasets/{dataset_id}/ai-readiness and GET /domains/{domain_id}/readiness), search and lineage (GET /datasets/find, GET /datasets/{dataset_id}/lineage, …/lineage/traverse, …/lineage/impact and …/lineage/path), the context (GET /datasets/{dataset_id}/context), the data-diff (POST /datasets/{dataset_id}/diff/{other_id}), contracts (GET /datasets/{dataset_id}/contract/versions, …/contract/versions/{number}, …/contract/diff, …/contract/proposals and …/contract/proposals/{proposal_id}), the data policy (GET /workspace/data-policy and GET /domains/{domain_id}/data-policy), the AI Act (GET /compliance/ai-systems, …/ai-systems/{system_id} and …/art10), the orchestrators (POST /integrations/dbt/run-results, GET /orchestrator-runs and POST /orchestrator-runs) and the VS Code extension's reads (GET /ide/resolve and GET /ide/datasets/{dataset_id}/columns). A client generated from this spec now gets these fields, and our breaking-change check now sees inside these responses. Card #455.

  12. v1.67.0

    • Added

      GET /datasets/{dataset_id}/rules/export?format=dbt|sodacl|gx: the dataset's active rules as a file for your repository, as dbt tests (schema.yml, the same as GET /datasets/{id}/rules.dbt.yml, which stays), SodaCL checks (missing_count, duplicate_count, invalid_count with valid regex/valid min/valid max, freshness, row_count) or a Great Expectations 1.x suite (JSON). In SodaCL and GX, only what POST /rules/import reads back as the same rule is written; what is left out (stream rules, check-digit validity, SQL rules, tolerances above zero…) comes with its reason in the file header (meta.datavault.unsupported in GX) and is counted in the X-Rules-Exported, X-Rules-Unsupported and X-Rules-Unsupported-Types headers. GET /rules/export?format=sodacl: the rules of the datasets the caller can see, in a single SodaCL file. POST /rules/import now reads SodaCL invalid_count with validity and multi-column duplicate_count, GX expect_compound_columns_to_be_unique and freshness in minutes (30m), and its dedupe sees a zero tolerance and a missing one, columns: [x] and column: x, 12.0 and 12 as the same rule: importing the file again does not duplicate rules. No LLM and nothing touches the source. Card #403.

  13. v1.66.0

    • Added

      Expected problems: each issue in GET /issues and GET /datasets/{id} gains resolution_reason: expected when it was ignored as expected (a migration, a month-end close), null otherwise. In GET /issues/sla-report, an expected issue counts in opened and in the MTTA, but stays out of resolved, the MTTR and in_scope/breached. Card #401.

  14. v1.65.0

    • Security

      The validation 422 no longer returns the value received: each detail item carries type, loc and msg, and ctx only with the rule's limits (gt, ge, lt, le, multiple_of, min_length, max_length, max_digits, decimal_places). A missing field's input was the whole request body, and on POST /auth/login and POST /auth/register that included the password. The input and ctx fields stay in the ValidationItem schema until the next major, but input is no longer sent. Card #450.

  15. v1.64.0

    • Added

      GET /incidents and GET /incidents/{incident_id}: the incident's blast radius. impact_scope is column when the root's problems are about a column (impact_columns) and column-level lineage (read from the views' SQL) leaves it: impact_tree then only carries the datasets that use those columns and the possible-impact ones, where the column map is missing. It is table at table level, as before, with impact_note: no_column_lineage when the problems were about a column without that map, and none when nothing downstream is affected. Each tree node gains impact (column = certain, table = possible) and via_columns; the incident gains downstream_count, downstream_column_count and exposures_count. On a restricted dataset, via_columns is empty. Card #402.

  16. v1.63.0

    • Added

      GET /rules/{rule_id}/failing-rows.sql?dialect=&limit=: the query that lists the rows failing the rule, as text, for you to run in your warehouse (DataVault does not run it nor store rows). The dialect defaults to the dataset's source (X-DataVault-Dialect), or postgres, mysql, mssql, bigquery, snowflake, databricks and sqlite; names are always quoted and the dialect's LIMIT/TOP is used (limit from 1 to 1000, default 100). A rule with no row-level translation (row_count_min, stream rules, a list above 500 values…) answers 204 with the reason in X-DataVault-Reason. A dvk_* token can call it and the request is recorded in the audit trail as a read (failing_rows_sql_read). Card #400.

  17. v1.62.0

    • Added

      Resolution SLA by severity: GET /issues/sla-report?period=YYYY-MM&group_by=domain|team|severity returns, per group and in total, the issues opened, closed (resolved or ignored) and still unresolved at the end of the month, how many went past the target (breached, out of in_scope), MTTA and MTTR (median and p90, in SLA clock hours: unresolved and excluding snoozes) and the ageing of open issues (lt_1d, d1_3, d3_7, d7_30, gt_30d). Only datasets the caller can see are counted. Each issue in GET /issues gains sla_breached_at (when it went past its severity's target; null within the SLA or without a target) and GET /issues accepts sla=breached. Card #396.

  18. v1.61.0

    • Added

      POST /datasets/{dataset_id}/validate: validates up to 5,000 records (body up to 2 MB; above that, 413) against the dataset's active rules and returns, per record, whether it passed and which rules failed, with a stable code (invalid_check_digit, pattern_mismatch, not_in_reference…) and the rule's params; per rule, how many records passed, failed or were not applicable, with the rule's tolerance; and the batch verdict (pass, warn, fail or unknown). Rules over the whole dataset (unique, freshness, row_count_min…) carry a not_applicable_code. Nothing is stored. A dvk_* token can call it, with its own per-token rate limit (429 with Retry-After). Card #397.

  19. v1.60.0

    • Changed

      4xx errors now always have the same body (schema ErrorResponse; on 422 and 402, ValidationErrorResponse and PlanLimitErrorResponse, with the same fields): detail as before, code (a stable code when there is one — auth_required, token_invalid, token_read_only, workspace_suspended, plan_limit, org_quota, validation_error, rate_limited, scan_queue_full, payload_too_large — otherwise null) and request_id, the same value as the X-Request-ID header. Each error's own fields (plan and limit on 402, retry_after on 429) stay. Card #361.

    • Changed

      The OpenAPI documents errors: 401 and 403 on every authenticated operation, 404 on those with an id in the path, 402 on those that depend on the plan (with plan and limit in the schema), 422 with the same envelope (each item with the fields it had before: loc, msg, type, input and ctx) and the X-Request-ID header on every response. The main schemas (datasets, issues, scans, RAG gate, trust decision) and the responses of the verdict, trust facts, readiness, contract versions, reconciliation and IDE resolution carry examples. Card #361.

  20. v1.59.0

    • Added

      Fields confidence_band (high, low, or exact when the cluster's rows are equal in the compared columns) and reference_precision on the GET /duplicates clusters: confidence is the mean pair similarity, not a probability; the band and the reference precision (share of clusters with that similarity that were real duplicates) come from a synthetic Portuguese benchmark, not from the customer's data. The confidence of a new fuzzy cluster stays below 1, which is now reserved for exact clusters. Card #352.

  21. v1.58.0

    • Added

      Field kind on datasets (table, view, materialized_view, external or unknown): the object type in the source. A view or external profiles at basic over the first rows, unless the dataset has its own standard or deep depth. Card #347.

    • Added

      Fields primary_key, partition and clustering on each column of a dataset's detail: what the source declares about it. Card #347.

    • Added

      Field cost_guarded on datasets: true when scans profile it at basic over the first rows, without counting distinct values (kind view or external, without the dataset's own standard or deep depth). Card #347.

    • Changed

      key_columns becomes optional in the data-diff (POST /datasets/{id}/diff/{other}): without it, the key is the primary key declared on the base, if the candidate has the same columns; otherwise 422 as before. The report carries key_source (request or primary_key). Card #347.

  22. v1.57.0

    • Changed

      The score's consistencia dimension (score_history[].dimensions, GET /datasets/{id}) now deducts the dataset's open or acknowledged schema_drift and anomaly issues, not only the scan's own drift: 15 points per warning and 30 per critical, × (1 − age/K), where age is the number of scans since the issue was last detected and K = DATAVAULT_CONSISTENCY_DECAY_SCANS (5). A resolved or ignored issue stops deducting on the next scan. Structural drift (removed columns, changed type) still counts only in the scan that detects it. Scores already stored do not change. Card #351.

    • Added

      Value consistency_issue in the driver kind of GET /datasets/{id}/score/explain: an open issue whose deduction from consistency changed, with issue_id and params change (opened, decaying, detected_again, gone), age_before, age_after, points_before, points_after and decay_scans. New note consistency_model_changed when one of the scores predates this version. Card #351.

  23. v1.56.0

    • Added

      Numbers computed on samples say so with the same fields: estimated, sample_size and the 95% confidence interval (ci95, or <field>_ci95 when the object carries several numbers). dimensions_meta.unicidade.ci95 in the score; semantic.estimated and semantic.sample_size in the column profile; sample_matches and sample_size in the PII map columns. Card #350.

  24. v1.55.0

    • Added

      Value bi_refresh in the issue type: a failed (subtype failed) or late (delayed) refresh of a Power BI, Tableau or Looker BI dataset, attached to the dataset it reads; impact_json carries bi_dataset_id, bi_dataset_name and the affected dashboards. Card #271.

  25. v1.54.0

    • Added

      Uniform pagination: GET /issues, GET /scans, GET /signals and GET /knowledge now return the filtered total in the X-Total-Count header, as GET /datasets, /rules, /suggestions and /reports already did. GET /scans, GET /signals and GET /orchestrator-runs accept offset. In every listing, limit goes from 1 to 500 (GET /scans, GET /knowledge and GET /orchestrator-runs stopped at 200), offset is ≥ 0 and a limit above the cap returns 422. The body is still a list and each listing's default limit is unchanged. In the hybrid search of GET /knowledge, the total is the size of the fused list that pagination runs over. Card #333.

  26. v1.53.0

    • Added

      BI feed chargeback at GET /bi/chargeback: the token's workspace usage and cost per month, with one row per workspace, per domain and per team (month, dimension, workspace_id, workspace_name, entity_id, entity_name, scans, datasets, reports, llm_tokens, llm_tokens_byok, llm_cost_usd, llm_cost_eur, profile_bytes). An empty entity_id on a domain or team row means no domain or no team. A dataset in two domains counts half in each; a domain's share goes to its owner teams (with no owner, to its editor teams). By default it covers the months of the last 365 days; domain_id keeps only that domain's rows. Card #259.

  27. v1.52.0

    • Added

      Teams as owners: each issue in GET /issues gains assignee_team_id and assignee_team_name (the owning team, besides the person; null when there is no team). GET /issues and GET /issues/board accept assignee_team — a team id, mine (the caller's teams) or none (no team). GET /issues/board gains team_cells, the status × team counts (status, team_id, team_name, count; a null team_id means no team). Assigning the team belongs to the UI and only accepts a team that can read the issue's dataset. Card #240.

  28. v1.51.0

    • Added

      Public showcase for a data product, on the same model as the dataset badge (opaque token, outside the portal, no account needed): GET /public/badge/domains/{token}.svg returns the shields.io-style badge — on the left the month's SLA attainment («SLA 99,2 %») or, with no SLA declared, the score; on the right the readiness for BI, green (ready), amber (ready_with_warnings) or red (not_ready) — with an ETag and Cache-Control: public, max-age=3600. GET /public/domains/{token} returns the JSON for the /p/dominios/{token} page: name, description, owner name, version and status, score, declared SLA and the month's attainment, readiness per use case (rag, ml, agente, bi, with the status and the blocker and warning codes) and, per dataset, name, verdict, score and the public links only when that dataset is also shared, plus generated_at; with an ETag and Cache-Control: no-cache. Never emails, internal ids, columns or samples. Both share the dataset badge's per-IP rate limit; an unknown or revoked token returns 404. Management (GET, PUT and DELETE /domains/{domain_id}/public-share and POST …/public-share/rotate) belongs to the UI: anyone who can read the domain sees it; an admin, the domain owner or an owner team enables, rotates and revokes it. Card #180.

  29. v1.50.0

    • Added

      Remediation rehearsal: each item of GET /suggestions gains rehearsal, the latest read-only rehearsal of the suggestion's diagnosis step (null if it was never rehearsed). It carries version, status (queued, ok, blocked, timeout or error), affected_rows (rows the step 1 query returns, counted at the source), total_rows (the dataset's rows at the last scan), pct, requested_at, ran_at, duration_ms and actor (the email of the admin who asked for it). When nothing was counted, reason carries a code (no_diagnosis, aggregate, limit, not_select, forbidden, function_not_allowed, unknown_table, timeout, source_error, blackout…), params and a pt-PT message as a fallback. With status = ok, the suggestion's impact gets estimated_rows equal to the counted rows, estimated_rows_sample with the previous estimate and rows_source = rehearsal. Counts only: never values or the SQL. Card #178.

  30. v1.49.0

    • Added

      Synthetic data evaluation: GET /datasets/{dataset_id}/synthetic/evaluation?rows=&seed= returns the scorecard for the same rows that GET /synthetic would send with that pair. utility_score (0–100) measures fidelity to the profile (nulls, p05–p95 quantiles normalised by the IQR, total variation distance to the histograms and categorical shares, pair correlations), with utility_components and utility_weights. privacy_verdict is fail on sample_leak, extreme_leak or pii_leak, warn when k_min is below k_threshold (5), counted only over the quasi-identifiers with real-domain values (numeric, temporal or categorical), and pass otherwise. The reason comes as a key (leaks on fail, k_below on warn, null on pass) and params (sample_columns, extreme_columns and pii_columns for leaks; k_min, k_threshold and qi_columns for k_below), from which the UI builds the text, and reason is the same text in pt-PT, as a fallback. Rates, deltas, counts and column names only. Card #174.

    • Changed

      GET /datasets/{dataset_id}/synthetic runs the evaluation before sending: when privacy_verdict = fail it answers 409 and sends no rows (CSV or JSON); the detail carries code = synthetic_privacy_fail, the reason's key and params and the pt-PT message. With format=json, the manifest carries the scorecard in manifest.evaluation. The multi-table pack (POST /synthetic-packs) does not run this evaluation yet: each table's evaluation in the pack manifest stays null. Card #174.

  31. v1.48.0

    • Added

      Multi-table synthetic data pack for dev/test and CI fixtures: POST /synthetic-packs (tag synthetic, allowed to tokens) with exactly one of connection_id, domain_id or dataset_ids (up to max_tables, at most 20), rows_per_table (up to 20,000 by default), seed and format: zip (one CSV per table, schema.sql with keys and FKs, and manifest.json with strategies, FKs, warnings, seed and the «synthetic data — contains no real data» notice) or sqlite (the same tables, with the manifest in the _datavault_manifest table). Columns linked by lineage (cliente_id → clientes) only carry keys that exist in the parent table, with the profile's null rate; in a lineage cycle, the FK that closes it is dropped, with a warning. The same call over the same profiles returns the same bytes. Synchronous, with a time budget (30 s by default): when exceeded, 422 and no file. An unknown field in the body returns 422. Each workspace has a request limit (20 per 10 minutes by default), shared by members and tokens: above it, 429 with Retry-After. A dataset or domain the caller cannot see is «not found». Card #175.

  32. v1.47.0

    • Added

      Trust score blockers and warnings (blockers[] and warnings[] in GET /datasets/{dataset_id}/trust and in the trust of GET /datasets/{dataset_id}) gain params: the numbers the text is built from, per key — critical_issue carries count; schema_change_unacknowledged carries count, grace_days and oldest_days; unused_dataset has no params. message stays in pt-PT. Card #476.

  33. v1.46.0

    • Added

      Deterministic, LLM-free lineage graph traversal (the same queries as the assistant's and the MCP server's get_lineage, impact_of, path_between and find_datasets tools): GET /datasets/{dataset_id}/lineage/traverse?direction=&hops= (upstream and/or downstream up to 3 hops, with the «A → B → C» path, each edge's origin in kinds, the column map and the downstream exposures), GET /datasets/{dataset_id}/lineage/impact?column= (downstream datasets, exposures and destinations and, with the column, the columns derived from it), GET /datasets/{dataset_id}/lineage/path?target_id= (shortest path up to 6 hops, in the flow direction or the reverse one) and GET /datasets/find (combined filters: query, layer, domain, has_pii, min_score, usage_class). Up to 40 datasets and 60 edges (truncated), names only: never an exposure's owner, email or URL. A dataset the caller cannot see is «Dataset restrito». Card #173.

  34. v1.45.0

    • Added

      Data product versions: GET /domains/{domain_id}/versions lists the published versions (newest first) with version (MAJOR.MINOR), bump (major breaks the consumer: a dataset left, a column of the active contract disappeared or the SLA got weaker; minor is compatible), change_summary (in Portuguese) and counts. GET /domains/{domain_id}/versions/{from_version}/diff/{to_version} returns the changes between two versions in the contract-diff format, each with an impact; from_version=latest and to_version=current compare the current state with the last version and carry next_version; from an older version to current, next_version is null. latest as the target or current as the source returns 422. Card #168.

    • Added

      GET /domains and GET /domains/{domain_id} gain status (draft | published | deprecated), version and published_at (null until the first publication). Domains that already existed are published at 1.0. Card #168.

    • Added

      GET /domains/{domain_id}/readiness has two new codes: the deprecated blocker (data product deprecated) and the draft warning (still a draft). Card #168.

  35. v1.44.0

    • Added

      GET /knowledge/graph becomes the estate graph: domain, exposure, metric and term nodes (alongside connection, dataset, rule, issue, signal and kb_entry) and feeds edges (upstream → downstream lineage, with lineage_kind: inferred, declared, parsed, dbt or openlineage; when a pair has several, the non-inferred one wins), member_of (dataset → domain), consumed_by (dataset → dbt exposure), measures (dbt metric → source dataset, resolved by name; an ambiguous name gets no edge) and means (non-deprecated glossary term → dataset, through approved links, with the columns in column). stats.by_edge_kind counts edges by kind. When the graph is cut at 1500 nodes, sources, domains and datasets are kept first. Card #172.

    • Added

      GET /knowledge/graph accepts focus=dataset:<id>: it returns only that dataset's neighbourhood up to hops hops (1 to 3; 2 by default), capped at 400 nodes — the closest are kept first. kinds (comma-separated node kinds) limits the node kinds; the focus kind is always included. hops outside 1–3, kinds with an unknown kind or a focus other than dataset:<id> return 422; a dataset outside the workspace returns 404. Card #172.

  36. v1.43.0

    • Added

      POST /trust/consumptions: an agent, application, pipeline or notebook records the datasets it consumed (up to 50), the purpose and the trust decision it saw (decision_seen; when omitted, the current Trust API decision is stored, with its as_of in trust_as_of). The consumer is created on the first call and identified by name + kind. With consumer.run_ref, repeating the call for the same run creates no new consumptions (replayed). Only ids and labels: unknown fields return 422. consumer.name accepts only letters (any language), digits, spaces and _ . : / -; Slack, Teams or markdown markup returns 422. Returns 201; 404 if a dataset is not in the workspace; 429 with Retry-After above the per-workspace limit. It is a write allowed to dvk_* tokens. A consumption in untrusted emits the consumer_untrusted_read event and a notice to the channels, without opening an issue; channels get at most one notice per hour per consumer and dataset. Card #166.

    • Added

      GET /consumers and GET /datasets/{dataset_id}/consumers: the registered consumers, with consumptions_7d, consumptions_30d, datasets_30d, decisions_7d (last 7 days' consumptions by decision), last_decision, last_purpose and last_consumed_at. On a dataset, the counts and the last decision are that dataset's. GET /consumers sends X-Total-Count. Card #166.

    • Added

      The affected consumers (exposures) of GET /incidents, GET /incidents/{incident_id} and GET /issues/{issue_id}/impact gain node: exposure (dbt, as before) or consumer (a registered consumer with consumptions in the last 30 days). A consumer carries framework, consumptions_7d, last_decision and last_consumed_at for the dataset the impact reaches it through, and type is the consumer kind (agent, application, pipeline, notebook). Card #166.

  37. v1.42.0

    • Added

      Feedback-tuned thresholds: GET /datasets/{dataset_id}/tuning returns, per metric×column (the subtype of anomaly issues), the step in force (threshold, on the classic |z| scale: 3.5 is the calibrated threshold without tuning), whether it is silenced, how many times it was ignored and resolved, and what the next «ignore» does (next_on_ignore). It also lists the rule retunes awaiting approval (retunes). Card #157.

    • Added

      An anomaly's explanation card (GET /issues/{issue_id}/explanation) carries tuned_threshold (the feedback-tuned step that went into z_threshold) and tuned_threshold_label (in Portuguese, e.g. «limiar afinado por feedback: 5,0»). Both are null when the metric has no tuning. Card #157.

  38. v1.41.0

    • Added

      GET /domains/{domain_id}/consumers: who depends on the data product. Users and external consumers registered by hand (source=manual) and the AI systems and dbt exposures that use the domain's datasets (source=derived). by_kind counts the four kinds (user, ai_system, dbt_exposure, external), total adds them up and summary is the impact sentence (in Portuguese), null with no consumers. A consumer with a channel_id gets the notice of a critical SLA breach. Card #165.

    • Added

      GET /domains/{domain_id}/readiness gains consumers: {total, by_kind} with the four consumer kinds always present. It is informative and does not change the status. Card #165.

  39. v1.40.0

    • Added

      GET /rules: each rule has last_result, its latest result in a scan, with status (passed, failed or not_evaluated), scan_id, evaluated_at, observed and threshold. It is null before the first scan with the rule. A not_evaluated rule ran but could not be checked: it opens no issue and does not count towards the score, and reason says why (for example, a referential rule whose reference, on another connection, has more than 200,000 keys). If the reason names a reference dataset the caller cannot see, it comes out without the name. Card #164.

    • Added

      referential rule (referential integrity between datasets): rule_type in GET /rules can now be referential, with params column, ref_dataset_id, ref_column and max_orphan_pct. In GET /datasets/{dataset_id}/rules.dbt.yml it comes out as a relationships test (to: ref('<table>'), field), and in GET /datasets/{dataset_id}/contract and GET /domains/{domain_id}/contract as an invalidValues check with arguments.references (<schema.table>.<column>). A reference to a dataset the caller cannot see is not named in these exports: it is left out of the contract, and rules.dbt.yml only keeps a note in a comment.

  40. v1.39.0

    • Added

      GET /tickets/integrations/{integration_id}/targets: where a ticket integration can create tickets, read live from the provider. In Jira, the projects (key is the project key) with the issue types of each one (issue_types, no sub-tasks); in Linear, the teams (key is the team id and issue_types is empty). Up to 100 targets. default_project and default_issue_type are the target configured on the integration (default_issue_type is null in Linear). Read-only: GET in Jira, a GraphQL *query* in Linear. 404 if the integration is not in this workspace, 402 if the plan does not include alert webhooks (the same condition as POST /tickets), 502 if the provider fails or the anti-SSRF protection refuses the address. Card #375.

  41. v1.38.0

    • Added

      Marking of AI-generated content (AI Act, Article 50(2)): ai_generated, ai_model and generated_at on reports and RCA diagnoses (GET /reports, GET /reports/{report_id}) and on suggestions (GET /suggestions, suggestions in GET /datasets/{dataset_id}). ai_generated is false for offline-mode text, which is deterministic; ai_model and generated_at are then null. On AI suggestions older than this version, and on those the assistant proposes, ai_model is null. The model never carries ARNs or account or project IDs: a cloud resource ID comes out as the model family (for example, claude-sonnet-5) or as undisclosed. Card #511.

    • Added

      GET /datasets/{dataset_id}/readme: ai_generated and ai_model for the draft (the date is the existing generated_at) and published_ai_generated and published_ai_model for the published version, which stays marked after approval. The GET /datasets/{dataset_id}/readme.md export of an AI-generated README starts with YAML front matter (ai-generated, ai-model, ai-generated-at).

    • Added

      GET /reports/{report_id}/export and GET /reports/{report_id}/export.pdf exports (and the public share): <meta name="ai-generated"> (true or false) and, when AI was used, ai-model and ai-generated-at, plus the visible label “Gerado por IA (model, date)” in the header. In the PDF the same values go into the document metadata, with the names without hyphens (aigenerated, aimodel, aigeneratedat).

  42. v1.37.0

    • Added

      GET /datasets/{dataset_id}/parsing: composite fields in the latest profile (addresses, full names, phone numbers with a country code, tax ID and name in the same column, free-text dates and amounts with a currency). Each column has its type (kind), locale (pt, es or en), the success rate on the sample (success_rate, parsed/samples), the coverage of each derived column (components), the masks of the values that are not split (failing_masks, digit → 9, letter → A, no values) and the signature of the suggestion with the read-only SQL and PySpark (suggestion_signature, parse_standardize:<column>). Without a profile, profiled is false.

  43. v1.36.0

    • Added

      GET /tickets?entity_type=…&entity_id=…: the external tickets (Jira, Linear or another tool) linked to an issue, an incident (inc-…), a suggestion, a contract proposal or a column classification, with provider, external_key, url and status (open, in_progress, done or unknown). The status is not synced with the tool yet: a ticket created by DataVault starts open; one linked by hand, or older than this version, stays unknown. Card #374.

    • Added

      tickets on every issue (GET /issues, and open_issues in GET /datasets/{dataset_id}): an issue can now have several external tickets, oldest first.

    • Added

      New values in the trails: field = ticket (ticket created) or ticket_link (existing ticket linked) in GET /issues/{issue_id}/events, with the ticket key in to_value; to_status = ticket in GET /suggestions/{suggestion_id}/events (the suggestion status does not change). Treat a value you do not know as “other”.

    • Deprecated

      external_ticket_url on issues — use tickets. It keeps responding, with the URL of the first linked ticket, and is only removed in a major version, not before 2027-04-05.

  44. v1.35.0

    • Added

      POST /integrations/dbt/run-results: the dbt test/dbt build run in the customer's pipeline sends its results back to DataVault. Body {run_results, manifest, connection_id?} with target/run_results.json (schema v4 to v6) and target/manifest.json from the same project. Each test is matched to the dataset (schema + alias) and to the rule — by the rule_id in config.meta.datavault, otherwise by column and test type. With a rule, the result counts towards GET /datasets/{dataset_id}/verdict (a critical rule failing in dbt gives fail; the next push with pass clears it); without an equivalent rule (singular tests, relationships), it opens or resolves a rule_violation issue with subtype dbt_test (fail/error → critical, warn → warning). Re-sending the same invocation_id writes nothing again (status duplicate). With restricted domains, it only matches datasets the sender can write to; the others come back as no_dataset, with no dataset_id or rule_id. Execution metadata only: closed schema — unknown keys, fields carrying rows (failed_rows, rows…) or a failures that is not a count return 422. Accepts API tokens. Card #394.

    • Added

      dbt_core in OrchestratorSource (source filter of GET /orchestrator-runs): each dbt test received is recorded as a run with the invocation_id as external_run_id. Card #394.

    • Changed

      GET /datasets/{dataset_id}/verdict: each failed entry gains engine — datavault (DataVault scan) or dbt (the customer's dbt test; observed is then the count of failing rows). The verdict is the latest DataVault scan's, with each rule's most recent dbt result on top. Card #394.

  45. v1.34.0

    • Added

      Data product verdict: GET /domains/{domain_id}/verdict aggregates the verdicts of the domain's datasets for the CI gate — fail if any fails, warn if any warns, unknown if none has a verdict (domain without datasets or without scans) and pass otherwise. It returns domain_id, domain_name, verdict, with_policy, counts (how many datasets are pass, warn, fail and unknown) and datasets[], sorted by name: each one is that dataset's GET /datasets/{dataset_id}/verdict, plus dataset_id and dataset_name. with_policy (default true) counts data policy violations, like the per-dataset verdict; with false, quality only. Card #148.

    • Added

      Data product ODCS contracts: GET /domains/{domain_id}/contract returns a multi-document YAML bundle, one ODCS v3.1.0 contract per dataset of the domain, separated by --- and sorted by dataset name (empty for a domain without datasets). Card #148.

    • Added

      A dataset's ODCS contract (GET /datasets/{dataset_id}/contract) states which data product it belongs to: tenant (workspace name), domain and dataProduct (domain name, when the dataset belongs to exactly one; with several, the names go to customProperties as datavault.domains) and team with members: [{username, role: owner}], the owners by name. It never carries emails. Card #148.

  46. v1.33.0

    • Added

      tls_mode and tls_verified on connections (GET /connections and GET /connections/{connection_id}), for Postgres and MySQL/MariaDB only. tls_mode is the configured mode: verify-full (encrypts and verifies the server certificate and hostname, the default for new connections), require (encrypts without verifying) or disable (no TLS). It is null on a connection created before this version, which keeps the old behaviour (encrypts if it can, without verifying). tls_verified is true only with verify-full. On every other connector both are null. Card #449.

  47. v1.32.0

    • Added

      Critical data elements (CDEs) and the regulatory dossier. GET /cde lists the critical elements — a dataset or a column marked for BCBS 239 (bcbs239), Solvency II (solvency2) or another regulation (custom), with a free-text reference and an owner —, filterable by regulation and dataset_id, with the total in the X-Total-Count header. GET /cde/dossier returns the dossier computed on the fly, per regulation and requirement (BCBS 239 principles 3 to 6; Commission Delegated Regulation (EU) 2015/35, Article 19(1) to (3)): each element with its controls per dimension and the latest result of each rule, open issues, SLA, one-hop lineage, ODCS contract and gaps, plus the coverage_pct KPI (required dimensions with a control and a result in the last 30 days). Filters regulation and domain_id. GET /cde/dossier.md and GET /cde/dossier.csv export the same dossier. Statuses per dimension: gap, no_result, failing, passing; per requirement also human (human assessment). No status claims compliance. Deterministic, no generative AI. Card #392.

  48. v1.31.0

    • Added

      suggestions_status on datasets (GET /datasets and GET /datasets/{dataset_id}): AI suggestions no longer delay the scan — the scan becomes completed with the profiles, issues, scores and heuristic suggestions, and the AI ones are generated afterwards, in a queue. Values: pending (being generated), ready, failed (the heuristics remain) or limited (above the per-scan cap of datasets with AI — the heuristics remain); null when there is nothing to generate (AI off, or AI suggestions reused from the previous scan). The scan's stats (GET /scans/{scan_id}) gains ai_suggestions (max_datasets, queued, limited) when the scan left datasets waiting for the AI; duration_s is the scan's duration, without the AI. Card #385.

  49. v1.30.0

    • Added

      resample on POST /scans (and on the returned ScanOut): true makes this scan read a new row sample, with another seed. By default (false) each dataset always reads the same sample, so duplicate clusters and pattern violations do not swing between scans without the data changing. Card #317.

    • Added

      sample_method in dimensions_meta.unicidade of score_history (GET /datasets/{dataset_id}): how the sample was picked — system or bernoulli (the source's native TABLESAMPLE/SAMPLE), hash (primary-key hash), stride (one row in every k of a file), head (the first rows) or full. null on scores older than this version. Card #317.

    • Changed

      The scans' row sample (pattern/required_fields rules, duplicates and the score's uniqueness) is no longer “the first N rows the source returned”: it is picked by the dialect's method, with a fixed seed per dataset; sample_size in dimensions_meta is now that sample's size. The table profile (metrics without a column) gains sample and value_sample (method, rows, fraction, seed, deterministic, covers_table), the duplicates issue's impact_json gains sample, and so do the details of rule results evaluated on the sample. Label quality reads with the same method, and the last_result of GET /datasets/{dataset_id}/label-quality gains sample. On the first scan after this version the sample changes once: duplicate clusters, unicidade and pattern violation percentages may change without the data changing. Card #317.

  50. v1.29.0

    • Added

      Signed outgoing webhooks (the alerts "Generic webhook" channel): every delivery carries X-DataVault-Event, X-DataVault-Delivery (a UUID, the same on every attempt), X-DataVault-Timestamp and X-DataVault-Signature: v1=<HMAC-SHA256 of the channel secret over "{timestamp}.{body}">. Retries on 5xx, timeouts and connection failures (1 s, 5 s and 25 s); a 4xx is final. The body gains occurred_at, and the contract (events, WebhookPayload JSON Schema, verification in Python and Node, 5-minute anti-replay window) is in docs/WEBHOOKS.md. The secret is shown once, when the channel is created or on POST /notification-channels/{id}/rotate-secret; older channels send unsigned until it is rotated. Card #323.

    • Changed

      The generic webhook's weekly digest now goes out with event: "datavault.digest" (previously datavault.alerts with empty lists), and "Test channel" with datavault.test. Card #323.

  51. v1.28.0

    • Added

      GET /issues/{issue_id}/explanation: new fields z_threshold (the |z| threshold applied to the anomaly's baseline), p_value (p-value of the null model calibrated for the baseline size) and confidence (high | medium | low, from the p-value), plus the baseline object (kind, weekday, weekday_label, points), which the response computed but did not expose. For older anomalies p_value and confidence are null and z_threshold is 3.5. Card #319.

    • Changed

      anomaly issues (GET /issues): the threshold is no longer a fixed |z| ≥ 3.5. It now depends on the number of baseline points (20.2 with 5, 7.3 with 8, 5.2 with 12), the baseline must have at least 5 points, and each scan applies Benjamini–Hochberg (α = 0.05) across each dataset's series. Expect fewer anomalies on noisy series. The impact_json of new anomalies carries z_threshold, p_value and confidence. Method and harness figures: docs/METODOS.md, "Anomalias". Card #319.

  52. v1.27.0

    • Added

      GET /datasets/{dataset_id}/score/explain?from=&to=: "why did the score change?" — the score delta between two scans broken down by dimension (weight·Δdimension) and, inside each dimension, by drivers (column_nulls, duplicates, rule_failing/rule_passing, rule_set_changed, freshness, drift, …) with signed points in score points and params to phrase them. Σ drivers + unexplained_points = delta. Defaults to the last two completed scans; 422 without two scored scans. Card #318.

  53. v1.26.0

    • Added

      dimensions_meta on every score_history point of GET /datasets/{dataset_id}: the score dimensions computed on a sample rather than the whole table — today only unicidade (uniqueness), with estimated, sample_size (the first N rows the source returned, not random) and duplicate_rows. Empty ({}) when every dimension covers the whole table and on scores older than this version. Card #294.

    • Fixed

      Score dimension unicidade (uniqueness: dimensions.unicidade and, in turn, score and latest_score): when duplicates are searched in a sample — tables above 5,000 rows or sampled files — the rate is now the sample's, the same basis the duplicates issue already used (“% of the sample”). Previously the sample's duplicates were divided by the whole table's row_count and the dimension stayed close to 100 on large tables. **Scores of large tables with duplicates go down** from the first scan after this version (e.g. 500 redundant rows in the 5,000 read from a 1 M-row table: unicidade 90.0 instead of 100.0, score −2.5 points); scores already stored are not recomputed. Review the min_score thresholds of SLAs and contracts on those tables.

  54. v1.25.0

    • Added

      GET /datasets/{dataset_id}/freshness and the freshness block in GET /datasets/{dataset_id}: the column that measures the dataset's freshness — column (pinned; null = chosen on every scan by name and type: updated/modified > ingest/loaded > created > event > other TIMESTAMP > DATE, never due, expiry, end, birth or deletion columns), the last profile's used_column, detected_column and source (configured, detected, connector), latest_timestamp in UTC, timezone (aware or assumed:<zone>), source_timezone, future_columns (maximum more than 24 h after the profile — left out of the automatic choice), candidates and the dataset's freshness rule (rule_id, rule_column, rule_max_age_hours). Card #292.

    • Added

      source_timezone on connections (GET /connections, GET /connections/{connection_id}): the IANA zone in which the source's timestamps without a zone are read (default UTC, the behaviour so far).

    • Fixed

      freshness rule: the column parameter now measures that column (it used to be ignored and the table's first date column was measured); a timestamp more than 24 h in the future fails the rule with details.error instead of reporting it fresh. In profile table metrics, latest_timestamp is in UTC with its offset (+00:00) and comes with latest_timestamp_column and latest_timestamp_tz.

  55. v1.24.0

    • Added

      GET /datasets/{dataset_id}/rules.dbt-tests.zip: the dataset's active expression and custom_sql rules as dbt singular tests — a ZIP with tests/*.sql (the SELECT of the violating rows in the source's dialect, with ref() in place of the table; each rule's text goes inside Jinja raw blocks, so dbt never runs Jinja written in a rule) and a README listing the rules left out and why.

  56. v1.23.0

    • Changed

      POST /scans (and scan requests from schedules, the dbt Cloud webhook and the SDK): when the workspace already has max_queued_scans requested scans queued or running (5 on the default plan; see the plan catalogue), the API answers 429 with Retry-After (seconds) and the body {"detail", "limit": "queued_scans", "queued", "max_queued", "retry_after_s"} — this is capacity backpressure, not the per-request rate limit (limit tells them apart; the rate limit uses retry_after). Nothing is rejected for good: retrying after retry_after_s enqueues again. Card #251 (enterprise scale).

  57. v1.22.0

    • Added

      GET /governance?months=1..12 and GET /governance/domains/{domain_id}: governance programme effectiveness (the CDO dashboard). Each KPI has a stable code, group (coverage, resolution, sla, adoption, quality, incidents, usage), unit, direction, value with the sample behind it, target and target_source (workspace → organization → default; in a domain with a declared SLA, domain_sla is the SLO goal), status (on_target, at_risk, off_target, no_data, no_target), gap, previous/delta and trend per UTC calendar month (the current one with partial). KPIs: owner, documentation, classification, contract and rule coverage (ownership_pct, documentation_pct, classification_pct, contracts_pct, rules_pct); cycle_time_hours and mttr_critical_hours; slo_attainment_pct and due_date_met_pct; suggestion_adoption_pct and never_again_adoption_pct; score_<dimension> per scorecard dimension; critical_incidents (a lineage cascade counts as one) and active_users_pct/datasets_in_use_pct. Flow KPIs measure the last 30 days (window=last_30d, vs. the previous 30); coverage, usage and scores the current state (window=now, vs. the previous month's close). The workspace one includes domains (the per-domain matrix, with a status per KPI) and targets. Datasets in restricted domains without access do not count.

    • Added

      GET /governance/scorecard.csv (the executive scorecard: one row per KPI with value, goal and its source, status, gap, change and one value per month, plus one per domain × KPI) and GET /governance/evidence.md (the programme evidence in Markdown for the customer's procurement or due-diligence pack: status summary, KPIs with goal and trend, domains and the method). Aggregates only — no dataset or column names, samples or people; CSV cells protected against formula injection.

  58. v1.21.0

    • Added

      GET /catalog/metadata-quality and GET /domains/{domain_id}/metadata-quality: catalog and metadata quality (governance KPIs). pct = checks passed ÷ applicable (non-applicable checks stay out of the denominator; null when nothing is measured), by dimension (completeness, timeliness, consistency) and by check with a stable code — has_owner, has_description, has_classification, has_domain, has_contract, has_rules, description_current (the schema did not change after the description/README), owner_active (owner not deactivated, still in the workspace and signed in within the last 90 days), unused_has_owner (a dataset with no measured usage has an active owner) and column_docs_match_schema — with coverage_pct (% of applicable datasets that pass). findings are the metadata issues (missing_owner, description_stale, owner_inactive, unused_without_owner, …) with suggestions per channel (readme, catalog_import, manual) and the endpoint that fixes them; target (goal, reached, gap_pp), trend (daily snapshot; the whole-workspace one only for whoever sees the whole workspace), domains (per domain) and external_catalog (coverage and glossary terms read from the connected external catalog). The workspace one lists the 50 worst datasets in datasets (datasets_truncated). Datasets in restricted domains without access do not count.

    • Added

      GET /catalog/metadata-quality.csv?level=datasets|summary: CSV export for a governance or procurement pack — one row per dataset with each check's status (cumprido, falhou, n/a) and the issue codes, or the summary of the workspace and each domain with the goal and the coverage per check. Metadata only; cells protected against formula injection.

  59. v1.20.0

    • Added

      GET /datasets/{dataset_id}/pipeline-health: the health of the Fivetran/Airbyte pipelines that write the dataset — status (the worst of ok, failed, delayed, paused, unknown; not_monitored with no integrator connected) and, per pipeline, last success and last failure, the integrator's message, cadence and tolerated delay, the target and source ends (updated_at and age_s: «source updated X ago, target Y ago»), lag_s (how far the target is behind the source), the row-count reconciliation between both ends' profiles (recon, with comparable) and the tables that joined/left the pipeline (schema_change); recent_runs lists the latest syncs. A source in a restricted domain appears as «Dataset restrito», with no table, latency or reconciliation.

    • Added

      GET /orchestrator-runs accepts source=fivetran and source=airbyte: the syncs of the connected integrators, read from their metadata APIs (read-only), with the pipeline in job_id/job_name. Issues gain the pipeline_sync type (subtypes failed and delayed).

  60. v1.19.0

    • Added

      API versioning and deprecation policy (the portal's «Stability» section): what is in this portal is the contract; a minor version only adds; removing, renaming or changing the type or meaning of a field, parameter or code is a breaking change and only happens in a major. A deprecated operation is announced here (type deprecated) and responds with the Deprecation (RFC 9745), Sunset (RFC 8594) and Link with rel="deprecation" headers at least 180 days before it stops responding. CORS exposes these three headers.

    • Added

      GET /health now includes api_version — this API's version, the same as GET /version.

    • Added

      GET /connections/{connection_id}/query-log and GET /connections/{connection_id}/query-log/export: what DataVault ran on the source, per normalised statement shape (no values) — operation (scan, discover, test), scan_id, query_tag, endpoint (primary or replica), executions, errors, total and maximum duration (ms), rows and first/last run. Filters scan_id, operation and days (1–366); the list pages with limit (default 100, maximum 500) / offset and the export returns a download in format=csv (default) or format=json.

    • Added

      GET /connections and GET /connections/{connection_id} now carry the source policy set by the DBA: blackout_timezone, blackout_windows (recurring windows in local time), blackout_active (the current window, or null), blackout_upcoming (next occurrences), max_concurrent_queries and max_sample_rows. GET /connections/types now carries each connector's label, group and docs_anchor.

    • Changed

      POST /scans responds 409 with detail = {code, message, …} when the source is inside a blackout window (code: blackout_active) or already runs its maximum number of scans (code: source_busy); no query reaches the source. The new override_blackout and override_reason fields (a reason of at least 5 characters, audited) force the scan inside a window, but only for admins — with an API token they return 403.

  61. v1.18.0

    • Added

      Remote MCP server in production: https://mcp.praxis.lbostuff.tech/mcp (MCP Streamable HTTP, TLS). Every request is authenticated with a dvk_ API token (Authorization: Bearer) validated against the API — without a token, 401 — and it exposes the same read-only tools and resources (including get_verdict and get_trust_decision) over the token's workspace. No backend code needed: claude mcp add --transport http datavault https://mcp.praxis.lbostuff.tech/mcp --header "Authorization: Bearer dvk_…". Requests whose Host header is outside the allowlist get 421 (DNS-rebinding protection), and those with a cross-site Origin get 403.

    • Changed

      GET /ide/agent-config now returns, in production, remote_available: true, mcp_url set to the remote MCP server and local_server_cmd: null: the configurations generated for Claude Code, Claude Desktop, Cursor, VS Code and Codex point to it instead of http://127.0.0.1:8765/mcp.

  62. v1.17.0

    • Added

      Data quality scorecard by dimension (DAMA/ISO 8000): GET /datasets/{id}/dimensions returns, for completeness, uniqueness, validity, timeliness, consistency, reasonableness, accuracy and integrity, the 0–100 score of the latest scan (share of passed checks), the number of checks, the open issues and the per-scan history. A dimension without checks comes with measured: false and score: null ("not measured"), never 100. GET /dashboard/dimensions aggregates per workspace and per domain using only the measured datasets (datasets_measured out of datasets_total), and GET /dashboard/dimensions.csv exports one row per dataset × dimension. The composite score is unchanged.

    • Added

      GET /issues accepts ?dimension= (e.g. completeness) and every issue now carries a dimension field, resolved by the deterministic type/subtype → dimension map: each issue lands in exactly one dimension. An unknown dimension returns 422.

  63. v1.16.0

    • Added

      GET /version (no authentication): {version, build_sha, api_version} — the product version (semver, 1.0.0 for the first stable release), the 7 hex characters of the commit being served and this API's version, which keeps its own semver. GET /health and GET /health/ready now include build_sha. Useful to quote the version in force in a support request or a contract.

  64. v1.15.0

    • Added

      GET /rules, GET /suggestions, GET /reports and GET /duplicates now return the filtered total in the X-Total-Count header (same filters as the request, before limit/offset, and only what the caller can see under domain permissions), like GET /datasets. The body is still the page as a list; limit defaults to 50, maximum 500.

  65. v1.14.0

    • Added

      GET /bi and GET /bi/{feed}: stable tabular feeds for Excel and Power BI — scores (latest score per dataset, day and domain), issues, slo (dataset-day), finops (current cost snapshot per dataset), activity (issues and scans) and the datasets dimension. JSON ({feed, schema_version: 1, columns, total, limit, offset, next_offset, rows}) or CSV (format=csv, optional bom=1) on the same route; limit (≤ 10,000, default 5000) / offset pagination with a stable order; filters since, until (UTC, 90-day default window for scores/slo/activity), domain_id, dataset_id and status (issues only). Fixed snake_case columns, ISO UTC dates and dot decimals; columns are only ever appended. ETag and If-None-Match → 304; X-Total-Count and X-Next-Offset. The catalog carries the Power Query (M) code. Read-only, no OData; accepts API tokens.

  66. v1.13.0

    • Added

      GET /ide/agent-config?tool=: ready-to-paste MCP configuration for coding agents (claude-code, claude-desktop, cursor, vscode, codex; omitted = all) — file (.mcp.json, claude_desktop_config.json, .cursor/mcp.json, .vscode/mcp.json, ~/.codex/config.toml), install command and rules snippets (AGENTS.md, CLAUDE.md, .cursorrules, .github/copilot-instructions.md). Never returns the token: it carries token_placeholder and, where the tool supports it, reads it from DATAVAULT_TOKEN. Read-only.

  67. v1.12.0

    • Added

      GET /datasets/{id}/trust-facts and GET /domains/{id}/trust-facts (?detail=minimal|full, default minimal): trust facts in a stable envelope (schema_version: 1) — verdict with policy and the agente decision, quality score and 7-day trend, trust score, contract (status and active version), policy (status and violated rules), sensitivity counts per level, AI-readiness per use case, freshness vs target and 30-day attainment, domains and owner, upstream/downstream counts, last scan, open issues per severity and relative links; the domain variant adds an aggregate plus each dataset's facts. Metadata only: minimal never includes column names. ETag (hash of the facts, without as_of) and If-None-Match → 304; Cache-Control: private, no-cache. Read-only, no LLM; accepts API tokens.

  68. v1.11.0

    • Added

      GET /domains/{id}/readiness?use_case= (rag, ml, agente, bi; default bi): data product readiness per use case — ready | ready_with_warnings | not_ready, blockers[] and warnings[] with a stable code (no_datasets, no_scan, stale_scan, verdict_fail, verdict_warn, contract_invalid, ai_readiness_blocked, open_critical_issues, slo_breach_open, attainment_below_objective), dataset_id, dataset_name and message, per-dataset detail, monthly SLA, checked_at and valid_until (next scheduled scan). Read-only, no LLM; accepts API tokens.

  69. v1.10.0

    • Added

      Trust decision (GET /datasets/{id}/trust-decision, POST /trust-decisions): new fields required_actions[] (what is missing to move up; never empty for untrusted/unknown) and valid_until (as_of + ttl_seconds). Additive only — existing fields, codes and TTL are unchanged. Every check made with an API token is recorded in the audit trail (trust_decision_checked, no PII; a batch is a single event).

  70. v1.9.0

    • Added

      GET /datasets/{id}/trust-decision?purpose= and POST /trust-decisions (≤ 50 ids): trust decision before consuming a dataset for a purpose (agente, rag, treino, bi) — trusted | caution | untrusted | unknown, reasons with a stable code and message_pt, verdict, AI-readiness, freshness vs SLA, open incidents, unmasked PII, contract and ttl_seconds (also in Cache-Control: max-age). Read-only; POST accepts API tokens.

  71. v1.8.0

    • Added

      GET /ide/resolve?ref= and GET /ide/datasets/{id}/columns: quality summary per table referenced in a SQL/dbt file (score, open issues, active contract, PII, data policy violations) and per-column profile (type, null %, distinct, classification) — powers the VS Code extension.

    • Changed

      POST /datasets/{id}/diff/{other_id} now accepts API tokens — the data-diff only reads both sources and persists nothing (CI and VS Code extension).

  72. v1.7.0

    • Added

      GET /datasets/{id}/contract/versions, GET /datasets/{id}/contract/versions/{n}, GET /datasets/{id}/contract/diff?from=&to= and GET /datasets/{id}/contract/proposals[/{id}]: numbered versions of the ODCS contract agreed with the source producer, section-by-section diff and proposals under review.

    • Changed

      GET /datasets/{id}/contract accepts source=active to download the contract accepted by the producer, and GET /datasets/{id}/verdict now validates the dataset against that contract (contract.active_version): a promised column that disappeared ⇒ fail; diverging type, required column accepting nulls or a check without an active rule ⇒ warn.

  73. v1.6.0

    • Added

      API-wide rate limiting: each API token has its own limit (300 requests/min by default, on top of the per-IP limit) and the GDPR export and report requests have additional limits. Responses carry X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset; above the limit the API returns 429 with Retry-After (seconds) and retry_after in the body.

  74. v1.5.0

    • Changed

      POST /scans now accepts API tokens — orchestrators (Airflow, CI) trigger scans and wait for the verdict with GET /datasets/{id}/verdict. Subject to the plan's scan quota.

    • Added

      GET /orchestrator-runs and POST /orchestrator-runs: orchestrator runs (dbt Cloud, Airflow) kept as root-cause diagnosis context ("dbt job X failed 20 min before this issue"). POST accepts API tokens; run metadata only.

  75. v1.4.0

    • Added

      GET /reports/{id}/trace: path of each report generation as a graph (source, scope, profile, rules, anomalies, signals, brief, writing, AI label, export, delivery), with timings and fallbacks used. lang=pt|en.

    • Added

      GET /routines/blocks: versioned catalogue of the report pipeline steps — inputs, outputs, what is configurable today and each step's badges.

  76. v1.3.0

    • Added

      POST /rag-gate: quality gate for RAG pipelines — accept/review/reject verdict per document (personal data, corpus duplicates, language, boilerplate) before indexing. Accepts API tokens; no content is stored.

  77. v1.2.0

    • Added

      GET /datasets/{id}/verdict: compact pass/warn/fail/unknown verdict for CI gates.

    • Added

      GET /datasets/{id}/schema-changes: schema change history with downstream impact.

    • Added

      GET /incidents and GET /incidents/{id}: related issues grouped into a single incident through lineage.

    • Added

      GET /knowledge and GET /knowledge/graph: knowledge base search and relationship graph.

    • Added

      GET /dashboard/pii and GET /dashboard/pii.csv: workspace PII map — datasets × personal data types (email, tax ID, phone, address, health…), detection source, active-rule coverage and CSV export for the DPO. Metadata only, never the values.

    • Added

      GET /dashboard/slo and GET /dashboard/slo.csv: attainment of the ODCS contract's latency slaProperties against the age observed in profiles — monthly and per-dataset attainment, breaches with that day's issues and CSV export.

    • Added

      GET /lineage/sankey: lineage by medallion layer (Bronze → Silver → Gold) with score, estimated volume and risk inherited from upstream.

    • Added

      GET /catalog/treemap: the whole catalogue aggregated by source and schema, with score and risk.

  78. v1.1.0

    • Added

      GET /datasets/{id}/contract (ODCS v3.1) and GET /datasets/{id}/contract.dbt.yml: exportable data contract.

    • Changed

      API tokens are now available from the Team plan upwards.

  79. v1.0.0

    • Added

      Read-only dvk_* API tokens, created and revoked by workspace admins.

    • Added

      Read access to datasets, scans, issues, suggestions, rules, lineage and AI-readiness under /api/v1.

    • Security

      Tokens are stored only as a SHA-256 hash; any method other than GET returns 403.

No workspace yet?

Start on the free plan and move to Team when you need API tokens.