Saltar para o conteúdo
DataVault

Developers

A qualidade dos teus dados, por API.

Lê scores, issues, contratos e vereditos do DataVault a partir dos teus pipelines, do CI ou de um agente. REST, JSON, OpenAPI 3.1 — e tokens que só leem.

150 endpoints · versão 1.74.0 · atualizada a 12 de outubro de 2026

Começar

Da conta ao primeiro pedido em três passos.

  1. Cria um token

    Um admin do workspace cria-o em Definições → Tokens de API (planos Team e superiores). O valor dvk_… aparece uma única vez — guarda-o num cofre de segredos.

  2. Chama a API

    Envia o token no header Authorization: Bearer. Todas as respostas são JSON (exceto os exports .yml, .sql e .md).

  3. Automatiza

    Liga o veredito de um dataset ao teu CI, alimenta dashboards ou gera um cliente tipado a partir do contrato OpenAPI.

export DATAVAULT_TOKEN="dvk_…"

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

Autenticação

Tokens de API: só leitura, por desenho.

Cada token pertence a um workspace e herda as suas permissões de leitura. Não abre a Consola, não altera dados e não vê credenciais das fontes.

  • Formato dvk_ seguido de 48 caracteres hexadecimais. Guardamos apenas o hash SHA-256 — se o perderes, cria outro.
  • Só GET, HEAD e OPTIONS, mais as poucas escritas marcadas na referência (gate RAG, disparo de scans, runs de orquestradores, data-diff e decisão de confiança em lote). Qualquer outro método devolve 403, mesmo que o endpoint exista.
  • Revogação imediata em Definições → Tokens de API; a última utilização fica visível para auditoria.
  • Criação e revogação ficam no registo de atividade do workspace.

Paginação

As listas aceitam limit e offset. O catálogo (GET /datasets) devolve o total no header X-Total-Count.

Header de autenticação

Authorization: Bearer dvk_3f9c…

Códigos de erro

401
Token em falta, inválido ou revogado.
402
A funcionalidade não está incluída no plano do workspace (ex.: dossiê do AI Act).
403
Método de escrita com um token, ou workspace suspenso.
404
O recurso não existe ou pertence a outro workspace.
422
Parâmetros inválidos — o corpo indica o campo e o motivo.

Todas as respostas trazem o header X-Request-ID. Os erros 4xx trazem detail (o motivo, legível), code (estável, para decidires no código; null se o erro não tiver um) e request_id, igual ao header. Cita o request_id ao suporte para encontrarmos o pedido nos 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"
}

Caso de uso

Um gate de qualidade no teu CI.

GET /datasets/{id}/verdict devolve pass, warn, fail ou unknown com as regras que falharam. Falha o deploy quando uma regra crítica falha ou o contrato de dados deixa de ser válido.

SDKs gerados do contrato

O openapi.json é gerado do código a cada release, por isso um cliente gerado nunca diverge da API. Os exemplos acima usam openapi-typescript e openapi-python-client; qualquer gerador OpenAPI 3.1 serve.

# .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 "DataVault: $verdict"
[ "$verdict" != "fail" ] || exit 1

Estabilidade

O que acontece à tua integração quando a API muda.

O contrato é o que está neste portal. Uma versão minor só acrescenta; uma quebra só sai numa major, depois de uma depreciação anunciada. Resumo da política de versionamento — a versão em vigor é a 1.74.0.

  • 180 dias

    de aviso mínimo antes de uma operação depreciada deixar de responder

  • 30 dias

    de aviso antes de reduzirmos rate limits ou quotas

  • 2 minor

    do SDK Python suportadas ao mesmo tempo, com correções

  • O que é o contrato

    Tudo o que está neste portal: as operações de /api/v1 que um token de API consegue chamar, com os seus parâmetros, campos e códigos de estado. O resto da API (a app web, a gestão da conta e dos tokens, as áreas internas) não está coberto e pode mudar sem aviso.

  • Mudanças compatíveis (minor)

    Uma versão minor só acrescenta: operações, parâmetros opcionais, campos nas respostas, valores novos de enums e códigos novos. O teu cliente deve ignorar campos que não conhece e tratar valores de enum desconhecidos. Os code das razões e dos bloqueadores (decisão de confiança, readiness) são estáveis; os envelopes com schema_version (factos de confiança, feeds de BI) só mudam de schema_version numa quebra.

  • O que é uma quebra (major)

    Remover ou renomear uma operação, um parâmetro ou um campo; mudar o seu tipo ou formato; tornar obrigatório o que era opcional ou recusar um pedido que era aceite; mudar o significado de um campo, de um code ou de um estado HTTP. Uma quebra só sai de duas formas: numa versão de caminho nova (/api/v2), com /api/v1 sem mudanças, ou numa versão major anunciada no changelog depois de uma depreciação. O nosso CI compara cada alteração com o contrato publicado e recusa uma quebra que não esteja anunciada.

  • Depreciação

    Anunciada no changelog (tipo deprecated) pelo menos 180 dias antes do fim, com comunicação direta aos admins dos workspaces com tokens de API ativos. Durante a janela a operação continua a responder, com os headers Deprecation (RFC 9745 — desde quando), Sunset (RFC 8594 — quando deixa de responder) e Link com rel="deprecation" (a nota de migração). Exceção: uma correção de segurança pode encurtar a janela e fica no changelog com o tipo security.

  • SDK

    O SDK Python (datavault-sdk) tem versões próprias. Suportamos as duas minor mais recentes (N e N-1): recebem correções e acompanham a API. Versões mais antigas continuam a funcionar enquanto a API não tiver quebras, mas sem correções.

  • Rate limits e quotas

    Os limites de pedidos (por token e por IP) e as quotas do plano podem mudar; uma redução é anunciada com pelo menos 30 dias de antecedência, exceto medidas contra abuso. Os valores em vigor vêm nas respostas (X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset); acima do limite a API responde 429 com Retry-After e, acima de uma quota do plano, 402.

  • Reportar um problema

    Uma resposta que contradiga este contrato é um bug nosso. Escreve para suporte@praxis.lbostuff.tech com o método e o caminho, o estado HTTP, a hora (UTC), o X-Request-ID da resposta (nos erros vem também em request_id) e a resposta de GET /version (version, build_sha, api_version) — nunca o token. Vulnerabilidades: o contacto em /.well-known/security.txt.

Como sabes que algo vai ser removido

Uma operação depreciada continua a responder durante a janela, mas passa a trazer estes headers — e aparece no changelog abaixo com o tipo «Descontinuado». Vigia-os nos teus clientes: o CORS expõe-nos também no browser.

Ler a política completa

Exemplo de resposta de uma operação depreciada

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

Changelog da API

O que mudou no contrato.

