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.
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.
Call the API
Send the token in the Authorization: Bearer header. Every response is JSON (except the .yml, .sql and .md exports).
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"import os
import httpx
client = httpx.Client(
base_url="https://api.praxis.lbostuff.tech/api/v1",
headers={"Authorization": f"Bearer {os.environ['DATAVAULT_TOKEN']}"},
timeout=30,
)
resp = client.get("/datasets", params={"limit": 100})
resp.raise_for_status()
print("total:", resp.headers["X-Total-Count"])
for ds in resp.json():
print(ds["name"], ds["latest_score"])const res = await fetch(
"https://api.praxis.lbostuff.tech/api/v1/issues?severity=critical&status=open",
{ headers: { Authorization: `Bearer ${process.env.DATAVAULT_TOKEN}` } },
);
if (!res.ok) throw new Error(`BeryX ${res.status}`);
const issues = await res.json();
for (const issue of issues) {
console.log(issue.dataset_name, issue.title);
}// npx openapi-typescript https://beryx.lbostuff.tech/developers/openapi.json -o datavault.d.ts
// npm i openapi-fetch
import createClient from "openapi-fetch";
import type { paths } from "./datavault";
const dv = createClient<paths>({
baseUrl: "https://api.praxis.lbostuff.tech",
headers: { Authorization: `Bearer ${process.env.DATAVAULT_TOKEN}` },
});
const { data, error } = await dv.GET("/api/v1/datasets/{dataset_id}/verdict", {
params: { path: { dataset_id: "…" } },
});# pipx install openapi-python-client
# openapi-python-client generate --url https://beryx.lbostuff.tech/developers/openapi.json
import os
from beryx_api_client import AuthenticatedClient
from beryx_api_client.api.datasets import list_datasets_api_v1_datasets_get
client = AuthenticatedClient(
base_url="https://api.praxis.lbostuff.tech",
token=os.environ["DATAVAULT_TOKEN"],
)
datasets = list_datasets_api_v1_datasets_get.sync(client=client, limit=100)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 1Stability
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/v1operations 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
codeof reasons and blockers (trust decision, readiness) are stable; envelopes withschema_version(trust facts, BI feeds) only changeschema_versionin 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
codeor an HTTP status. A breaking change only ships in one of two ways: under a new path version (/api/v2), leaving/api/v1unchanged, 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 theDeprecation(RFC 9745 — since when),Sunset(RFC 8594 — when it stops responding) andLinkwithrel="deprecation"(the migration note) headers. Exception: a security fix may shorten the window and is logged in the changelog with typesecurity.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 responds429withRetry-Afterand, 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-IDof the response (errors also carry it inrequest_id) and the response ofGET /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 policyThe 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.
v1.78.0
- Added
access_roleinGET /api/v1/datasets/{dataset_id}: the caller's role on that dataset,viewer,editororowner. 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-onlydvk_*token stays read-only whatever the role. Card #586.
- Added
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-TimestampandX-BeryX-Signatureand, until that date, also theX-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 readBERYX_<NAME>andDATAVAULT_<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 namedberyx, and the configurations generated in “Connect your agent” useberyxandBERYX_TOKEN.GET /workspace/configdownloadsberyx.yaml;beryx config plan|applyandscripts/workspace_config.py importwithout a path readberyx.yamland, if it does not exist,datavault.yaml. In the dbt export (theschema.ymland the singular tests inrules.dbt-tests.zip), the rule goes inconfig.meta.beryx, and the GX suite carries the table andunsupportedinmeta.beryx; the import and therun_results.jsonmatching readmeta.beryxandmeta.datavault, with the new one winning. The webhook User-Agent becomesBeryX-Webhooks/1 DataVault-Webhooks/1: a filter that looks forDataVault-Webhooksstill accepts the deliveries. Unchanged: asset names in Collibra, the OpenLineage namespace, thedvk_token prefix, the event types (datavault.alerts…) and the names of the exported files (tests/datavault_*.sqland the<table>.datavaultsuite), so the tests are not duplicated in your repository.
- Deprecated
v1.76.0
- Security
POST /synthetic-packsruns the utility × privacy evaluation ofGET /datasets/{dataset_id}/syntheticon each table's rows before writing the file. Withprivacy_verdict = failin a table, it answers 409 and sends no file. Thedetailcarriescode = synthetic_privacy_fail, themessagein pt-PT and, intables, thename,dataset_id,keyandparamsof each refused table. Each table'sevaluationin the manifest is no longernulland 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.
- Security
v1.75.0
- Added
languageinGET /reportsandGET /reports/{report_id}: the language of thecontent_mdtext (pt,enores). The LLM writes in the workspace language (workspace.default_locale) and the offline template writes inpt; older reports come withnulland are inpt. Section headings stay in Portuguese. Card #355.
- Added
v1.74.0
- Added
GET /api/v1/suggestions: thecategoryfilter acceptsenrichment, 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.
- Added
v1.73.0
- Added
Outbound webhooks:
code(a stable code in English and snake_case, e.g.alerts_new,escalation_level,digest_weekly) andparams(only numbers and identifiers) in everyWebhookPayloadevent, andtitle/bodyin the channel's language: thelocalein the channel'sconfig, otherwiseworkspace.default_locale, otherwise pt-PT. On email channels, each recipient gets the message in their own language (the account'slocale, 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:
detailfollows the person's language (the accountlocale), otherwise the workspace default, otherwise theAccept-Languageheader (pt, en or es; de and fr get en), otherwise pt-PT. Each catalogued error carries its stablecode, including those that had anullcodeuntil now. This also covers the plan 402/403, the rate-limit and scan-queue 429, the body-size 413 and themessageof a structureddetail({code, message}), whosecodestays the same. With no known language, the text is the same as before. Card #519.
- Added
v1.72.0
- Added
GET /api/v1/datasets/{dataset_id}: each profile column'ssemanticgainsregion. For thephonetype it is the country of the numbering plan (ISO 3166-1 alpha-2), taken from the data country; for every other type and forphone_pt(which is PT) it isnull. Card #516.
- Added
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) andtimezone(IANA) inGET /auth/me, plusworkspace.default_localeandworkspace.timezone.PATCH /auth/mesaves the person's own (only the keys sent change; null falls back to the workspace default) andGET/PUT /workspace/localemanage the workspace defaults (admin). Card #518. - Added
Schedules with a time zone:
timezone(IANA) in the body ofPATCH /connections/{connection_id}/scheduleandscan_timezoneinGET /connections;timezoneinPOST/PATCH /report-schedulesand 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 inUTCand keep their hour; withouttimezone, thePATCHkeeps the connection's time zone. Card #518.
- Added
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 3viacolumns, the edge kinds,inferred_onlyand the evidencesignature; ties come inambiguous, with the candidates. Admins see all of them; domain managers only those for their domains; everyone else gets403. An invisible neighbour counts towards the total and comes as “Dataset restrito” (restricted dataset).has_lineagesays whether any lineage link touches a visible dataset, becausecounts.with_lineageonly counts datasets without a domain. Card #468. - Added
Metadata quality: the
missing_domainissue gains a suggestion withchannel: "lineage"when datasets without a domain have a lineage suggestion (“Juntar pela linhagem — N com sugestão”, withlinkto/datasets?vista=dominiosandendpointGET /api/v1/domains/suggestions). New value in thechannelenum ofMetadataSuggestionOut. Card #468.
- Added
v1.69.0
- Added
Data country (ISO 3166-1 alpha-2):
data_countryinGET /connectionsandGET /connections/{connection_id}(the source's; null = the workspace's),data_countryinGET /datasetsandGET /datasets/{dataset_id}(the dataset's own; null = inherited) andcountryin the dataset detail, with theeffectivecountry the scans use (dataset → connection → workspace) and thesourceit comes from.GET /datasets/{dataset_id}/data-countryreturns the same object. Workspaces default toPT, 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.
- Added
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}/verdictandGET /domains/{domain_id}/verdict), the trust decision and trust facts (GET /datasets/{dataset_id}/trust-decision,POST /trust-decisions,GET /datasets/{dataset_id}/trust-factsandGET /domains/{domain_id}/trust-facts), readiness (GET /datasets/{dataset_id}/ai-readinessandGET /domains/{domain_id}/readiness), search and lineage (GET /datasets/find,GET /datasets/{dataset_id}/lineage,…/lineage/traverse,…/lineage/impactand…/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/proposalsand…/contract/proposals/{proposal_id}), the data policy (GET /workspace/data-policyandGET /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-runsandPOST /orchestrator-runs) and the VS Code extension's reads (GET /ide/resolveandGET /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.
- Added
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 asGET /datasets/{id}/rules.dbt.yml, which stays), SodaCL checks (missing_count,duplicate_count,invalid_countwithvalid regex/valid min/valid max,freshness,row_count) or a Great Expectations 1.x suite (JSON). In SodaCL and GX, only whatPOST /rules/importreads 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.unsupportedin GX) and is counted in theX-Rules-Exported,X-Rules-UnsupportedandX-Rules-Unsupported-Typesheaders.GET /rules/export?format=sodacl: the rules of the datasets the caller can see, in a single SodaCL file.POST /rules/importnow reads SodaCLinvalid_countwith validity and multi-columnduplicate_count, GXexpect_compound_columns_to_be_uniqueand freshness in minutes (30m), and its dedupe sees a zero tolerance and a missing one,columns: [x]andcolumn: x,12.0and12as the same rule: importing the file again does not duplicate rules. No LLM and nothing touches the source. Card #403.
- Added
v1.66.0
- Added
Expected problems: each issue in
GET /issuesandGET /datasets/{id}gainsresolution_reason:expectedwhen it was ignored as expected (a migration, a month-end close), null otherwise. InGET /issues/sla-report, an expected issue counts inopenedand in the MTTA, but stays out ofresolved, the MTTR andin_scope/breached. Card #401.
- Added
v1.65.0
- Security
The validation
422no longer returns the value received: eachdetailitem carriestype,locandmsg, andctxonly with the rule's limits (gt,ge,lt,le,multiple_of,min_length,max_length,max_digits,decimal_places). A missing field'sinputwas the whole request body, and onPOST /auth/loginandPOST /auth/registerthat included the password. Theinputandctxfields stay in theValidationItemschema until the next major, butinputis no longer sent. Card #450.
- Security
v1.64.0
- Added
GET /incidentsandGET /incidents/{incident_id}: the incident's blast radius.impact_scopeiscolumnwhen the root's problems are about a column (impact_columns) and column-level lineage (read from the views' SQL) leaves it:impact_treethen only carries the datasets that use those columns and the possible-impact ones, where the column map is missing. It istableat table level, as before, withimpact_note: no_column_lineagewhen the problems were about a column without that map, andnonewhen nothing downstream is affected. Each tree node gainsimpact(column= certain,table= possible) andvia_columns; the incident gainsdownstream_count,downstream_column_countandexposures_count. On a restricted dataset,via_columnsis empty. Card #402.
- Added
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), orpostgres,mysql,mssql,bigquery,snowflake,databricksandsqlite; names are always quoted and the dialect'sLIMIT/TOPis used (limitfrom 1 to 1000, default 100). A rule with no row-level translation (row_count_min, stream rules, a list above 500 values…) answers204with the reason inX-DataVault-Reason. Advk_*token can call it and the request is recorded in the audit trail as a read (failing_rows_sql_read). Card #400.
- Added
v1.62.0
- Added
Resolution SLA by severity:
GET /issues/sla-report?period=YYYY-MM&group_by=domain|team|severityreturns, 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 ofin_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 inGET /issuesgainssla_breached_at(when it went past its severity's target; null within the SLA or without a target) andGET /issuesacceptssla=breached. Card #396.
- Added
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 stablecode(invalid_check_digit,pattern_mismatch,not_in_reference…) and the rule'sparams; per rule, how many records passed, failed or were not applicable, with the rule's tolerance; and the batchverdict(pass,warn,failorunknown). Rules over the whole dataset (unique,freshness,row_count_min…) carry anot_applicable_code. Nothing is stored. Advk_*token can call it, with its own per-token rate limit (429withRetry-After). Card #397.
- Added
v1.60.0
- Changed
4xxerrors now always have the same body (schemaErrorResponse; on422and402,ValidationErrorResponseandPlanLimitErrorResponse, with the same fields):detailas 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— otherwisenull) andrequest_id, the same value as theX-Request-IDheader. Each error's own fields (planandlimiton 402,retry_afteron 429) stay. Card #361. - Changed
The OpenAPI documents errors:
401and403on every authenticated operation,404on those with an id in the path,402on those that depend on the plan (withplanandlimitin the schema),422with the same envelope (each item with the fields it had before:loc,msg,type,inputandctx) and theX-Request-IDheader 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.
- Changed
v1.59.0
- Added
Fields
confidence_band(high,low, orexactwhen the cluster's rows are equal in the compared columns) andreference_precisionon theGET /duplicatesclusters:confidenceis 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. Theconfidenceof a new fuzzy cluster stays below 1, which is now reserved for exact clusters. Card #352.
- Added
v1.58.0
- Added
Field
kindon datasets (table,view,materialized_view,externalorunknown): the object type in the source. Avieworexternalprofiles atbasicover the first rows, unless the dataset has its ownstandardordeepdepth. Card #347. - Added
Fields
primary_key,partitionandclusteringon each column of a dataset's detail: what the source declares about it. Card #347. - Added
Field
cost_guardedon datasets:truewhen scans profile it atbasicover the first rows, without counting distinct values (kindvieworexternal, without the dataset's ownstandardordeepdepth). Card #347. - Changed
key_columnsbecomes 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 carrieskey_source(requestorprimary_key). Card #347.
- Added
v1.57.0
- Changed
The score's
consistenciadimension (score_history[].dimensions,GET /datasets/{id}) now deducts the dataset's open or acknowledgedschema_driftandanomalyissues, 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_issuein the driverkindofGET /datasets/{id}/score/explain: an open issue whose deduction from consistency changed, withissue_idandparamschange(opened,decaying,detected_again,gone),age_before,age_after,points_before,points_afteranddecay_scans. New noteconsistency_model_changedwhen one of the scores predates this version. Card #351.
- Changed
v1.56.0
- Added
Numbers computed on samples say so with the same fields:
estimated,sample_sizeand the 95% confidence interval (ci95, or<field>_ci95when the object carries several numbers).dimensions_meta.unicidade.ci95in the score;semantic.estimatedandsemantic.sample_sizein the column profile;sample_matchesandsample_sizein the PII map columns. Card #350.
- Added
v1.55.0
- Added
Value
bi_refreshin the issuetype: a failed (subtypefailed) or late (delayed) refresh of a Power BI, Tableau or Looker BI dataset, attached to the dataset it reads;impact_jsoncarriesbi_dataset_id,bi_dataset_nameand the affecteddashboards. Card #271.
- Added
v1.54.0
- Added
Uniform pagination:
GET /issues,GET /scans,GET /signalsandGET /knowledgenow return the filtered total in theX-Total-Countheader, asGET /datasets,/rules,/suggestionsand/reportsalready did.GET /scans,GET /signalsandGET /orchestrator-runsacceptoffset. In every listing,limitgoes from 1 to 500 (GET /scans,GET /knowledgeandGET /orchestrator-runsstopped at 200),offsetis ≥ 0 and alimitabove the cap returns 422. The body is still a list and each listing's defaultlimitis unchanged. In the hybrid search ofGET /knowledge, the total is the size of the fused list that pagination runs over. Card #333.
- Added
v1.53.0
- Added
BI feed
chargebackatGET /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 emptyentity_idon 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 itsownerteams (with noowner, to itseditorteams). By default it covers the months of the last 365 days;domain_idkeeps only that domain's rows. Card #259.
- Added
v1.52.0
- Added
Teams as owners: each issue in
GET /issuesgainsassignee_team_idandassignee_team_name(the owning team, besides the person; null when there is no team).GET /issuesandGET /issues/boardacceptassignee_team— a team id,mine(the caller's teams) ornone(no team).GET /issues/boardgainsteam_cells, the status × team counts (status,team_id,team_name,count; a nullteam_idmeans no team). Assigning the team belongs to the UI and only accepts a team that can read the issue's dataset. Card #240.
- Added
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}.svgreturns 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 anETagandCache-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, plusgenerated_at; with anETagandCache-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,PUTandDELETE /domains/{domain_id}/public-shareandPOST …/public-share/rotate) belongs to the UI: anyone who can read the domain sees it; an admin, the domain owner or anownerteam enables, rotates and revokes it. Card #180.
- Added
v1.50.0
- Added
Remediation rehearsal: each item of
GET /suggestionsgainsrehearsal, the latest read-only rehearsal of the suggestion's diagnosis step (null if it was never rehearsed). It carriesversion,status(queued,ok,blocked,timeoutorerror),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_msandactor(the email of the admin who asked for it). When nothing was counted,reasoncarries acode(no_diagnosis,aggregate,limit,not_select,forbidden,function_not_allowed,unknown_table,timeout,source_error,blackout…),paramsand a pt-PTmessageas a fallback. Withstatus = ok, the suggestion'simpactgetsestimated_rowsequal to the counted rows,estimated_rows_samplewith the previous estimate androws_source = rehearsal. Counts only: never values or the SQL. Card #178.
- Added
v1.49.0
- Added
Synthetic data evaluation:
GET /datasets/{dataset_id}/synthetic/evaluation?rows=&seed=returns the scorecard for the same rows thatGET /syntheticwould 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), withutility_componentsandutility_weights.privacy_verdictisfailonsample_leak,extreme_leakorpii_leak,warnwhenk_minis belowk_threshold(5), counted only over the quasi-identifiers with real-domain values (numeric, temporal or categorical), andpassotherwise. The reason comes as akey(leaksonfail,k_belowonwarn, null onpass) andparams(sample_columns,extreme_columnsandpii_columnsforleaks;k_min,k_thresholdandqi_columnsfork_below), from which the UI builds the text, andreasonis the same text in pt-PT, as a fallback. Rates, deltas, counts and column names only. Card #174. - Changed
GET /datasets/{dataset_id}/syntheticruns the evaluation before sending: whenprivacy_verdict = failit answers 409 and sends no rows (CSV or JSON); thedetailcarriescode = synthetic_privacy_fail, the reason'skeyandparamsand the pt-PTmessage. Withformat=json, themanifestcarries the scorecard inmanifest.evaluation. The multi-table pack (POST /synthetic-packs) does not run this evaluation yet: each table'sevaluationin the pack manifest stays null. Card #174.
- Added
v1.48.0
- Added
Multi-table synthetic data pack for dev/test and CI fixtures:
POST /synthetic-packs(tagsynthetic, allowed to tokens) with exactly one ofconnection_id,domain_idordataset_ids(up tomax_tables, at most 20),rows_per_table(up to 20,000 by default),seedandformat:zip(one CSV per table,schema.sqlwith keys and FKs, andmanifest.jsonwith strategies, FKs, warnings, seed and the «synthetic data — contains no real data» notice) orsqlite(the same tables, with the manifest in the_datavault_manifesttable). 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 withRetry-After. A dataset or domain the caller cannot see is «not found». Card #175.
- Added
v1.47.0
- Added
Trust score blockers and warnings (
blockers[]andwarnings[]inGET /datasets/{dataset_id}/trustand in thetrustofGET /datasets/{dataset_id}) gainparams: the numbers the text is built from, perkey—critical_issuecarriescount;schema_change_unacknowledgedcarriescount,grace_daysandoldest_days;unused_datasethas no params.messagestays in pt-PT. Card #476.
- Added
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_betweenandfind_datasetstools):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 inkinds, 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) andGET /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.
- Added
v1.45.0
- Added
Data product versions:
GET /domains/{domain_id}/versionslists the published versions (newest first) withversion(MAJOR.MINOR),bump(majorbreaks the consumer: a dataset left, a column of the active contract disappeared or the SLA got weaker;minoris 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 animpact;from_version=latestandto_version=currentcompare the current state with the last version and carrynext_version; from an older version tocurrent,next_versionisnull.latestas the target orcurrentas the source returns 422. Card #168. - Added
GET /domainsandGET /domains/{domain_id}gainstatus(draft | published | deprecated),versionandpublished_at(nulluntil the first publication). Domains that already existed arepublishedat1.0. Card #168. - Added
GET /domains/{domain_id}/readinesshas two new codes: thedeprecatedblocker (data product deprecated) and thedraftwarning (still a draft). Card #168.
- Added
v1.44.0
- Added
GET /knowledge/graphbecomes the estate graph:domain,exposure,metricandtermnodes (alongsideconnection,dataset,rule,issue,signalandkb_entry) andfeedsedges (upstream → downstream lineage, withlineage_kind:inferred,declared,parsed,dbtoropenlineage; when a pair has several, the non-inferredone wins),member_of(dataset → domain),consumed_by(dataset → dbt exposure),measures(dbt metric → source dataset, resolved by name; an ambiguous name gets no edge) andmeans(non-deprecated glossary term → dataset, through approved links, with the columns incolumn).stats.by_edge_kindcounts edges by kind. When the graph is cut at 1500 nodes, sources, domains and datasets are kept first. Card #172. - Added
GET /knowledge/graphacceptsfocus=dataset:<id>: it returns only that dataset's neighbourhood up tohopshops (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.hopsoutside 1–3,kindswith an unknown kind or afocusother thandataset:<id>return 422; a dataset outside the workspace returns 404. Card #172.
- Added
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 itsas_ofintrust_as_of). The consumer is created on the first call and identified byname+kind. Withconsumer.run_ref, repeating the call for the same run creates no new consumptions (replayed). Only ids and labels: unknown fields return 422.consumer.nameaccepts 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 withRetry-Afterabove the per-workspace limit. It is a write allowed todvk_*tokens. A consumption inuntrustedemits theconsumer_untrusted_readevent 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 /consumersandGET /datasets/{dataset_id}/consumers: the registered consumers, withconsumptions_7d,consumptions_30d,datasets_30d,decisions_7d(last 7 days' consumptions by decision),last_decision,last_purposeandlast_consumed_at. On a dataset, the counts and the last decision are that dataset's.GET /consumerssendsX-Total-Count. Card #166. - Added
The affected consumers (
exposures) ofGET /incidents,GET /incidents/{incident_id}andGET /issues/{issue_id}/impactgainnode:exposure(dbt, as before) orconsumer(a registered consumer with consumptions in the last 30 days). Aconsumercarriesframework,consumptions_7d,last_decisionandlast_consumed_atfor the dataset the impact reaches it through, andtypeis the consumer kind (agent,application,pipeline,notebook). Card #166.
- Added
v1.42.0
- Added
Feedback-tuned thresholds:
GET /datasets/{dataset_id}/tuningreturns, per metric×column (thesubtypeof 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) carriestuned_threshold(the feedback-tuned step that went intoz_threshold) andtuned_threshold_label(in Portuguese, e.g. «limiar afinado por feedback: 5,0»). Both arenullwhen the metric has no tuning. Card #157.
- Added
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_kindcounts the four kinds (user,ai_system,dbt_exposure,external),totaladds them up andsummaryis the impact sentence (in Portuguese),nullwith no consumers. A consumer with achannel_idgets the notice of a critical SLA breach. Card #165. - Added
GET /domains/{domain_id}/readinessgainsconsumers:{total, by_kind}with the four consumer kinds always present. It is informative and does not change thestatus. Card #165.
- Added
v1.40.0
- Added
GET /rules: each rule haslast_result, its latest result in a scan, withstatus(passed,failedornot_evaluated),scan_id,evaluated_at,observedandthreshold. It isnullbefore the first scan with the rule. Anot_evaluatedrule ran but could not be checked: it opens no issue and does not count towards the score, andreasonsays why (for example, areferentialrule 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
referentialrule (referential integrity between datasets):rule_typeinGET /rulescan now bereferential, withparamscolumn,ref_dataset_id,ref_columnandmax_orphan_pct. InGET /datasets/{dataset_id}/rules.dbt.ymlit comes out as arelationshipstest (to: ref('<table>'),field), and inGET /datasets/{dataset_id}/contractandGET /domains/{domain_id}/contractas aninvalidValuescheck witharguments.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, andrules.dbt.ymlonly keeps a note in a comment.
- Added
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 (keyis the project key) with the issue types of each one (issue_types, no sub-tasks); in Linear, the teams (keyis the team id andissue_typesis empty). Up to 100 targets.default_projectanddefault_issue_typeare the target configured on the integration (default_issue_typeisnullin 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 asPOST /tickets), 502 if the provider fails or the anti-SSRF protection refuses the address. Card #375.
- Added
v1.38.0
- Added
Marking of AI-generated content (AI Act, Article 50(2)):
ai_generated,ai_modelandgenerated_aton reports and RCA diagnoses (GET /reports,GET /reports/{report_id}) and on suggestions (GET /suggestions,suggestionsinGET /datasets/{dataset_id}).ai_generatedisfalsefor offline-mode text, which is deterministic;ai_modelandgenerated_atare thennull. On AI suggestions older than this version, and on those the assistant proposes,ai_modelisnull. 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 asundisclosed. Card #511. - Added
GET /datasets/{dataset_id}/readme:ai_generatedandai_modelfor the draft (the date is the existinggenerated_at) andpublished_ai_generatedandpublished_ai_modelfor the published version, which stays marked after approval. TheGET /datasets/{dataset_id}/readme.mdexport of an AI-generated README starts with YAML front matter (ai-generated,ai-model,ai-generated-at). - Added
GET /reports/{report_id}/exportandGET /reports/{report_id}/export.pdfexports (and the public share):<meta name="ai-generated">(trueorfalse) and, when AI was used,ai-modelandai-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).
- Added
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,esoren), 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,profiledisfalse.
- Added
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, withprovider,external_key,urlandstatus(open,in_progress,doneorunknown). The status is not synced with the tool yet: a ticket created by DataVault startsopen; one linked by hand, or older than this version, staysunknown. Card #374. - Added
ticketson every issue (GET /issues, andopen_issuesinGET /datasets/{dataset_id}): an issue can now have several external tickets, oldest first. - Added
New values in the trails:
field=ticket(ticket created) orticket_link(existing ticket linked) inGET /issues/{issue_id}/events, with the ticket key into_value;to_status=ticketinGET /suggestions/{suggestion_id}/events(the suggestion status does not change). Treat a value you do not know as “other”. - Deprecated
external_ticket_urlon issues — usetickets. It keeps responding, with the URL of the first linked ticket, and is only removed in a major version, not before 2027-04-05.
- Added
v1.35.0
- Added
POST /integrations/dbt/run-results: thedbt test/dbt buildrun in the customer's pipeline sends its results back to DataVault. Body{run_results, manifest, connection_id?}withtarget/run_results.json(schema v4 to v6) andtarget/manifest.jsonfrom the same project. Each test is matched to the dataset (schema + alias) and to the rule — by therule_idinconfig.meta.datavault, otherwise by column and test type. With a rule, the result counts towardsGET /datasets/{dataset_id}/verdict(a critical rule failing in dbt givesfail; the next push withpassclears it); without an equivalent rule (singular tests,relationships), it opens or resolves arule_violationissue withsubtypedbt_test(fail/error→critical,warn→warning). Re-sending the sameinvocation_idwrites nothing again (statusduplicate). With restricted domains, it only matches datasets the sender can write to; the others come back asno_dataset, with nodataset_idorrule_id. Execution metadata only: closed schema — unknown keys, fields carrying rows (failed_rows,rows…) or afailuresthat is not a count return 422. Accepts API tokens. Card #394. - Added
dbt_coreinOrchestratorSource(sourcefilter ofGET /orchestrator-runs): eachdbt testreceived is recorded as a run with theinvocation_idasexternal_run_id. Card #394. - Changed
GET /datasets/{dataset_id}/verdict: eachfailedentry gainsengine—datavault(DataVault scan) ordbt(the customer'sdbt test;observedis 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.
- Added
v1.34.0
- Added
Data product verdict:
GET /domains/{domain_id}/verdictaggregates the verdicts of the domain's datasets for the CI gate —failif any fails,warnif any warns,unknownif none has a verdict (domain without datasets or without scans) andpassotherwise. It returnsdomain_id,domain_name,verdict,with_policy,counts(how many datasets arepass,warn,failandunknown) anddatasets[], sorted by name: each one is that dataset'sGET /datasets/{dataset_id}/verdict, plusdataset_idanddataset_name.with_policy(defaulttrue) counts data policy violations, like the per-dataset verdict; withfalse, quality only. Card #148. - Added
Data product ODCS contracts:
GET /domains/{domain_id}/contractreturns 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),domainanddataProduct(domain name, when the dataset belongs to exactly one; with several, the names go tocustomPropertiesasdatavault.domains) andteamwithmembers: [{username, role: owner}], the owners by name. It never carries emails. Card #148.
- Added
v1.33.0
- Added
tls_modeandtls_verifiedon connections (GET /connectionsandGET /connections/{connection_id}), for Postgres and MySQL/MariaDB only.tls_modeis the configured mode:verify-full(encrypts and verifies the server certificate and hostname, the default for new connections),require(encrypts without verifying) ordisable(no TLS). It isnullon a connection created before this version, which keeps the old behaviour (encrypts if it can, without verifying).tls_verifiedistrueonly withverify-full. On every other connector both arenull. Card #449.
- Added
v1.32.0
- Added
Critical data elements (CDEs) and the regulatory dossier.
GET /cdelists 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 byregulationanddataset_id, with the total in theX-Total-Countheader.GET /cde/dossierreturns 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 thecoverage_pctKPI (required dimensions with a control and a result in the last 30 days). Filtersregulationanddomain_id.GET /cde/dossier.mdandGET /cde/dossier.csvexport the same dossier. Statuses per dimension:gap,no_result,failing,passing; per requirement alsohuman(human assessment). No status claims compliance. Deterministic, no generative AI. Card #392.
- Added
v1.31.0
- Added
suggestions_statuson datasets (GET /datasetsandGET /datasets/{dataset_id}): AI suggestions no longer delay the scan — the scan becomescompletedwith 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) orlimited(above the per-scan cap of datasets with AI — the heuristics remain);nullwhen there is nothing to generate (AI off, or AI suggestions reused from the previous scan). The scan'sstats(GET /scans/{scan_id}) gainsai_suggestions(max_datasets,queued,limited) when the scan left datasets waiting for the AI;duration_sis the scan's duration, without the AI. Card #385.
- Added
v1.30.0
- Added
resampleonPOST /scans(and on the returnedScanOut):truemakes 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_methodindimensions_meta.unicidadeofscore_history(GET /datasets/{dataset_id}): how the sample was picked —systemorbernoulli(the source's nativeTABLESAMPLE/SAMPLE),hash(primary-key hash),stride(one row in every k of a file),head(the first rows) orfull.nullon scores older than this version. Card #317. - Changed
The scans' row sample (
pattern/required_fieldsrules, 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_sizeindimensions_metais now that sample's size. The table profile (metricswithout a column) gainssampleandvalue_sample(method,rows,fraction,seed,deterministic,covers_table), the duplicates issue'simpact_jsongainssample, and so do thedetailsof rule results evaluated on the sample. Label quality reads with the same method, and thelast_resultofGET /datasets/{dataset_id}/label-qualitygainssample. On the first scan after this version the sample changes once: duplicate clusters,unicidadeand pattern violation percentages may change without the data changing. Card #317.
- Added
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-TimestampandX-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 gainsoccurred_at, and the contract (events,WebhookPayloadJSON 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 onPOST /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"(previouslydatavault.alertswith empty lists), and "Test channel" withdatavault.test. Card #323.
- Added
v1.28.0
- Added
GET /issues/{issue_id}/explanation: new fieldsz_threshold(the |z| threshold applied to the anomaly's baseline),p_value(p-value of the null model calibrated for the baseline size) andconfidence(high|medium|low, from the p-value), plus thebaselineobject (kind,weekday,weekday_label,points), which the response computed but did not expose. For older anomaliesp_valueandconfidencearenullandz_thresholdis 3.5. Card #319. - Changed
anomalyissues (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. Theimpact_jsonof new anomalies carriesz_threshold,p_valueandconfidence. Method and harness figures: docs/METODOS.md, "Anomalias". Card #319.
- Added
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 signedpointsin score points andparamsto phrase them. Σ drivers +unexplained_points=delta. Defaults to the last two completed scans; 422 without two scored scans. Card #318.
- Added
v1.26.0
- Added
dimensions_metaon everyscore_historypoint ofGET /datasets/{dataset_id}: the score dimensions computed on a sample rather than the whole table — today onlyunicidade(uniqueness), withestimated,sample_size(the first N rows the source returned, not random) andduplicate_rows. Empty ({}) when every dimension covers the whole table and on scores older than this version. Card #294. - Fixed
Score dimension
unicidade(uniqueness:dimensions.unicidadeand, in turn,scoreandlatest_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'srow_countand 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:unicidade90.0 instead of 100.0,score−2.5 points); scores already stored are not recomputed. Review themin_scorethresholds of SLAs and contracts on those tables.
- Added
v1.25.0
- Added
GET /datasets/{dataset_id}/freshnessand thefreshnessblock inGET /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'sused_column,detected_columnandsource(configured,detected,connector),latest_timestampin UTC,timezone(awareorassumed:<zone>),source_timezone,future_columns(maximum more than 24 h after the profile — left out of the automatic choice),candidatesand the dataset'sfreshnessrule (rule_id,rule_column,rule_max_age_hours). Card #292. - Added
source_timezoneon connections (GET /connections,GET /connections/{connection_id}): the IANA zone in which the source's timestamps without a zone are read (defaultUTC, the behaviour so far). - Fixed
freshnessrule: thecolumnparameter 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 withdetails.errorinstead of reporting it fresh. In profile table metrics,latest_timestampis in UTC with its offset (+00:00) and comes withlatest_timestamp_columnandlatest_timestamp_tz.
- Added
v1.24.0
- Added
GET /datasets/{dataset_id}/rules.dbt-tests.zip: the dataset's activeexpressionandcustom_sqlrules as dbt singular tests — a ZIP withtests/*.sql(the SELECT of the violating rows in the source's dialect, withref()in place of the table; each rule's text goes inside Jinjarawblocks, so dbt never runs Jinja written in a rule) and a README listing the rules left out and why.
- Added
v1.23.0
- Changed
POST /scans(and scan requests from schedules, the dbt Cloud webhook and the SDK): when the workspace already hasmax_queued_scansrequested scans queued or running (5 on the default plan; see the plan catalogue), the API answers429withRetry-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 (limittells them apart; the rate limit usesretry_after). Nothing is rejected for good: retrying afterretry_after_senqueues again. Card #251 (enterprise scale).
- Changed
v1.22.0
- Added
GET /governance?months=1..12andGET /governance/domains/{domain_id}: governance programme effectiveness (the CDO dashboard). Each KPI has a stablecode,group(coverage,resolution,sla,adoption,quality,incidents,usage),unit,direction,valuewith thesamplebehind it,targetandtarget_source(workspace→organization→default; in a domain with a declared SLA,domain_slais the SLO goal),status(on_target,at_risk,off_target,no_data,no_target),gap,previous/deltaandtrendper UTC calendar month (the current one withpartial). KPIs: owner, documentation, classification, contract and rule coverage (ownership_pct,documentation_pct,classification_pct,contracts_pct,rules_pct);cycle_time_hoursandmttr_critical_hours;slo_attainment_pctanddue_date_met_pct;suggestion_adoption_pctandnever_again_adoption_pct;score_<dimension>per scorecard dimension;critical_incidents(a lineage cascade counts as one) andactive_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 includesdomains(the per-domain matrix, with a status per KPI) andtargets. 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) andGET /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.
- Added
v1.21.0
- Added
GET /catalog/metadata-qualityandGET /domains/{domain_id}/metadata-quality: catalog and metadata quality (governance KPIs).pct= checks passed ÷ applicable (non-applicable checks stay out of the denominator;nullwhen 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) andcolumn_docs_match_schema— withcoverage_pct(% of applicable datasets that pass).findingsare 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) andexternal_catalog(coverage and glossary terms read from the connected external catalog). The workspace one lists the 50 worst datasets indatasets(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.
- Added
v1.20.0
- Added
GET /datasets/{dataset_id}/pipeline-health: the health of the Fivetran/Airbyte pipelines that write the dataset —status(the worst ofok,failed,delayed,paused,unknown;not_monitoredwith 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_atandage_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, withcomparable) and the tables that joined/left the pipeline (schema_change);recent_runslists the latest syncs. A source in a restricted domain appears as «Dataset restrito», with no table, latency or reconciliation. - Added
GET /orchestrator-runsacceptssource=fivetranandsource=airbyte: the syncs of the connected integrators, read from their metadata APIs (read-only), with the pipeline injob_id/job_name. Issues gain thepipeline_synctype (subtypesfailedanddelayed).
- Added
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 theDeprecation(RFC 9745),Sunset(RFC 8594) andLinkwithrel="deprecation"headers at least 180 days before it stops responding. CORS exposes these three headers. - Added
GET /healthnow includesapi_version— this API's version, the same asGET /version. - Added
GET /connections/{connection_id}/query-logandGET /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. Filtersscan_id,operationanddays(1–366); the list pages withlimit(default 100, maximum 500) /offsetand the export returns a download informat=csv(default) orformat=json. - Added
GET /connectionsandGET /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, ornull),blackout_upcoming(next occurrences),max_concurrent_queriesandmax_sample_rows.GET /connections/typesnow carries each connector'slabel,groupanddocs_anchor. - Changed
POST /scansresponds409withdetail = {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 newoverride_blackoutandoverride_reasonfields (a reason of at least 5 characters, audited) force the scan inside a window, but only for admins — with an API token they return403.
- Added
v1.18.0
- Added
Remote MCP server in production:
https://mcp.praxis.lbostuff.tech/mcp(MCP Streamable HTTP, TLS). Every request is authenticated with advk_API token (Authorization: Bearer) validated against the API — without a token,401— and it exposes the same read-only tools and resources (includingget_verdictandget_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 whoseHostheader is outside the allowlist get421(DNS-rebinding protection), and those with a cross-siteOriginget403. - Changed
GET /ide/agent-confignow returns, in production,remote_available: true,mcp_urlset to the remote MCP server andlocal_server_cmd: null: the configurations generated for Claude Code, Claude Desktop, Cursor, VS Code and Codex point to it instead ofhttp://127.0.0.1:8765/mcp.
- Added
v1.17.0
- Added
Data quality scorecard by dimension (DAMA/ISO 8000):
GET /datasets/{id}/dimensionsreturns, 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 withmeasured: falseandscore: null("not measured"), never 100.GET /dashboard/dimensionsaggregates per workspace and per domain using only the measured datasets (datasets_measuredout ofdatasets_total), andGET /dashboard/dimensions.csvexports one row per dataset × dimension. The composite score is unchanged. - Added
GET /issuesaccepts?dimension=(e.g.completeness) and every issue now carries adimensionfield, resolved by the deterministic type/subtype → dimension map: each issue lands in exactly one dimension. An unknown dimension returns 422.
- Added
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 /healthandGET /health/readynow includebuild_sha. Useful to quote the version in force in a support request or a contract.
- Added
v1.15.0
- Added
GET /rules,GET /suggestions,GET /reportsandGET /duplicatesnow return the filtered total in theX-Total-Countheader (same filters as the request, beforelimit/offset, and only what the caller can see under domain permissions), likeGET /datasets. The body is still the page as a list;limitdefaults to 50, maximum 500.
- Added
v1.14.0
- Added
GET /biandGET /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 thedatasetsdimension. JSON ({feed, schema_version: 1, columns, total, limit, offset, next_offset, rows}) or CSV (format=csv, optionalbom=1) on the same route;limit(≤ 10,000, default 5000) /offsetpagination with a stable order; filterssince,until(UTC, 90-day default window forscores/slo/activity),domain_id,dataset_idandstatus(issuesonly). Fixed snake_case columns, ISO UTC dates and dot decimals; columns are only ever appended.ETagandIf-None-Match→304;X-Total-CountandX-Next-Offset. The catalog carries the Power Query (M) code. Read-only, no OData; accepts API tokens.
- Added
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 carriestoken_placeholderand, where the tool supports it, reads it fromDATAVAULT_TOKEN. Read-only.
- Added
v1.12.0
- Added
GET /datasets/{id}/trust-factsandGET /domains/{id}/trust-facts(?detail=minimal|full, defaultminimal): trust facts in a stable envelope (schema_version: 1) — verdict with policy and theagentedecision, 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:minimalnever includes column names.ETag(hash of the facts, withoutas_of) andIf-None-Match→304;Cache-Control: private, no-cache. Read-only, no LLM; accepts API tokens.
- Added
v1.11.0
- Added
GET /domains/{id}/readiness?use_case=(rag,ml,agente,bi; defaultbi): data product readiness per use case —ready | ready_with_warnings | not_ready,blockers[]andwarnings[]with a stablecode(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_nameandmessage, per-dataset detail, monthly SLA,checked_atandvalid_until(next scheduled scan). Read-only, no LLM; accepts API tokens.
- Added
v1.10.0
- Added
Trust decision (
GET /datasets/{id}/trust-decision,POST /trust-decisions): new fieldsrequired_actions[](what is missing to move up; never empty foruntrusted/unknown) andvalid_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).
- Added
v1.9.0
- Added
GET /datasets/{id}/trust-decision?purpose=andPOST /trust-decisions(≤ 50 ids): trust decision before consuming a dataset for a purpose (agente,rag,treino,bi) —trusted | caution | untrusted | unknown, reasons with a stablecodeandmessage_pt, verdict, AI-readiness, freshness vs SLA, open incidents, unmasked PII, contract andttl_seconds(also inCache-Control: max-age). Read-only;POSTaccepts API tokens.
- Added
v1.8.0
- Added
GET /ide/resolve?ref=andGET /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).
- Added
v1.7.0
- Added
GET /datasets/{id}/contract/versions,GET /datasets/{id}/contract/versions/{n},GET /datasets/{id}/contract/diff?from=&to=andGET /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}/contractacceptssource=activeto download the contract accepted by the producer, andGET /datasets/{id}/verdictnow 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.
- Added
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-RemainingandX-RateLimit-Reset; above the limit the API returns429withRetry-After(seconds) andretry_afterin the body.
- Added
v1.5.0
- Changed
POST /scansnow accepts API tokens — orchestrators (Airflow, CI) trigger scans and wait for the verdict withGET /datasets/{id}/verdict. Subject to the plan's scan quota. - Added
GET /orchestrator-runsandPOST /orchestrator-runs: orchestrator runs (dbt Cloud, Airflow) kept as root-cause diagnosis context ("dbt job X failed 20 min before this issue").POSTaccepts API tokens; run metadata only.
- Changed
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.
- Added
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.
- Added
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 /incidentsandGET /incidents/{id}: related issues grouped into a single incident through lineage. - Added
GET /knowledgeandGET /knowledge/graph: knowledge base search and relationship graph. - Added
GET /dashboard/piiandGET /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/sloandGET /dashboard/slo.csv: attainment of the ODCS contract's latencyslaPropertiesagainst 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.
- Added
v1.1.0
- Added
GET /datasets/{id}/contract(ODCS v3.1) andGET /datasets/{id}/contract.dbt.yml: exportable data contract. - Changed
API tokens are now available from the Team plan upwards.
- Added
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.
- Added
No workspace yet?
Start on the free plan and move to Team when you need API tokens.