API de Exportação de Conhecimento
A API de Exportação de Conhecimento entrega o conteúdo curado no DATTA — documentos otimizados, FAQ, glossário e trechos — para chatbots e sistemas externos. Você curou a base na plataforma; agora o chatbot do portal, a intranet ou o sistema de atendimento precisam desse conteúdo, e sem acesso ao DATTA inteiro. A API resolve a integração: REST versionada, com autenticação por service account, sincronização incremental e limite de requisições.
Governança de fábrica: só sai conteúdo aprovado na Curadoria de Conhecimento e, para trechos, apenas conteúdo vigente. Rascunhos e conteúdo substituído nunca são exportados. Toda resposta carrega o header de contrato X-Knowledge-Export-Version: 1.
Arquitetura e decisões de projeto: exportação de conhecimento. Autenticação geral e convenções de erro: visão geral da API do DATTA. As rotas exatas estão na referência de API.
1. Service accounts
Credencial máquina-a-máquina com escopo por domínio (contexto), permissão fixa KNOWLEDGE_EXPORT e secret revelado uma única vez.
1.1 Operações de gestão
| Operação | Permissão exigida | Entrada |
|---|---|---|
| Criar conta | DATA_ACCESS_MANAGE_USERS | {nome, descricao, dominios: ["MEC", ...]} |
| Listar contas | DATA_ACCESS_VIEW ou DATA_ACCESS_MANAGE_USERS | — |
| Revogar conta | DATA_ACCESS_MANAGE_USERS | {"reason": "..."} (obrigatório) |
| Trocar credenciais por token | pública (limite de 10/min por clientId+IP) | {clientId, clientSecret} |
| Consultar situação da conta | uso interno entre componentes da plataforma | — |
Por padrão de perfis, apenas administradores possuem DATA_ACCESS_MANAGE_USERS e KNOWLEDGE_EXPORT.
1.2 Criar (reveal-once)
A criação devolve 201 com a credencial completa — a única vez em que o secret aparece:
{ "id":"<uuid>", "nome":"bot-mec", "dominios":["MEC"],
"clientId":"<uuid>", "clientSecret":"dsa_<base64url>",
"aviso":"Guarde o clientSecret agora: ele não será exibido novamente." }- O
clientSecret(dsa_+ 32 bytes aleatórios) é armazenado apenas como hash bcrypt e nunca reaparece — guarde-o no seu cofre de segredos no ato da criação. - A conta é persistida como um registro
ServiceAccountna base de sistemadatta(Neo4j). - Auditoria:
DATA_ACCESS.SERVICE_ACCOUNT_CREATED. - A permissão da conta é fixa
["KNOWLEDGE_EXPORT"](v1); não há tempo de vida por conta — o tempo de vida do token é global (datta.auth.service-account-token-ttl, padrão 900 segundos).
1.3 Obter token (client-credentials)
Enviando clientId e clientSecret, a conta recebe um token de curta duração:
{"accessToken":"<jwt>","tokenType":"Bearer","expiresIn":900,"scope":["MEC"]}Claims do token (HS256, mesma chave dos tokens de usuário): sub = svc:{id}, type = service-account, scope = [domínios], permissions = ["KNOWLEDGE_EXPORT"].
Qualquer falha — clientId inexistente, secret errado, conta revogada — devolve o mesmo 401 genérico "Credenciais inválidas" (anti-enumeração, com comparação de tempo constante). Acima de 10 trocas por minuto para o mesmo clientId+IP, a resposta é 429 com Retry-After: 60.
1.4 Revogar
A revogação exige reason e é sempre suave (nunca exclusão física), idempotente, com auditoria DATA_ACCESS.SERVICE_ACCOUNT_REVOKED:
{"id":"<uuid>","ativo":false,"revogadoEm":"..."}Propagação: a situação da conta é consultada com cache de 60 segundos (chave datta:knowledge:export:sa-status:{id}) — tokens ainda válidos param de funcionar em até 60 segundos, com 401 "Credencial revogada. Solicite uma nova credencial ao administrador."
Interface: a gestão de service accounts na página de acesso a dados ainda não foi implementada — hoje ela é feita 100% pelas operações acima.
2. Coleções exportadas
Cada coleção é lida por domínio. O {dominio} aceita [a-zA-Z0-9_-]{1,64} e é comparado ao escopo da credencial de forma normalizada (minúsculas, alfanumérico). A autorização acontece em camadas: authority KNOWLEDGE_EXPORT → escopo de domínio → verificação de revogação → limite de requisições.
Nos exemplos abaixo, $EXPORT é o endereço base de exportação do seu ambiente (veja a referência de API) e $TOKEN é o token obtido na seção 1.3.
2.1 Manifesto — inventário + ETag
curl -H "Authorization: Bearer $TOKEN" "$EXPORT/MEC/manifest"{
"dominio": "MEC",
"snapshotVersion": "9f2c3a1b0d4e5f67",
"geradoEm": "2026-07-09T12:00:00Z",
"colecoes": {
"documentos": {"total": 12, "lastModified": "2026-07-08T18:20:11Z"},
"faq": {"total": 9, "lastModified": "2026-07-08T18:20:14Z"},
"glossario": {"total": 4, "lastModified": "2026-07-07T10:02:00Z"},
"chunks": {"total": 3210, "lastModified": "2026-07-08T18:19:55Z"}
}
}Polling barato com If-None-Match (o snapshotVersion vira o ETag):
curl -i -H "Authorization: Bearer $TOKEN" \
-H 'If-None-Match: "9f2c3a1b0d4e5f67"' "$EXPORT/MEC/manifest"
# HTTP/1.1 304 Not Modified (cache de 60s; nenhuma consulta ao índice no acerto)2.2 Documentos (?format=json|md&since=...)
Documentos otimizados aprovados. Com format=md a resposta é text/markdown (renderização por seção); com format=json (padrão) vem um array de:
{"documentCode":"url-3f2a...","titulo":"...","tituloDocumento":"...",
"dominio":"MEC","versao":2,"geradoEm":"...",
"secoes":[{"titulo":"Resumo","conteudo":"...","ordem":1,"chunkIdsOrigem":["..."]}]}2.3 FAQ e glossário (?since=...)
Itens achatados — um objeto por par pergunta/resposta ou por termo:
{"documentCode":"...","versao":1,"geradoEm":"...","pergunta":"...","resposta":"...","ordem":1,"chunkIdsOrigem":["..."]}
{"termo":"FIES","definicao":"...","siglaDe":"Fundo de Financiamento Estudantil","aliases":[],"origem":"url-...","versao":1,"geradoEm":"...","chunkIdsOrigem":["..."]}2.4 Trechos (chunks) — NDJSON em streaming
curl -H "Authorization: Bearer $TOKEN" \
"$EXPORT/MEC/chunks?since=2026-07-01T00:00:00Z&includeEmbeddings=false" \
-o chunks.ndjson
# Content-Type: application/x-ndjson — uma linha JSON por trecho- Somente trechos vigentes do índice do contexto, paginados internamente por cursor sobre
(createdAt, _id)(página interna padrão de 500) — a coleção nunca é montada inteira em memória: 100 mil trechos fluem com consumo constante. includeEmbeddings=trueincluiembedding+embeddingModel(payload ~4–8 KB a mais por trecho; por padrão o campo nem é buscado no índice).- Linha:
{"id","documentCode","dominio","texto","secao","ordem","lastModified"[,"embedding","embeddingModel"]}.
2.5 Bundle — pacote zip
curl -H "Authorization: Bearer $TOKEN" -OJ "$EXPORT/MEC/bundle"
# conhecimento-MEC.zip:
# manifest.json
# documentos/<documentCode>-v<versao>.md
# faq.json
# glossario.jsonO zip é montado em streaming, com as quatro coleções buscadas em paralelo (concorrência 4).
2.6 Sincronização incremental (since)
Todas as coleções aceitam since em ISO-8601 (instante ou com offset — valor inválido retorna 400 com exemplo). Fluxo recomendado do consumidor:
- Poll do manifesto com
If-None-Match(ex.: a cada 5 min) —304= nada a fazer; snapshotVersionmudou → baixar apenas os deltas, informandosince=<último lastModified conhecido>em cada coleção;- Persistir o novo
snapshotVersion+lastModifiedpor coleção.
O filtro since compara com geradoEm do ativo (coleções) e createdAt (trechos) — quando um documento é regerado, a nova versão aparece no delta depois de aprovada na curadoria.
Exemplo prático — conectando um chatbot externo
- O administrador cria a conta
bot-meccom escopo["MEC"](seção 1.2) e entregaclientId/clientSecretao time do chatbot — que os guarda em cofre. - O chatbot troca as credenciais por um token (seção 1.3) e baixa a carga inicial pelo bundle.
- A cada 5 minutos, faz poll do manifesto com
If-None-Match. Custa um304na maioria das vezes. - Quando o
snapshotVersionmuda, baixa só os deltas comsincee atualiza a base local. - Se a credencial vazar, o administrador revoga com
reason(seção 1.4) — em até 60 segundos o consumo para, e a trilha de auditoria mostra tudo o que foi exportado.
3. Limite de requisições, erros e auditoria
- Limite de requisições: contagem por identidade (service account ou usuário) no cache da plataforma, sob a chave
datta:knowledge:export:rl:{serviceAccountId|usuário}, com janela de 1 minuto e padrão de 60 req/min (datta.knowledge.export.rate-limit-per-minute, variável de ambienteKNOWLEDGE_EXPORT_RATE_LIMIT). Excedido → 429 comRetry-Aftere{"error": ..., "reason": "rate_limit"}. - Erros estruturados (sempre
{"error": <mensagem pt-BR>, "reason": <código estável>}):403 fora_do_escopo(domínio fora do escopo da conta — auditado),401 credencial_revogada,400parasince,formatou domínio inválidos. - Auditoria (registrada em
datta.audit.knowledge, indo paraotel-logs-*):KNOWLEDGE.EXPORTEDpor coleção servida (autor, domínio, coleção, itens, formato) eKNOWLEDGE.EXPORT_DENIEDcom o motivo. - Métricas:
knowledge_export_requests_total{colecao,outcome=success|error|not_modified}eknowledge_export_items_total{colecao}.
4. Solução de problemas
| Sintoma | Causa | Ação |
|---|---|---|
401 "Credenciais inválidas" ao pedir o token | clientId/secret errados ou conta revogada (resposta proposital e idêntica) | Conferir a conta na listagem de service accounts; se revogada, criar nova (reveal-once) |
403 fora_do_escopo | Domínio pedido não está nos dominios da conta (comparação normalizada) | Criar conta com o domínio correto — o escopo não é editável depois |
401 credencial_revogada até 60s após revogar | Cache da situação da conta (60s) | Esperado — teto documentado de propagação |
| Coleções vazias com ativos existentes | Ativos ainda em rascunho (não passaram pela curadoria) | Aprovar na tela de curadoria; a exportação só serve conteúdo aprovado |
| Exportação continua funcionando com a autenticação fora do ar | A verificação de revogação degrada aberta (decisão de projeto documentada) | Restaurar o serviço de autenticação; a janela se limita à indisponibilidade + os 60s de cache |
429 constante de um consumidor | Polling sem If-None-Match/since | Orientar o consumidor ao fluxo incremental; se o volume for legítimo, subir KNOWLEDGE_EXPORT_RATE_LIMIT |