Mudanças que quebram compatibilidade são anunciadas aqui antes de chegarem a produção. Novidades do produto em geral: ver o changelog do DataVault.

  1. v1.74.0

    • Novo

      GET /api/v1/suggestions: o filtro category aceita enrichment, a categoria das propostas de enriquecimento (valores de fontes de referência com a fonte, a data e a confiança, aplicados por PR ou SQL depois de aprovados). Só nascem quando alguém pede uma proposta no dataset; a IA não as cria. Cartão #389.

  2. v1.73.0

    • Novo

      Webhooks de saída: code (código estável em inglês e snake_case, ex.: alerts_new, escalation_level, digest_weekly) e params (só números e identificadores) em todos os eventos do WebhookPayload, e title/body na língua do canal: o locale do config do canal, senão workspace.default_locale, senão pt-PT. Nos canais de email, cada destinatário recebe a mensagem na sua língua (locale da conta, senão a do workspace) e um endereço externo na do canal. Os códigos estão em docs/WEBHOOKS.md. Cartão #519.

    • Novo

      Erros na língua do pedido: o detail sai na língua da pessoa (locale da conta), senão na omissão do workspace, senão na do cabeçalho Accept-Language (pt, en ou es; de e fr recebem en), senão em pt-PT. Cada erro do catálogo traz o seu code estável, também os que até aqui tinham code null. Vale também para o 402/403 do plano, o 429 do rate limit e da fila de scans, o 413 do corpo e a message de um detail estruturado ({code, message}), cujo code não muda. Sem língua conhecida, o texto é o de antes. Cartão #519.

  3. v1.72.0

    • Novo

      GET /api/v1/datasets/{dataset_id}: o semantic de cada coluna do perfil ganha region. No tipo phone é o país do plano de numeração (ISO 3166-1 alfa-2), lido do país dos dados; nos outros tipos e em phone_pt (que é PT) vem a null. Cartão #516.

  4. v1.71.0

    • Novo

      Língua, região e fuso: locale (pt-PT, pt-BR, en-GB, en-US, es-ES, de-CH ou fr-CH) e timezone (IANA) em GET /auth/me, mais workspace.default_locale e workspace.timezone. PATCH /auth/me grava os da própria pessoa (só as chaves enviadas mudam; null volta à omissão do workspace) e GET/PUT /workspace/locale gere as omissões do workspace (admin). Cartão #518.

    • Novo

      Agendamentos com fuso: timezone (IANA) no corpo de PATCH /connections/{connection_id}/schedule e scan_timezone em GET /connections; timezone em POST/PATCH /report-schedules e na resposta. A hora (hour, scan_hour) lê-se nesse fuso e o próximo instante UTC calcula-se para cada ocorrência, por isso a hora local aguenta a mudança de hora. Os agendamentos existentes ficam em UTC e não mudam de hora; sem timezone, o PATCH mantém o fuso da conexão. Cartão #518.

  5. v1.70.0

    • Novo

      GET /api/v1/domains/suggestions: o domínio sugerido para os datasets sem domínio, pela maioria das ligações de linhagem a datasets já curados (pelo menos metade das ligações, sem empate). Cada sugestão traz os votos, o total de ligações, até 3 colunas via, os tipos de aresta, inferred_only e a signature da evidência; os empates vêm em ambiguous, com os candidatos. Admins veem todas; quem gere domínios só as desses domínios; os outros recebem 403. Um vizinho invisível conta no total e vem como «Dataset restrito». has_lineage diz se há alguma ligação de linhagem a um dataset visível, porque counts.with_lineage só conta os datasets sem domínio. Cartão #468.

    • Novo

      Qualidade dos metadados: o problema missing_domain ganha uma sugestão com channel: "lineage" quando há datasets sem domínio com sugestão pela linhagem («Juntar pela linhagem — N com sugestão», com link para /datasets?vista=dominios e endpoint GET /api/v1/domains/suggestions). Valor novo no enum channel de MetadataSuggestionOut. Cartão #468.

  6. v1.69.0

    • Novo

      País dos dados (ISO 3166-1 alfa-2): data_country em GET /connections e GET /connections/{connection_id} (o da fonte; null = o do workspace), data_country em GET /datasets e GET /datasets/{dataset_id} (o próprio do dataset; null = herdado) e country no detalhe do dataset, com o país effective que os scans usam (dataset → conexão → workspace) e a source de onde vem. GET /datasets/{dataset_id}/data-country dá o mesmo objeto. O workspace tem PT por omissão, por isso nada muda até alguém escolher outro país; noutro país, os telefones de 9 dígitos sem indicativo e os códigos postais deixam de ser lidos como portugueses. Cartão #513.

  7. v1.68.0

    • Novo

      Schema de resposta em 30 operações que devolviam um objeto sem schema. Os campos são os que já vinham e o JSON não muda. São o veredito (GET /datasets/{dataset_id}/verdict e GET /domains/{domain_id}/verdict), a decisão e os factos de confiança (GET /datasets/{dataset_id}/trust-decision, POST /trust-decisions, GET /datasets/{dataset_id}/trust-facts e GET /domains/{domain_id}/trust-facts), a prontidão (GET /datasets/{dataset_id}/ai-readiness e GET /domains/{domain_id}/readiness), a pesquisa e a linhagem (GET /datasets/find, GET /datasets/{dataset_id}/lineage, …/lineage/traverse, …/lineage/impact e …/lineage/path), o contexto (GET /datasets/{dataset_id}/context), o data-diff (POST /datasets/{dataset_id}/diff/{other_id}), os contratos (GET /datasets/{dataset_id}/contract/versions, …/contract/versions/{number}, …/contract/diff, …/contract/proposals e …/contract/proposals/{proposal_id}), a política de dados (GET /workspace/data-policy e GET /domains/{domain_id}/data-policy), o AI Act (GET /compliance/ai-systems, …/ai-systems/{system_id} e …/art10), os orquestradores (POST /integrations/dbt/run-results, GET /orchestrator-runs e POST /orchestrator-runs) e as leituras da extensão do VS Code (GET /ide/resolve e GET /ide/datasets/{dataset_id}/columns). Um cliente gerado a partir deste spec passa a ter estes campos, e a nossa verificação de quebras passa a ver dentro destas respostas. Cartão #455.

  8. v1.67.0

    • Novo

      GET /datasets/{dataset_id}/rules/export?format=dbt|sodacl|gx: as regras ativas do dataset como ficheiro para o teu repositório, em testes dbt (schema.yml, o mesmo de GET /datasets/{id}/rules.dbt.yml, que continua), checks SodaCL (missing_count, duplicate_count, invalid_count com valid regex/valid min/valid max, freshness, row_count) ou uma suite do Great Expectations 1.x (JSON). No SodaCL e no GX só sai o que o POST /rules/import lê de volta como a mesma regra; o que fica de fora (regras de streams, validade por dígito de controlo, regras SQL, tolerâncias acima de zero…) vem com o motivo no cabeçalho do ficheiro (meta.datavault.unsupported no GX) e conta-se nos headers X-Rules-Exported, X-Rules-Unsupported e X-Rules-Unsupported-Types. GET /rules/export?format=sodacl: as regras dos datasets que quem pede vê, num só ficheiro SodaCL. O POST /rules/import passa a ler invalid_count com validade e duplicate_count de várias colunas no SodaCL, expect_compound_columns_to_be_unique no GX e frescura em minutos (30m), e o dedupe vê como a mesma regra a tolerância a zero e a ausente, columns: [x] e column: x, 12.0 e 12: reimportar o ficheiro não duplica regras. Sem LLM e sem tocar na fonte. Cartão #403.

  9. v1.66.0

    • Novo

      Problemas «esperados»: cada issue de GET /issues e de GET /datasets/{id} ganha resolution_reason: expected quando foi ignorado como esperado (uma migração, um fecho de mês), null nos restantes. No GET /issues/sla-report, um issue esperado conta nos opened e no MTTA, mas fica fora dos resolved, do MTTR e do in_scope/breached. Cartão #401.

  10. v1.65.0

    • Segurança

      O 422 da validação deixa de devolver o valor recebido: cada item de detail traz type, loc e msg, e o ctx só com os limites da regra (gt, ge, lt, le, multiple_of, min_length, max_length, max_digits, decimal_places). O input de um campo em falta era o corpo inteiro do pedido, e no POST /auth/login e no POST /auth/register isso incluía a password. Os campos input e ctx continuam no schema ValidationItem até à próxima major, mas o input já não vem. Cartão #450.

  11. v1.64.0

    • Novo

      GET /incidents e GET /incidents/{incident_id}: raio de impacto do incidente. impact_scope é column quando os problemas da raiz são de uma coluna (impact_columns) e há linhagem coluna-a-coluna (lida do SQL das views) a sair dela: a impact_tree só leva os datasets que usam essas colunas e os de impacto possível, onde falta o mapa de colunas. É table ao nível da tabela, como antes, com impact_note: no_column_lineage quando os problemas eram de uma coluna sem esse mapa, e none quando nada a jusante é afetado. Cada nó da árvore ganha impact (column = certo, table = possível) e via_columns; o incidente ganha downstream_count, downstream_column_count e exposures_count. Num dataset restrito, via_columns vem vazio. Cartão #402.

  12. v1.63.0

    • Novo

      GET /rules/{rule_id}/failing-rows.sql?dialect=&limit=: a query que lista as linhas que falham a regra, em texto, para correres no teu warehouse (o DataVault não a executa nem guarda linhas). Dialeto por omissão o da fonte do dataset (X-DataVault-Dialect), ou postgres, mysql, mssql, bigquery, snowflake, databricks e sqlite; nomes sempre citados e LIMIT/TOP do dialeto (limit de 1 a 1000, omissão 100). Uma regra sem tradução por linha (row_count_min, regras de streams, lista acima de 500 valores…) responde 204 com o motivo em X-DataVault-Reason. Um token dvk_* pode chamá-la e o pedido fica no trilho de auditoria como leitura (failing_rows_sql_read). Cartão #400.

  13. v1.62.0

    • Novo

      SLA de resolução por severidade: GET /issues/sla-report?period=AAAA-MM&group_by=domain|team|severity devolve, por grupo e no total, os issues criados, fechados (resolvidos ou ignorados) e por resolver no fim do mês, quantos passaram o alvo (breached, sobre in_scope), o MTTA e o MTTR (mediana e p90, em horas de relógio do SLA: por resolver e sem os adiamentos) e o ageing das abertas (lt_1d, d1_3, d3_7, d7_30, gt_30d). Só conta os datasets que quem pede vê. Cada issue de GET /issues ganha sla_breached_at (quando passou o alvo da sua severidade; null dentro do SLA ou sem alvo) e GET /issues aceita sla=breached. Cartão #396.

  14. v1.61.0

    • Novo

      POST /datasets/{dataset_id}/validate: valida até 5 000 registos (corpo até 2 MB; acima, 413) contra as regras ativas do dataset e devolve, por registo, se passou e que regras falharam, com um code estável (invalid_check_digit, pattern_mismatch, not_in_reference…) e os params da regra; por regra, quantos registos passaram, falharam ou não se aplicam, com a tolerância da regra; e o verdict do lote (pass, warn, fail ou unknown). As regras do dataset inteiro (unique, freshness, row_count_min…) ficam com not_applicable_code. Nada é guardado. Um token dvk_* pode chamá-la, com rate limit próprio por token (429 com Retry-After). Cartão #397.

  15. v1.60.0

    • Alterado

      Os erros 4xx passam a ter sempre o mesmo corpo (schema ErrorResponse; no 422 e no 402, ValidationErrorResponse e PlanLimitErrorResponse, com os mesmos campos): detail como antes, code (código estável quando existe — auth_required, token_invalid, token_read_only, workspace_suspended, plan_limit, org_quota, validation_error, rate_limited, scan_queue_full, payload_too_large — senão null) e request_id, o mesmo valor do header X-Request-ID. Os campos próprios de cada erro (plan e limit no 402, retry_after no 429) mantêm-se. Cartão #361.

    • Alterado

      O OpenAPI documenta os erros: 401 e 403 em todas as operações autenticadas, 404 nas que têm um id no caminho, 402 nas que dependem do plano (com plan e limit no schema), o 422 com o mesmo envelope (cada item com os campos de antes: loc, msg, type, input e ctx) e o header X-Request-ID em todas as respostas. Os schemas principais (datasets, issues, scans, gate RAG, decisão de confiança) e as respostas do veredito, dos factos de confiança, da readiness, das versões de contrato, da reconciliação e da resolução no IDE trazem exemplos. Cartão #361.

  16. v1.59.0

    • Novo

      Campos confidence_band (high, low, ou exact quando as linhas do cluster são iguais nas colunas comparadas) e reference_precision nos clusters de GET /duplicates: a confidence é a semelhança média dos pares, não uma probabilidade; a banda e a precisão de referência (fração de clusters com essa semelhança que eram duplicados reais) vêm de um benchmark sintético português, não dos dados do cliente. A confidence de um cluster fuzzy novo fica abaixo de 1, que passa a ser só dos exatos. Cartão #352.

  17. v1.58.0

    • Novo

      Campo kind nos datasets (table, view, materialized_view, external ou unknown): o tipo do objeto na fonte. Uma view ou external perfila em basic sobre as primeiras linhas, salvo profundidade própria standard ou deep do dataset. Cartão #347.

    • Novo

      Campos primary_key, partition e clustering em cada coluna do detalhe de um dataset: o que a fonte declara sobre ela. Cartão #347.

    • Novo

      Campo cost_guarded nos datasets: true quando os scans o perfilam em basic sobre as primeiras linhas e sem contar valores distintos (kind view ou external, sem profundidade própria standard ou deep). Cartão #347.

    • Alterado

      key_columns passa a opcional no data-diff (POST /datasets/{id}/diff/{other}): sem ele, a chave é a chave primária declarada na base, se o candidato tiver as mesmas colunas; senão, 422 como antes. O relatório traz key_source (request ou primary_key). Cartão #347.

  18. v1.57.0

    • Alterado

      A dimensão consistencia do score (score_history[].dimensions, GET /datasets/{id}) passa a descontar os issues schema_drift e anomaly abertos ou reconhecidos do dataset, e não só o drift do próprio scan: 15 pontos por aviso e 30 por crítico, × (1 − idade/K), em que a idade são os scans desde a última deteção e K = DATAVAULT_CONSISTENCY_DECAY_SCANS (5). Um issue resolvido ou ignorado deixa de descontar no scan seguinte. O drift estrutural (colunas removidas, tipo alterado) continua a contar só no scan que o deteta. Os scores já gravados não mudam. Cartão #351.

    • Novo

      Valor consistency_issue no kind dos drivers de GET /datasets/{id}/score/explain: um issue aberto cujo desconto na consistência mudou, com issue_id e params change (opened, decaying, detected_again, gone), age_before, age_after, points_before, points_after e decay_scans. Nota nova consistency_model_changed quando um dos scores é anterior a esta versão. Cartão #351.

  19. v1.56.0

    • Novo

      Números calculados sobre amostras dizem-no com os mesmos campos: estimated, sample_size e o intervalo de confiança a 95% (ci95, ou <campo>_ci95 quando o objeto tem vários números). dimensions_meta.unicidade.ci95 no score; semantic.estimated e semantic.sample_size no perfil das colunas; sample_matches e sample_size nas colunas do mapa de PII. Cartão #350.

  20. v1.55.0

    • Novo

      Valor bi_refresh no type dos issues: refresh falhado (subtype failed) ou atrasado (delayed) de um dataset BI do Power BI, do Tableau ou do Looker, preso ao dataset de que ele depende; impact_json traz bi_dataset_id, bi_dataset_name e os dashboards afetados. Cartão #271.

  21. v1.54.0

    • Novo

      Paginação uniforme: GET /issues, GET /scans, GET /signals e GET /knowledge passam a devolver o total filtrado no header X-Total-Count, como já faziam GET /datasets, /rules, /suggestions e /reports. GET /scans, GET /signals e GET /orchestrator-runs aceitam offset. Em todas as listagens, limit vai de 1 a 500 (GET /scans, GET /knowledge e GET /orchestrator-runs paravam nos 200), offset é ≥ 0 e um limit acima do teto dá 422. O corpo continua a ser uma lista e o limit por omissão de cada listagem não muda. Na pesquisa híbrida de GET /knowledge, o total é o tamanho da lista fundida sobre a qual corre a paginação. Cartão #333.

  22. v1.53.0

    • Novo

      Feed BI chargeback em GET /bi/chargeback: uso e custo do workspace do token por mês, com uma linha por workspace, por domínio e por equipa (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). entity_id vazio numa linha de domínio ou de equipa = sem domínio ou sem equipa. Um dataset em dois domínios conta metade em cada; a parte de um domínio vai para as equipas owner (sem owner, para as editor). Por omissão cobre os meses dos últimos 365 dias; domain_id deixa só as linhas desse domínio. Cartão #259.

  23. v1.52.0

    • Novo

      Equipas como responsáveis: cada issue de GET /issues ganha assignee_team_id e assignee_team_name (a equipa responsável, além da pessoa; null sem equipa). GET /issues e GET /issues/board aceitam assignee_team — o id de uma equipa, mine (as equipas de quem pede) ou none (sem equipa). GET /issues/board ganha team_cells, as contagens estado × equipa (status, team_id, team_name, count; team_id null = sem equipa). Atribuir a equipa é da UI e só aceita uma equipa que leia o dataset do issue. Cartão #240.

  24. v1.51.0

    • Novo

      Montra pública de um data product, com o mesmo modelo do badge do dataset (token opaco, fora do portal, sem conta): GET /public/badge/domains/{token}.svg devolve o badge estilo shields.io — à esquerda o cumprimento do SLA no mês («SLA 99,2 %») ou, sem SLA declarado, o score; à direita a readiness para BI, verde (ready), âmbar (ready_with_warnings) ou vermelho (not_ready) — com ETag e Cache-Control: public, max-age=3600. GET /public/domains/{token} devolve o JSON da página /p/dominios/{token}: nome, descrição, nome do owner, versão e estado, score, SLA declarado e cumprimento do mês, readiness por caso de uso (rag, ml, agente, bi, com estado e códigos dos bloqueadores e avisos) e, por dataset, nome, veredito, score e os links públicos só se o dataset também estiver partilhado, mais generated_at; com ETag e Cache-Control: no-cache. Nunca emails, ids internos, colunas ou amostras. Os dois partilham o rate limit por IP do badge do dataset; um token desconhecido ou revogado dá 404. A gestão (GET, PUT e DELETE /domains/{domain_id}/public-share e POST …/public-share/rotate) é da UI: ver é de quem lê o domínio; ativar, rodar e revogar, de um admin, do owner do domínio ou de uma equipa owner. Cartão #180.

  25. v1.50.0

    • Novo

      Ensaio da remediação: cada item de GET /suggestions ganha rehearsal, o último ensaio só-leitura do passo de diagnóstico da sugestão (null se nunca foi ensaiada). Traz version, status (queued, ok, blocked, timeout ou error), affected_rows (linhas que o passo 1 devolve, contadas na fonte), total_rows (linhas do dataset no último scan), pct, requested_at, ran_at, duration_ms e actor (o email do admin que o pediu). Quando não contou, reason traz code (no_diagnosis, aggregate, limit, not_select, forbidden, function_not_allowed, unknown_table, timeout, source_error, blackout…), params e message em pt-PT, de recurso. Com status = ok, o impact da sugestão passa a ter estimated_rows igual às linhas contadas, estimated_rows_sample com a estimativa anterior e rows_source = rehearsal. Só contagens: nunca valores nem o SQL. Cartão #178.

  26. v1.49.0

    • Novo

      Avaliação dos dados sintéticos: GET /datasets/{dataset_id}/synthetic/evaluation?rows=&seed= devolve o scorecard das mesmas linhas que GET /synthetic enviaria com esse par. utility_score (0–100) mede a fidelidade ao perfil (nulos, quantis p05–p95 normalizados pelo IQR, distância de variação total aos histogramas e às quotas categóricas, correlações dos pares), com utility_components e utility_weights. privacy_verdict é fail com sample_leak, extreme_leak ou pii_leak, warn com k_min abaixo de k_threshold (5), contado só sobre os quase-identificadores com valores do domínio real (numéricos, temporais ou categóricos), e pass no resto. O motivo chega como key (leaks em fail, k_below em warn, null em pass) e params (sample_columns, extreme_columns e pii_columns em leaks; k_min, k_threshold e qi_columns em k_below), com que a UI compõe o texto, e reason é o mesmo texto em pt-PT, de recurso. Só taxas, deltas, contagens e nomes de colunas. Cartão #174.

    • Alterado

      GET /datasets/{dataset_id}/synthetic corre a avaliação antes de enviar: com privacy_verdict = fail responde 409 e não envia nenhuma linha (CSV ou JSON); o detail traz code = synthetic_privacy_fail, a key e os params do motivo e o message em pt-PT. Em format=json, o manifest traz o scorecard em manifest.evaluation. O pack multi-tabela (POST /synthetic-packs) ainda não corre esta avaliação: o evaluation de cada tabela no manifesto do pack continua null. Cartão #174.

  27. v1.48.0

    • Novo

      Pack de dados sintéticos multi-tabela para dev/test e fixtures de CI: POST /synthetic-packs (tag synthetic, permitido a tokens) com exatamente um de connection_id, domain_id ou dataset_ids (até max_tables, no máximo 20), rows_per_table (até 20 000 por omissão), seed e format: zip (um CSV por tabela, schema.sql com chaves e FKs e manifest.json com estratégias, FKs, avisos, seed e o aviso «dados sintéticos — não contém dados reais») ou sqlite (as mesmas tabelas e o manifesto na tabela _datavault_manifest). As colunas ligadas pela linhagem (cliente_id → clientes) só levam chaves que existem na tabela de origem, com a taxa de nulos do perfil; num ciclo da linhagem, a FK que o fecha sai, com aviso. A mesma chamada sobre os mesmos perfis dá os mesmos bytes. Síncrono e com orçamento de tempo (30 s por omissão): excedido, 422 sem ficheiro. Um campo desconhecido no corpo dá 422. Cada workspace tem um limite de pedidos (20 em 10 minutos por omissão), partilhado por membros e tokens: acima, 429 com Retry-After. Um dataset ou domínio invisível para quem pede é «não encontrado». Cartão #175.

  28. v1.47.0

    • Novo

      Os bloqueadores e avisos do trust score (blockers[] e warnings[] de GET /datasets/{dataset_id}/trust e do trust de GET /datasets/{dataset_id}) ganham params: os números com que o texto se compõe, por key — critical_issue traz count; schema_change_unacknowledged traz count, grace_days e oldest_days; unused_dataset vem sem params. O message continua em pt-PT. Cartão #476.

  29. v1.46.0

    • Novo

      Travessia do grafo de linhagem, determinística e sem LLM (as mesmas consultas das tools get_lineage, impact_of, path_between e find_datasets do assistente e do servidor MCP): GET /datasets/{dataset_id}/lineage/traverse?direction=&hops= (upstream e/ou downstream até 3 saltos, com o caminho «A → B → C», a origem de cada aresta em kinds, o mapa de colunas e os exposures a jusante), GET /datasets/{dataset_id}/lineage/impact?column= (datasets, exposures e destinos a jusante e, com a coluna, as colunas que derivam dela), GET /datasets/{dataset_id}/lineage/path?target_id= (caminho mais curto até 6 saltos, no sentido do fluxo ou no inverso) e GET /datasets/find (filtros compostos: query, layer, domain, has_pii, min_score, usage_class). Até 40 datasets e 60 arestas (truncated), só nomes: nunca o owner, o email nem o URL de um exposure. Um dataset invisível para quem pede é «Dataset restrito». Cartão #173.

  30. v1.45.0

    • Novo

      Versões do data product: GET /domains/{domain_id}/versions lista as versões publicadas (a mais recente primeiro) com version (MAJOR.MINOR), bump (major quebra o consumidor: um dataset saiu, uma coluna do contrato ativo desapareceu ou o SLA enfraqueceu; minor é compatível), change_summary e contagens. GET /domains/{domain_id}/versions/{from_version}/diff/{to_version} devolve as mudanças entre duas versões no formato do diff de contratos, cada uma com impact; from_version=latest e to_version=current comparam o estado atual com a última versão e trazem next_version; de uma versão mais antiga até current, next_version é null. latest no destino ou current na origem dão 422. Cartão #168.

    • Novo

      GET /domains e GET /domains/{domain_id} ganham status (draft | published | deprecated), version e published_at (null até à 1.ª publicação). Os domínios que já existiam estão published na 1.0. Cartão #168.

    • Novo

      GET /domains/{domain_id}/readiness tem dois códigos novos: o bloqueador deprecated (data product descontinuado) e o aviso draft (em rascunho). Cartão #168.

  31. v1.44.0

    • Novo

      GET /knowledge/graph passa a ser o grafo do património: nós domain, exposure, metric e term (ao lado de connection, dataset, rule, issue, signal e kb_entry) e arestas feeds (linhagem upstream → downstream, com lineage_kind: inferred, declared, parsed, dbt ou openlineage; num par com várias, ganha a que não é inferred), member_of (dataset → domínio), consumed_by (dataset → exposure dbt), measures (métrica dbt → dataset de origem, resolvido pelo nome; um nome ambíguo fica sem aresta) e means (termo não depreciado do glossário → dataset, pelas ligações aprovadas, com as colunas em column). stats.by_edge_kind conta as arestas por tipo. No corte a 1500 nós ficam primeiro as fontes, os domínios e os datasets. Cartão #172.

    • Novo

      GET /knowledge/graph aceita focus=dataset:<id>: devolve só a vizinhança desse dataset a hops saltos (1 a 3; 2 por omissão), com teto de 400 nós — ficam primeiro os mais próximos. kinds (tipos de nó separados por vírgulas) limita os tipos de nó; o do foco entra sempre. hops fora de 1–3, kinds com um tipo desconhecido ou um focus que não seja dataset:<id> dão 422; um dataset que não é do workspace dá 404. Cartão #172.

  32. v1.43.0

    • Novo

      POST /trust/consumptions: um agente, aplicação, pipeline ou notebook regista os datasets que consumiu (até 50), a finalidade e a decisão de confiança que viu (decision_seen; omitida, fica a decisão atual da Trust API e o seu as_of em trust_as_of). O consumidor nasce no primeiro registo e é identificado por name + kind. Com consumer.run_ref, repetir o registo do mesmo run não cria consumos novos (replayed). Só ids e rótulos: campos desconhecidos dão 422. O consumer.name só aceita letras (de qualquer língua), algarismos, espaços e _ . : / -; marcação de Slack, Teams ou markdown dá 422. Responde 201; 404 se um dataset não é do workspace; 429 com Retry-After acima do limite por workspace. É uma escrita permitida aos tokens dvk_*. Um consumo em untrusted gera o evento consumer_untrusted_read e um aviso aos canais, sem abrir problema; os canais recebem no máximo um aviso por hora por consumidor e dataset. Cartão #166.

    • Novo

      GET /consumers e GET /datasets/{dataset_id}/consumers: os consumidores registados, com consumptions_7d, consumptions_30d, datasets_30d, decisions_7d (consumos dos últimos 7 dias por decisão), last_decision, last_purpose e last_consumed_at. No dataset, as contagens e a última decisão são as desse dataset. GET /consumers traz X-Total-Count. Cartão #166.

    • Novo

      Os consumidores afetados (exposures) de GET /incidents, GET /incidents/{incident_id} e GET /issues/{issue_id}/impact ganham node: exposure (dbt, como até aqui) ou consumer (consumidor registado com consumos nos últimos 30 dias). Um consumer traz framework, consumptions_7d, last_decision e last_consumed_at do dataset por onde o impacto chega, e o type é o tipo do consumidor (agent, application, pipeline, notebook). Cartão #166.

  33. v1.42.0

    • Novo

      Limiares afinados pelo feedback: GET /datasets/{dataset_id}/tuning devolve, por métrica×coluna (subtype dos issues de anomalia), o degrau em vigor (threshold, na escala clássica do |z|: 3,5 é o limiar calibrado sem afinação), se está silenciada, quantas vezes foi ignorada e resolvida, e o que o próximo «ignorar» faz (next_on_ignore). Traz também os reajustes de regras à espera de aprovação (retunes). Cartão #157.

    • Novo

      O cartão de explicação de uma anomalia (GET /issues/{issue_id}/explanation) traz tuned_threshold (o degrau afinado pelo feedback que entrou no z_threshold) e tuned_threshold_label («limiar afinado por feedback: 5,0»). Vêm null quando a métrica não tem afinação. Cartão #157.

  34. v1.41.0

    • Novo

      GET /domains/{domain_id}/consumers: quem depende do data product. Utilizadores e consumidores externos registados à mão (source=manual) e os sistemas de IA e exposures dbt que usam os datasets do domínio (source=derived). by_kind conta os quatro tipos (user, ai_system, dbt_exposure, external), total soma-os e summary é a frase de impacto («3 sistemas de IA e 2 dashboards dependem deste produto»), null sem consumidores. Um consumidor com channel_id recebe o aviso de uma violação crítica do SLA. Cartão #165.

    • Novo

      GET /domains/{domain_id}/readiness ganha consumers: {total, by_kind} com os quatro tipos de consumidor sempre presentes. É informativo e não muda o status. Cartão #165.

  35. v1.40.0

    • Novo

      GET /rules: cada regra traz last_result, o último resultado num scan, com status (passed, failed ou not_evaluated), scan_id, evaluated_at, observed e threshold. Antes do primeiro scan com a regra vem a null. Uma regra not_evaluated correu mas não se conseguiu verificar: não abre issue nem conta no score, e reason diz porquê (por exemplo, uma regra referential cuja referência, noutra ligação, passa das 200 000 chaves). Se a razão nomear um dataset de referência que quem pede não vê, sai uma frase sem o nome. Cartão #164.

    • Novo

      Regra referential (integridade referencial entre datasets): rule_type em GET /rules passa a poder ser referential, com params column, ref_dataset_id, ref_column e max_orphan_pct. Em GET /datasets/{dataset_id}/rules.dbt.yml sai como teste relationships (to: ref('<tabela>'), field), e em GET /datasets/{dataset_id}/contract e GET /domains/{domain_id}/contract como check invalidValues com arguments.references (<schema.tabela>.<coluna>). Uma referência a um dataset que quem pede não vê não dá nome nestes exports: sai do contrato, e no rules.dbt.yml fica só uma nota em comentário.

  36. v1.39.0

    • Novo

      GET /tickets/integrations/{integration_id}/targets: onde uma integração de tickets pode criar tickets, lido agora do fornecedor. No Jira, os projetos (key é a chave do projeto) com os tipos de issue de cada um (issue_types, sem subtarefas); no Linear, as equipas (key é o id da equipa e issue_types vem vazio). Até 100 destinos. default_project e default_issue_type são o destino configurado na integração (default_issue_type é null no Linear). Só lê: GET no Jira, *query* GraphQL no Linear. 404 se a integração não for deste workspace, 402 se o plano não incluir os webhooks de alertas (a mesma condição de POST /tickets), 502 se o fornecedor falhar ou se a proteção anti-SSRF recusar o endereço. Cartão #375.

  37. v1.38.0

    • Novo

      Marcação do conteúdo gerado por IA (Regulamento da IA, art. 50.º, n.º 2): ai_generated, ai_model e generated_at nos relatórios e diagnósticos RCA (GET /reports, GET /reports/{report_id}) e nas sugestões (GET /suggestions, suggestions em GET /datasets/{dataset_id}). ai_generated é false no texto do modo offline, que é determinístico; aí ai_model e generated_at vêm a null. Nas sugestões IA anteriores a esta versão, e nas que o assistente propõe, ai_model vem a null. O modelo nunca traz ARNs nem ids de conta ou de projeto: um id de recurso cloud sai como a família do modelo (por exemplo, claude-sonnet-5) ou como undisclosed. Cartão #511.

    • Novo

      GET /datasets/{dataset_id}/readme: ai_generated e ai_model do rascunho (a data é o generated_at que já existia) e published_ai_generated e published_ai_model da versão publicada, que continua marcada depois de aprovada. O export GET /datasets/{dataset_id}/readme.md de um README gerado por IA começa com um front matter YAML (ai-generated, ai-model, ai-generated-at).

    • Novo

      Exports GET /reports/{report_id}/export e GET /reports/{report_id}/export.pdf (e a partilha pública): <meta name="ai-generated"> (true ou false) e, quando houve IA, ai-model e ai-generated-at, mais o rótulo visível «Gerado por IA (modelo, data)» no cabeçalho. No PDF os mesmos valores vão para os metadados do documento, com os nomes sem hífens (aigenerated, aimodel, aigeneratedat).

  38. v1.37.0

    • Novo

      GET /datasets/{dataset_id}/parsing: campos compostos do último perfil (moradas, nomes completos, telefones com indicativo, NIF e nome na mesma coluna, datas em texto livre e montantes com moeda). Cada coluna traz o tipo (kind), o locale (pt, es ou en), a taxa de sucesso na amostra (success_rate, parsed/samples), a cobertura de cada coluna derivada (components), as máscaras dos valores que não se decompõem (failing_masks, dígito → 9, letra → A, sem valores) e a assinatura da sugestão com o SQL e o PySpark só de leitura (suggestion_signature, parse_standardize:<coluna>). Sem perfil, profiled é false.

  39. v1.36.0

    • Novo

      GET /tickets?entity_type=…&entity_id=…: os tickets externos (Jira, Linear ou outra ferramenta) ligados a um issue, a um incidente (inc-…), a uma sugestão, a uma proposta de contrato ou à classificação de uma coluna, com provider, external_key, url e status (open, in_progress, done ou unknown). O estado ainda não é sincronizado com a ferramenta: um ticket criado pelo DataVault nasce open; um ligado à mão, ou anterior a esta versão, fica unknown. Cartão #374.

    • Novo

      tickets em cada issue (GET /issues, e open_issues em GET /datasets/{dataset_id}): um issue passa a poder ter vários tickets externos, do mais antigo para o mais recente.

    • Novo

      Valores novos nos rastos: field = ticket (ticket criado) ou ticket_link (ticket existente ligado) em GET /issues/{issue_id}/events, com a chave do ticket em to_value; to_status = ticket em GET /suggestions/{suggestion_id}/events (o estado da sugestão não muda). Trata um valor que não conheças como «outro».

    • Descontinuado

      external_ticket_url nos issues — usa tickets. Continua a responder, com o URL do primeiro ticket ligado, e só sai numa versão major, não antes de 2027-04-05.

  40. v1.35.0

    • Novo

      POST /integrations/dbt/run-results: o dbt test/dbt build corrido no pipeline do cliente devolve os resultados ao DataVault. Corpo {run_results, manifest, connection_id?} com o target/run_results.json (schema v4 a v6) e o target/manifest.json do mesmo projeto. Cada teste casa com o dataset (schema + alias) e com a regra — pelo rule_id em config.meta.datavault, senão pela coluna e tipo de teste. Com regra, o resultado conta para GET /datasets/{dataset_id}/verdict (uma regra crítica que falha no dbt dá fail; o envio seguinte com pass limpa-o); sem regra equivalente (testes singulares, relationships), abre ou resolve um issue rule_violation com subtype dbt_test (fail/error → critical, warn → warning). O mesmo invocation_id reenviado não grava nada outra vez (status duplicate). Com domínios restritos, só casa com os datasets em que quem envia pode escrever; os outros ficam como no_dataset, sem dataset_id nem rule_id. Só metadados de execução: schema fechado — chaves desconhecidas, campos com linhas (failed_rows, rows…) ou um failures que não seja uma contagem dão 422. Aceita tokens de API. Cartão #394.

    • Novo

      dbt_core em OrchestratorSource (filtro source de GET /orchestrator-runs): cada dbt test recebido fica como um run com o invocation_id como external_run_id. Cartão #394.

    • Alterado

      GET /datasets/{dataset_id}/verdict: cada entrada de failed ganha engine — datavault (scan do DataVault) ou dbt (dbt test do cliente; observed é então a contagem de linhas falhadas). O veredito é o do último scan do DataVault, com o resultado dbt mais recente de cada regra por cima. Cartão #394.

  41. v1.34.0

    • Novo

      Veredito do data product: GET /domains/{domain_id}/verdict agrega os vereditos dos datasets do domínio para o gate de CI — fail se algum falha, warn se algum avisa, unknown se nenhum tem veredito (domínio sem datasets ou sem scans) e pass nos outros casos. Devolve domain_id, domain_name, verdict, with_policy, counts (quantos datasets em pass, warn, fail e unknown) e datasets[], ordenados por nome: cada um é o GET /datasets/{dataset_id}/verdict desse dataset, mais dataset_id e dataset_name. with_policy (omissão true) conta as violações da política de dados, como o veredito por dataset; com false, só a qualidade. Cartão #148.

    • Novo

      Contratos ODCS do data product: GET /domains/{domain_id}/contract devolve um bundle YAML com vários documentos, um contrato ODCS v3.1.0 por dataset do domínio, separados por --- e ordenados pelo nome do dataset (vazio num domínio sem datasets). Cartão #148.

    • Novo

      O contrato ODCS de um dataset (GET /datasets/{dataset_id}/contract) diz a que data product pertence: tenant (nome do workspace), domain e dataProduct (nome do domínio, quando o dataset pertence a exatamente um; a vários, os nomes vão para customProperties como datavault.domains) e team com members: [{username, role: owner}], os owners pelo nome. Nunca leva emails. Cartão #148.

  42. v1.33.0

    • Novo

      tls_mode e tls_verified nas conexões (GET /connections e GET /connections/{connection_id}), só em Postgres e MySQL/MariaDB. tls_mode é o modo configurado: verify-full (cifra e verifica o certificado e o hostname do servidor, o default das conexões novas), require (cifra sem verificar) ou disable (sem TLS). Vem null numa conexão criada antes desta versão, que mantém o comportamento antigo (cifra se puder, sem verificar). tls_verified só é true com verify-full. Nos outros conectores, os dois vêm null. Cartão #449.

  43. v1.32.0

    • Novo

      Elementos de dados críticos (CDE) e dossiê regulatório. GET /cde lista os elementos críticos — um dataset ou uma coluna marcado para o BCBS 239 (bcbs239), a Solvência II (solvency2) ou outra regulação (custom), com referência livre e owner —, filtráveis por regulation e dataset_id, com o total no header X-Total-Count. GET /cde/dossier devolve o dossiê calculado na hora, por regulação e requisito (princípios 3 a 6 do BCBS 239; art. 19.º, n.os 1 a 3, do Regulamento Delegado (UE) 2015/35): cada elemento com os controlos por dimensão e o último resultado de cada regra, os problemas abertos, o SLA, a linhagem a um salto, o contrato ODCS e as lacunas, e o KPI coverage_pct (dimensões exigidas com controlo e resultado nos últimos 30 dias). Filtros regulation e domain_id. GET /cde/dossier.md e GET /cde/dossier.csv exportam o mesmo dossiê. Estados por dimensão: gap, no_result, failing, passing; por requisito também human (avaliação humana). Nenhum estado afirma conformidade. Determinístico, sem IA generativa. Cartão #392.

  44. v1.31.0

    • Novo

      suggestions_status nos datasets (GET /datasets e GET /datasets/{dataset_id}): as sugestões IA deixaram de atrasar o scan — o scan fica completed com os perfis, issues, scores e sugestões heurísticas, e a IA é gerada depois, numa fila. Valores: pending (a gerar), ready, failed (ficam as heurísticas) ou limited (acima do teto de datasets com IA por scan — ficam as heurísticas); null quando não há nada a gerar (IA desligada ou sugestões IA reutilizadas do scan anterior). O stats do scan (GET /scans/{scan_id}) ganha ai_suggestions (max_datasets, queued, limited) quando o scan deixou datasets à espera da IA; o duration_s é a duração do scan, sem a IA. Cartão #385.

  45. v1.30.0

    • Novo

      resample em POST /scans (e no ScanOut devolvido): true faz este scan ler uma amostra de linhas nova, com outra semente. Por omissão (false) cada dataset lê sempre a mesma amostra, para os clusters de duplicados e as violações de padrão não oscilarem entre scans sem os dados mudarem. Cartão #317.

    • Novo

      sample_method em dimensions_meta.unicidade de score_history (GET /datasets/{dataset_id}): como a amostra foi escolhida — system ou bernoulli (amostragem nativa TABLESAMPLE/SAMPLE da fonte), hash (hash da chave primária), stride (uma linha em cada k de um ficheiro), head (as primeiras linhas) ou full. null nos scores anteriores a esta versão. Cartão #317.

    • Alterado

      A amostra de linhas dos scans (regras pattern/required_fields, duplicados e a unicidade do score) deixa de ser «as primeiras N linhas que a fonte devolveu» e passa a ser escolhida pelo método do dialeto, com semente fixa por dataset; sample_size de dimensions_meta passa a ser o tamanho dessa amostra. O perfil da tabela (metrics sem coluna) ganha sample e value_sample (method, rows, fraction, seed, deterministic, covers_table), o impact_json do issue de duplicados ganha sample e os details dos resultados de regras avaliadas sobre a amostra também. A qualidade de rótulos lê com o mesmo método e o last_result de GET /datasets/{dataset_id}/label-quality ganha sample. No primeiro scan depois desta versão a amostra muda uma vez: clusters de duplicados, unicidade e percentagens de violação de padrão podem mudar sem os dados mudarem. Cartão #317.

  46. v1.29.0

    • Novo

      Webhooks de saída assinados (canal «Webhook genérico» dos alertas): cada entrega leva X-DataVault-Event, X-DataVault-Delivery (UUID, igual em todas as tentativas), X-DataVault-Timestamp e X-DataVault-Signature: v1=<HMAC-SHA256 do secret do canal sobre "{timestamp}.{corpo}">. Retry em 5xx, timeout e falhas de ligação (1 s, 5 s e 25 s); um 4xx é final. O corpo ganha occurred_at, e o contrato (eventos, JSON Schema do WebhookPayload, verificação em Python e Node, janela anti-replay de 5 minutos) está em docs/WEBHOOKS.md. O secret é mostrado uma vez na criação do canal ou em POST /notification-channels/{id}/rotate-secret; canais anteriores enviam sem assinatura até o rodarem. Cartão #323.

    • Alterado

      O digest semanal do webhook genérico passa a sair com event: "datavault.digest" (antes, datavault.alerts com as listas vazias), e o «Testar canal» com datavault.test. Cartão #323.

  47. v1.28.0

    • Novo

      GET /issues/{issue_id}/explanation: campos z_threshold (limiar |z| aplicado à baseline da anomalia), p_value (p-valor do modelo nulo calibrado para o tamanho da baseline) e confidence (high | medium | low, a partir do p-valor), e o objeto baseline (kind, weekday, weekday_label, points), que a resposta já calculava mas não expunha. Em anomalias anteriores, p_value e confidence vêm null e z_threshold é 3,5. Cartão #319.

    • Alterado

      Issues anomaly (GET /issues): o limiar deixou de ser |z| ≥ 3,5 fixo. Passa a depender do número de pontos da baseline (20,2 com 5, 7,3 com 8, 5,2 com 12), que tem agora de ter pelo menos 5 pontos, e cada scan aplica Benjamini–Hochberg (α = 0,05) às séries de cada dataset. Esperam-se menos anomalias em séries com ruído. O impact_json das novas anomalias traz z_threshold, p_value e confidence. Método e números do harness: docs/METODOS.md, «Anomalias». Cartão #319.

  48. v1.27.0

    • Novo

      GET /datasets/{dataset_id}/score/explain?from=&to=: «porque mudou o score?» — o Δscore entre dois scans decomposto por dimensão (peso·Δdimensão) e, dentro de cada dimensão, pelos drivers (column_nulls, duplicates, rule_failing/rule_passing, rule_set_changed, freshness, drift, …) com points em pontos de score, com sinal, e params para formatar a frase. Σ drivers + unexplained_points = delta. Por omissão compara os dois últimos scans concluídos; 422 sem dois scans com score. Cartão #318.

  49. v1.26.0

    • Novo

      dimensions_meta em cada ponto de score_history de GET /datasets/{dataset_id}: as dimensões do score calculadas sobre uma amostra e não sobre a tabela inteira — hoje só unicidade, com estimated, sample_size (as primeiras N linhas que a fonte devolveu, sem aleatoriedade) e duplicate_rows. Vazio ({}) quando todas cobrem a tabela inteira e nos scores anteriores a esta versão. Cartão #294.

    • Corrigido

      Dimensão unicidade do score (dimensions.unicidade e, por arrasto, score e latest_score): quando os duplicados são procurados numa amostra — tabelas acima de 5 000 linhas ou ficheiros amostrados —, a taxa passa a ser a da amostra, a mesma base que o issue de duplicados já usava («% da amostra»). Antes, os duplicados da amostra eram divididos pelo row_count da tabela inteira e a dimensão ficava perto de 100 em tabelas grandes. **Os scores de tabelas grandes com duplicados descem** a partir do primeiro scan depois desta versão (ex.: 500 linhas redundantes nas 5 000 lidas de uma tabela de 1 M de linhas: unicidade 90,0 em vez de 100,0, score −2,5 pontos); os scores já gravados não são recalculados. Reveja os limiares min_score de SLAs e contratos nessas tabelas.

  50. v1.25.0

    • Novo

      GET /datasets/{dataset_id}/freshness e o bloco freshness em GET /datasets/{dataset_id}: a coluna que mede a frescura do dataset — column (fixada; null = escolhida em cada scan por nome e tipo: updated/modified > ingest/loaded > created > event > outros TIMESTAMP > DATE, sem colunas de prazo, validade, fim, nascimento ou apagamento), used_column, detected_column e source (configured, detected, connector) do último perfil, latest_timestamp em UTC, timezone (aware ou assumed:<fuso>), source_timezone, future_columns (máximo mais de 24 h depois do perfil — ficam fora da escolha automática), candidates e a regra freshness do dataset (rule_id, rule_column, rule_max_age_hours). Cartão #292.

    • Novo

      source_timezone nas conexões (GET /connections, GET /connections/{connection_id}): o fuso IANA em que se leem os timestamps sem fuso da fonte (omissão UTC, o comportamento de sempre).

    • Corrigido

      Regra freshness: o parâmetro column passa a medir essa coluna (antes era ignorado e media-se a primeira coluna de data da tabela); um timestamp mais de 24 h no futuro dá a regra como falhada com details.error em vez de «fresca». Nas métricas de tabela dos perfis, latest_timestamp vem em UTC com o desvio (+00:00) e acompanhado de latest_timestamp_column e latest_timestamp_tz.

  51. v1.24.0

    • Novo

      GET /datasets/{dataset_id}/rules.dbt-tests.zip: as regras expression e custom_sql ativas do dataset como testes singulares do dbt — um ZIP com tests/*.sql (o SELECT das linhas violadoras no dialeto da fonte, com ref() no lugar da tabela; o texto de cada regra vai dentro de blocos Jinja raw, e o dbt não executa Jinja escrito numa regra) e um README com as regras que ficaram de fora e porquê.

  52. v1.23.0

    • Alterado

      POST /scans (e os pedidos de scan por agendamento, webhook do dbt Cloud e SDK): quando o workspace já tem max_queued_scans scans pedidos em fila ou a correr (5 no plano por omissão; ver o catálogo de planos), a API responde 429 com Retry-After (segundos) e o corpo {"detail", "limit": "queued_scans", "queued", "max_queued", "retry_after_s"} — é backpressure de capacidade, não o rate limit por pedido (limit distingue-os; o rate limit usa retry_after). Nada é rejeitado de forma definitiva: repetir o pedido depois de retry_after_s volta a enfileirar. Cartão #251 (escala enterprise).

  53. v1.22.0

    • Novo

      GET /governance?months=1..12 e GET /governance/domains/{domain_id}: eficácia do programa de governação (o painel do CDO). Cada KPI tem code estável, group (coverage, resolution, sla, adoption, quality, incidents, usage), unit, direction, value com a sample que o sustenta, target e target_source (workspace → organization → default; no domínio com SLA declarado, domain_sla é a meta do SLO), status (on_target, at_risk, off_target, no_data, no_target), gap, previous/delta e trend por mês de calendário UTC (o corrente com partial). KPIs: cobertura de owner, documentação, classificação, contratos e regras (ownership_pct, documentation_pct, classification_pct, contracts_pct, rules_pct); cycle_time_hours e mttr_critical_hours; slo_attainment_pct e due_date_met_pct; suggestion_adoption_pct e never_again_adoption_pct; score_<dimensão> por dimensão do scorecard; critical_incidents (uma cascata de linhagem conta como um) e active_users_pct/datasets_in_use_pct. O fluxo mede os últimos 30 dias (window=last_30d, vs. os 30 anteriores); cobertura, uso e scores o estado atual (window=now, vs. o fecho do mês anterior). O do workspace traz domains (a matriz por domínio, com semáforo por KPI) e targets. Datasets em domínios restritos sem acesso não contam.

    • Novo

      GET /governance/scorecard.csv (o scorecard executivo: uma linha por KPI com valor, meta e origem, estado, folga, variação e um valor por mês, e uma por domínio × KPI) e GET /governance/evidence.md (a evidência do programa em Markdown para o pack de procurement ou due diligence do cliente: resumo do semáforo, KPIs com meta e tendência, domínios e o método). Só agregados — sem nomes de datasets, colunas, amostras nem pessoas; células CSV protegidas contra injeção de fórmulas.

  54. v1.21.0

    • Novo

      GET /catalog/metadata-quality e GET /domains/{domain_id}/metadata-quality: qualidade do catálogo e dos metadados (KPIs de governação). pct = checks cumpridos ÷ aplicáveis (os não aplicáveis ficam fora do denominador; null sem nada medido), por dimensão (completeness, timeliness, consistency) e por check com código estável — has_owner, has_description, has_classification, has_domain, has_contract, has_rules, description_current (o schema não mudou depois da descrição/README), owner_active (owner não desativado, não saiu do workspace e com sessão nos últimos 90 dias), unused_has_owner (dataset sem uso medido tem owner ativo) e column_docs_match_schema — com coverage_pct (% dos datasets aplicáveis que cumprem). findings são os problemas de metadados (missing_owner, description_stale, owner_inactive, unused_without_owner, …) com sugestões por canal (readme, catalog_import, manual) e o endpoint que resolve; target (meta, reached, gap_pp), trend (fotografia diária; a do workspace inteiro só para quem vê o workspace inteiro), domains (por domínio) e external_catalog (cobertura e termos de glossário lidos do catálogo externo ligado). O do workspace traz os 50 piores datasets em datasets (datasets_truncated). Datasets em domínios restritos sem acesso não contam.

    • Novo

      GET /catalog/metadata-quality.csv?level=datasets|summary: export CSV para um pack de governação ou de procurement — uma linha por dataset com o estado de cada check (cumprido, falhou, n/a) e os códigos dos problemas, ou o resumo do workspace e de cada domínio com a meta e a cobertura por check. Só metadados; células protegidas contra injeção de fórmulas.

  55. v1.20.0

    • Novo

      GET /datasets/{dataset_id}/pipeline-health: a saúde dos pipelines Fivetran/Airbyte que escrevem o dataset — status (o pior entre ok, failed, delayed, paused, unknown; not_monitored sem integrador ligado) e, por pipeline, último sucesso e última falha, mensagem do integrador, cadência e atraso tolerado, a ponta de destino e a de origem (updated_at e age_s: «origem atualizada há X, destino há Y»), lag_s (quanto o destino vai atrás da origem), a reconciliação de contagens entre os perfis das duas pontas (recon, com comparable) e as tabelas que entraram/saíram do pipeline (schema_change); recent_runs traz os syncs mais recentes. Uma origem num domínio restrito aparece como «Dataset restrito», sem tabela, latência nem reconciliação.

    • Novo

      GET /orchestrator-runs aceita source=fivetran e source=airbyte: os syncs dos integradores ligados, lidos das APIs de metadados (só leitura), com o pipeline em job_id/job_name. Os issues ganham o tipo pipeline_sync (subtipos failed e delayed).

  56. v1.19.0

    • Novo

      Política de versionamento e depreciação da API (secção «Estabilidade» do portal): o que está neste portal é o contrato; uma versão minor só acrescenta; remover, renomear ou mudar o tipo ou o significado de um campo, parâmetro ou código é uma quebra e só acontece numa major. Uma operação depreciada é anunciada aqui (tipo deprecated) e responde com os headers Deprecation (RFC 9745), Sunset (RFC 8594) e Link com rel="deprecation" pelo menos 180 dias antes de deixar de responder. O CORS expõe estes três headers.

    • Novo

      GET /health passa a incluir api_version — a versão desta API, a mesma de GET /version.

    • Novo

      GET /connections/{connection_id}/query-log e GET /connections/{connection_id}/query-log/export: o que o DataVault executou na fonte, por forma de statement normalizada (sem valores) — operation (scan, discover, test), scan_id, query_tag, endpoint (primário ou réplica), execuções, erros, duração total e máxima (ms), linhas e primeira/última execução. Filtros scan_id, operation e days (1–366); a lista pagina com limit (omissão 100, máximo 500) / offset e o export devolve um download em format=csv (omissão) ou format=json.

    • Novo

      GET /connections e GET /connections/{connection_id} passam a trazer a política da fonte definida pelo DBA: blackout_timezone, blackout_windows (janelas recorrentes em hora local), blackout_active (a janela em curso, ou null), blackout_upcoming (próximas ocorrências), max_concurrent_queries e max_sample_rows. GET /connections/types passa a trazer label, group e docs_anchor de cada conector.

    • Alterado

      POST /scans responde 409 com detail = {code, message, …} quando a fonte está numa janela de blackout (code: blackout_active) ou já tem o máximo de scans em curso (code: source_busy); nenhuma query chega à fonte. Os campos novos override_blackout e override_reason (motivo com pelo menos 5 caracteres, auditado) forçam o scan dentro de uma janela, mas só para admins — com um token de API dão 403.

  57. v1.18.0

    • Novo

      Servidor MCP remoto em produção: https://mcp.praxis.lbostuff.tech/mcp (MCP Streamable HTTP, TLS). Autentica cada pedido com um token de API dvk_ (Authorization: Bearer) validado na API — sem token, 401 — e expõe as mesmas tools e resources só de leitura (incluindo get_verdict e get_trust_decision) sobre o workspace do token. Liga-se sem o código do backend: claude mcp add --transport http datavault https://mcp.praxis.lbostuff.tech/mcp --header "Authorization: Bearer dvk_…". Pedidos com um header Host fora da allowlist recebem 421 (proteção anti-DNS-rebinding) e com um Origin de outro site 403.

    • Alterado

      GET /ide/agent-config passa a devolver em produção remote_available: true, mcp_url com o MCP remoto e local_server_cmd: null: as configurações geradas para Claude Code, Claude Desktop, Cursor, VS Code e Codex apontam para ele em vez de http://127.0.0.1:8765/mcp.

  58. v1.17.0

    • Novo

      Scorecard por dimensão de qualidade (DAMA/ISO 8000): GET /datasets/{id}/dimensions devolve, para completude, unicidade, validade, atualidade, consistência, razoabilidade, exatidão e integridade, o score 0–100 do último scan (percentagem de checks aprovados), o n.º de checks, os issues abertos e o histórico por scan. Uma dimensão sem checks vem com measured: false e score: null («não medido»), nunca 100. GET /dashboard/dimensions agrega por workspace e por domínio, só com os datasets medidos (datasets_measured de datasets_total), e GET /dashboard/dimensions.csv exporta uma linha por dataset × dimensão. O score composto não muda.

    • Novo

      GET /issues aceita ?dimension= (ex.: completeness) e cada issue passa a trazer o campo dimension, resolvido pelo mapa determinístico tipo/subtipo → dimensão: cada issue cai numa só dimensão. Uma dimensão desconhecida devolve 422.

  59. v1.16.0

    • Novo

      GET /version (sem autenticação): {version, build_sha, api_version} — a versão do produto (semver, 1.0.0 na primeira release estável), os 7 hex do commit a servir e a versão desta API, que continua com semver próprio. GET /health e GET /health/ready passam a incluir build_sha. Útil para citar a versão em vigor num pedido de suporte ou num contrato.

  60. v1.15.0

    • Novo

      GET /rules, GET /suggestions, GET /reports e GET /duplicates passam a devolver o total filtrado no header X-Total-Count (os mesmos filtros do pedido, antes de limit/offset, e só o que o utilizador pode ver pelas permissões por domínio), como GET /datasets. O corpo continua a ser a lista da página; limit por omissão 50, máximo 500.

  61. v1.14.0

    • Novo

      GET /bi e GET /bi/{feed}: feeds tabulares estáveis para Excel e Power BI — scores (último score por dataset, dia e domínio), issues, slo (dataset-dia), finops (retrato do custo por dataset), activity (issues e scans) e a dimensão datasets. JSON ({feed, schema_version: 1, columns, total, limit, offset, next_offset, rows}) ou CSV (format=csv, bom=1 opcional) na mesma rota; paginação limit (≤ 10 000, omissão 5000) / offset com ordem estável; filtros since, until (UTC, janela por omissão de 90 dias em scores/slo/activity), domain_id, dataset_id e status (só issues). Colunas fixas em snake_case, datas ISO em UTC e decimais com ponto; só se acrescentam colunas no fim. ETag e If-None-Match → 304; X-Total-Count e X-Next-Offset. O catálogo traz o código Power Query (M). Só leitura, sem OData; aceita tokens de API.

  62. v1.13.0

    • Novo

      GET /ide/agent-config?tool=: configuração MCP pronta a colar para agentes de código (claude-code, claude-desktop, cursor, vscode, codex; omitido = todas) — ficheiro (.mcp.json, claude_desktop_config.json, .cursor/mcp.json, .vscode/mcp.json, ~/.codex/config.toml), comando de instalação e snippets de regras (AGENTS.md, CLAUDE.md, .cursorrules, .github/copilot-instructions.md). Nunca devolve o token: traz token_placeholder e, onde a ferramenta o permite, lê-o de DATAVAULT_TOKEN. Só leitura.

  63. v1.12.0

    • Novo

      GET /datasets/{id}/trust-facts e GET /domains/{id}/trust-facts (?detail=minimal|full, omissão minimal): factos de confiança num envelope estável (schema_version: 1) — veredito com política e decisão agente, score de qualidade e tendência 7d, trust score, contrato (estado e versão ativa), política (estado e regras violadas), contagens de sensibilidade por nível, AI-readiness por caso de uso, freshness vs alvo e attainment 30d, domínios e owner, n.º de upstream/downstream, último scan, issues abertos por severidade e links relativos; o do domínio junta um agregado e os factos de cada dataset. Só metadados: minimal nunca traz nomes de colunas. ETag (hash dos factos, sem as_of) e If-None-Match → 304; Cache-Control: private, no-cache. Só leitura, sem LLM; aceita tokens de API.

  64. v1.11.0

    • Novo

      GET /domains/{id}/readiness?use_case= (rag, ml, agente, bi; omissão bi): readiness de um data product por caso de uso — ready | ready_with_warnings | not_ready, blockers[] e warnings[] com code estável (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 e message, detalhe por dataset, SLA do mês, checked_at e valid_until (próximo scan agendado). Só leitura, sem LLM; aceita tokens de API.

  65. v1.10.0

    • Novo

      Decisão de confiança (GET /datasets/{id}/trust-decision, POST /trust-decisions): campos novos required_actions[] (o que falta para subir de decisão; nunca vazio em untrusted/unknown) e valid_until (as_of + ttl_seconds). Só acrescentados — os campos, códigos e TTL anteriores não mudam. Cada consulta feita com um token de API fica no trilho de auditoria (trust_decision_checked, sem PII; o lote é um só evento).

  66. v1.9.0

    • Novo

      GET /datasets/{id}/trust-decision?purpose= e POST /trust-decisions (≤ 50 ids): decisão de confiança antes de consumir um dataset para uma finalidade (agente, rag, treino, bi) — trusted | caution | untrusted | unknown, razões com code estável e message_pt, veredito, AI-readiness, freshness vs SLA, incidentes abertos, PII sem máscara, contrato e ttl_seconds (também em Cache-Control: max-age). Só leitura; o POST aceita tokens de API.

  67. v1.8.0

    • Novo

      GET /ide/resolve?ref= e GET /ide/datasets/{id}/columns: resumo de qualidade por tabela referenciada num ficheiro SQL/dbt (score, issues abertos, contrato ativo, PII, violações da política de dados) e perfil por coluna (tipo, nulos %, distintos, classificação) — base da extensão VS Code.

    • Alterado

      POST /datasets/{id}/diff/{other_id} passa a aceitar tokens de API — o data-diff só lê as duas fontes e não persiste nada (CI e extensão VS Code).

  68. v1.7.0

    • Novo

      GET /datasets/{id}/contract/versions, GET /datasets/{id}/contract/versions/{n}, GET /datasets/{id}/contract/diff?from=&to= e GET /datasets/{id}/contract/proposals[/{id}]: versões numeradas do contrato ODCS acordado com o produtor da fonte, diff secção a secção e propostas em revisão.

    • Alterado

      GET /datasets/{id}/contract aceita source=active para descarregar o contrato aceite pelo produtor, e GET /datasets/{id}/verdict passa a validar o dataset contra esse contrato (contract.active_version): coluna prometida que desapareceu ⇒ fail; tipo divergente, obrigatória a aceitar nulos ou check sem regra ativa ⇒ warn.

  69. v1.6.0

    • Novo

      Rate limiting em toda a API: cada token de API tem um limite próprio (300 pedidos/min por omissão, além do limite por IP) e o export RGPD e o pedido de relatórios têm limites adicionais. As respostas trazem X-RateLimit-Limit, X-RateLimit-Remaining e X-RateLimit-Reset; acima do limite a API responde 429 com Retry-After (segundos) e retry_after no corpo.

  70. v1.5.0

    • Alterado

      POST /scans passa a aceitar tokens de API — orquestradores (Airflow, CI) disparam scans e esperam pelo veredito com GET /datasets/{id}/verdict. Sujeito à quota de scans do plano.

    • Novo

      GET /orchestrator-runs e POST /orchestrator-runs: runs de orquestradores (dbt Cloud, Airflow) guardados como contexto do diagnóstico de causa-raiz («o job dbt X falhou 20 min antes desta issue»). O POST aceita tokens de API; só metadados do run.

  71. v1.4.0

    • Novo

      GET /reports/{id}/trace: percurso de cada geração de relatório como grafo (fonte, âmbito, perfil, regras, anomalias, sinais, resumo, escrita, rótulo de IA, exportação, entrega), com tempos e alternativas usadas. lang=pt|en.

    • Novo

      GET /routines/blocks: catálogo versionado dos passos do pipeline de relatórios — o que entra, o que sai, o que é configurável hoje e os selos de cada passo.

  72. v1.3.0

    • Novo

      POST /rag-gate: gate de qualidade para pipelines RAG — veredito aceitar/rever/rejeitar por documento (dados pessoais, duplicados do corpus, idioma, boilerplate) antes de indexar. Aceita tokens de API; nenhum conteúdo é guardado.

  73. v1.2.0

    • Novo

      GET /datasets/{id}/verdict: veredito compacto pass/warn/fail/unknown para gates de CI.

    • Novo

      GET /datasets/{id}/schema-changes: histórico de mudanças de schema com impacto a jusante.

    • Novo

      GET /incidents e GET /incidents/{id}: issues relacionadas agrupadas num só incidente via linhagem.

    • Novo

      GET /knowledge e GET /knowledge/graph: pesquisa na base de conhecimento e grafo de relações.

    • Novo

      GET /dashboard/pii e GET /dashboard/pii.csv: mapa de PII do workspace — datasets × tipos de dado pessoal (email, NIF, telefone, morada, saúde…), origem da deteção, cobertura por regras ativas e export CSV para o DPO. Só metadados, nunca os valores.

    • Novo

      GET /dashboard/slo e GET /dashboard/slo.csv: cumprimento das slaProperties de latência do contrato ODCS face à idade observada nos perfis — attainment mensal e por dataset, violações com os issues do dia e export CSV.

    • Novo

      GET /lineage/sankey: linhagem por camada medallion (Bronze → Silver → Gold) com score, volume estimado e risco herdado de montante.

    • Novo

      GET /catalog/treemap: o catálogo inteiro agregado por fonte e schema, com score e risco.

  74. v1.1.0

    • Novo

      GET /datasets/{id}/contract (ODCS v3.1) e GET /datasets/{id}/contract.dbt.yml: contrato de dados exportável.

    • Alterado

      Tokens de API passam a estar disponíveis a partir do plano Team.

  75. v1.0.0

    • Novo

      Tokens de API dvk_* só de leitura, criados e revogados por administradores do workspace.

    • Novo

      Leitura de datasets, scans, issues, sugestões, regras, linhagem e AI-readiness em /api/v1.

    • Segurança

      Tokens guardados apenas como hash SHA-256; qualquer método além de GET devolve 403.

Ainda não tens workspace?

Começa no plano gratuito e passa a Team quando precisares de tokens de API.