Catalisa.Building Blocks
Catálogo/Inteligência/Data Extraction

Data Extraction

Beta

Documento entra como imagem, sai como JSON estruturado com nota de confiança

22
Endpoints
5
Entidades
1
Provedores
Tenant
Escopo
3017
Porta
2026-01
Desde

O comprovante que o cliente fotografou com o celular vira campo preenchido na sua esteira, com uma nota de confiança por campo — para que a sua equipe só revise o que a máquina não teve certeza.

Para quem é
  • Fintechs e financeiras que digitam à mão comprovante de renda, residência e documento de identidade no onboarding
  • Operações de cobrança e conciliação que recebem boleto e nota fiscal em imagem
  • Times de backoffice que mantêm uma fila de digitação manual como gargalo do processo
Substitui
  • Digitação manual de documento por equipe de backoffice
  • Serviço de OCR com regras por tipo de documento, mantidas à mão
  • Integração direta e específica com a API de um provedor de IA dentro do seu código
O que não é
  • Um verificador de autenticidade de documento ou serviço antifraude
  • Um sistema de biometria facial ou prova de vida
  • Um OCR de propósito geral para digitalizar acervo
O que dá para fazer

1 endpoints em 1 recurso.

Explorar a API →
01

Resumo executivo

O Data Extraction lê documentos. Você envia a foto de um comprovante, de uma CNH ou de um boleto; ele devolve os campos que você pediu, em JSON, com uma nota de confiança para cada um.

Na prática, ele resolve a fila de digitação. Sem ele, o comprovante de renda que o cliente fotografou vai para uma pessoa que lê e digita — e essa pessoa é o gargalo do onboarding, a fonte dos erros de digitação e a razão pela qual a análise leva um dia em vez de dois minutos. Com ele, a esteira recebe os campos já preenchidos e a pessoa só olha o que a máquina marcou como incerto.

Está em beta desde janeiro de 2026. Roda no monolito e em standalone na porta 3017, e está declarado nas pilhas de staging e de produção. Duas ressalvas importantes antes de qualquer promessa a cliente: um único provedor de IA está implementado — o Google Gemini —, e o processador assíncrono que executa a extração não está ligado nos ambientes publicados, o que faz toda extração criada ficar parada em PENDING. Leia a §15 primeiro.

AtributoValor
Identificadordata-extraction
CategoriaInteligência
EscopoTenant (exige organizationId no token, em todas as rotas)
Porta (standalone)3017
Path alias@data-extraction
Prefixo HTTP/data-extraction
StatusBeta desde 2026-01
Depende dePostgreSQL (schema extraction), Redis Streams, S3, provedor de IA
PermissõesDATA_EXTRACTION_ADMIN, DATA_EXTRACTION_READ, DATA_EXTRACTION_WRITE

02

O problema

negócio

O cenário. Uma fintech abre conta ou concede crédito. O cliente manda uma foto do RG, uma da CNH, um comprovante de residência e um holerite. Alguém precisa transformar essas quatro imagens em campos preenchidos para a análise seguir.

O que trava hoje.

  • A digitação é o gargalo. Cada documento leva minutos de uma pessoa. Em pico de campanha, a fila cresce e o tempo de resposta ao cliente cresce junto — exatamente quando ele está mais propenso a desistir.
  • A digitação erra. Um dígito trocado no CPF ou uma casa decimal a mais na renda passa despercebido e contamina a decisão de crédito. O erro não é raro; ele é estatístico.
  • O OCR tradicional não entende contexto. Reconhecer caracteres é diferente de saber qual daqueles números é a renda líquida. Fechar essa distância exige regras por tipo de documento e por leiaute — e cada banco emite o holerite do jeito dele.
  • Os modelos prontos não conhecem documento brasileiro. Os serviços internacionais têm modelo para driver's license e passport; RG, CNH no leiaute do Contran, comprovante de residência de concessionária e holerite brasileiro caem no caminho genérico ou exigem treinar modelo com base rotulada.
  • Enviar documento de identidade a terceiro é uma decisão de compliance, não de engenharia. Foto de RG é dado pessoal, e frequentemente carrega dado sensível. Mandar isso para uma API de IA sem base legal definida e sem contrato de operador é um risco que costuma ser assumido por omissão.

O custo de não resolver. O custo direto é a folha do backoffice, que cresce linearmente com o volume — o oposto do que se espera de software. O custo indireto é a conversão perdida no intervalo entre o cliente enviar o documento e receber a resposta. E o custo que só aparece uma vez é o incidente: um vazamento de base com foto de documento de identidade é o tipo de evento que a ANPD trata com severidade e que o mercado não esquece.


03

Proposta de valor

negócio
AntesDepois
Uma pessoa lê o documento e digitaO documento vira JSON, e a pessoa revisa só o que ficou abaixo do limiar de confiança
Regra de extração por leiaute de documentoCampos declarados em linguagem natural, sem base rotulada e sem treino
Tudo é conferido, porque não se sabe o que confiarNota de confiança por campo, para separar o que precisa de olho humano
O dado sai no vocabulário do documentoMapeamento configurável para o vocabulário do seu sistema
Chave de API do provedor de IA no ambiente da plataformaCredencial por organização, cifrada, com o consumo faturado direto ao cliente

Template em vez de treino. Você declara o que quer: nome do campo, tipo, descrição e obrigatoriedade. O building block monta o prompt e pede ao modelo de visão. Não há base rotulada, não há ciclo de treino e não há espera para começar a extrair um tipo de documento novo.

Confiança por campo, não por documento. A resposta traz uma nota de 0 a 1 para cada campo extraído, além da média geral. É isso que permite a regra que muda a economia do processo: aceitar automaticamente acima de um limiar e mandar para revisão só o resto.

Mapeamento é configuração. O modelo devolve valor_total; o seu sistema espera amount. Um FieldMapping faz a ponte, com transformações prontas para o que dói no Brasil: 1.234,56 vira 1234.56, 15/08/2026 vira 2026-08-15, R$ 1.500,00 vira 1500.

A conta de IA é do cliente. A credencial do provedor é configurada por organização e cifrada. O consumo é faturado pelo provedor diretamente a quem contratou — a Catalisa não intermedia, não embute margem e não compartilha cota entre clientes.

Documento brasileiro é caso de primeira classe. Há prompt dedicado para boleto bancário, com dezoito campos incluindo linha digitável, código de barras, CPF ou CNPJ de beneficiário e pagador, e contexto declarado para nota fiscal, cupom fiscal, documento de identidade, extrato bancário e contrato.


04

Casos de uso reais

negócio

Caso 1 — Uma fintech tira a digitação do caminho crítico do onboarding Cenário ilustrativo

Contexto

Fintech de crédito com cerca de 3.000 propostas por mês. Cada proposta traz documento de identidade, comprovante de residência e comprovante de renda.

A dor

Três documentos por proposta, nove mil leituras manuais por mês. A fila de digitação era o passo mais lento da esteira e o único que não escalava sem contratar. Em campanha, o tempo de resposta passava de horas para dois dias.

A solução com o BB

Um template por tipo de documento, com os campos que a análise precisa. O cliente envia o arquivo pelo file-storage, a esteira chama POST /extractions com o fileId e o templateId, e recebe 202 com um identificador. Quando a extração conclui, o evento data-extraction.extraction.completed avisa e os campos já mapeados estão em mappedData.

O resultado

A revisão humana passa a ser exceção, acionada pelo limiar de confiança, em vez de ser o caminho padrão. O gargalo sai do caminho crítico.

sequenceDiagram
  autonumber
  participant CLI as "Cliente final"
  participant FS as "File Storage"
  participant EST as "Esteira da fintech"
  participant DE as "Data Extraction"

  CLI->>FS: "envia identidade, residência e renda"
  FS-->>EST: fileId
  EST->>DE: "POST /extractions { fileId, templateId }"
  DE-->>EST: "202 — identificador da extração"
  Note over DE: "processamento assíncrono"
  DE-->>EST: "evento data-extraction.extraction.completed"
  EST->>DE: "GET /extractions/:id"
  DE-->>EST: "mappedData + confidenceScores"
  EST->>EST: "aplica o limiar de confiança"

Caso 2 — Uma operação de cobrança lê boleto de qualquer banco Cenário ilustrativo

Contexto

Empresa que recebe boletos de fornecedores em PDF e imagem, de dezenas de bancos diferentes, e precisa agendar pagamento.

A dor

Cada banco tem um leiaute. A tentativa anterior, com OCR mais expressão regular, funcionava para três bancos e quebrava no quarto. Manter as regras consumia mais tempo do que digitar.

A solução com o BB

O prompt de boleto já é especializado: extrai banco, código do banco, beneficiário e o documento dele, pagador e o documento dele, vencimento, valor, linha digitável, código de barras, desconto, juros, valor cobrado, instruções e o código de agência. Basta enviar o arquivo com documentType: "boleto".

O resultado

O leiaute deixa de importar, porque o modelo lê o documento em vez de casar posições. Documento de banco novo não exige regra nova.

flowchart LR
  B1["Boleto do banco A"] --> P["documentType: 'boleto'<br/>prompt dedicado, 18 campos"]
  B2["Boleto do banco B"] --> P
  B3["Boleto de um banco novo"] --> P
  P --> OUT["banco · código do banco · beneficiário e documento<br/>pagador e documento · vencimento · valor<br/>linha digitável · código de barras · desconto<br/>juros · valor cobrado · instruções · agência"]
  OUT --> PAG["Agendamento de pagamento"]

Caso 3 — Um backoffice revisa por exceção Cenário ilustrativo

Contexto

Operação que processa comprovantes de renda de qualidade muito variável — foto tremida, contraluz, papel amassado.

A dor

Como não havia sinal de qualidade da leitura, tudo era conferido. A automação anterior tinha aumentado o trabalho: agora a pessoa lia o documento e conferia o que a máquina tinha escrito.

A solução com o BB

Cada campo volta com nota de confiança. A esteira aplica a regra: acima de 0,90 aceita, entre 0,70 e 0,90 marca para conferência rápida, abaixo disso manda para revisão completa. Extração que falha é reprocessável por POST /extractions/:id/retry, e mudança no mapeamento é reaplicável sem reler o documento, por POST /extractions/:id/remap.

O resultado

O esforço de revisão se concentra onde a máquina admitiu incerteza. A conta que interessa não é "quantos documentos a IA leu", é "quantos um humano precisou abrir".

flowchart LR
  EXT["Extração COMPLETED<br/>com confidenceScores por campo"] --> R{"nota do campo"}
  R -->|"acima de 0,90"| A["Aceita automaticamente"]
  R -->|"entre 0,70 e 0,90"| B["Conferência rápida"]
  R -->|"abaixo de 0,70"| C["Revisão completa"]

  FALHA["Extração FAILED"] -->|"POST /:id/retry<br/>refaz a leitura e custa token"| EXT
  MAPERR["Mapeamento errado"] -->|"POST /:id/remap<br/>reaplica sem chamar a IA — de graça"| EXT

Caso 4 — Os modelos prontos do mercado não foram feitos para documento brasileiro Referência de mercado

Contexto

AWS Textract, Google Document AI e Azure AI Document Intelligence são as três ofertas de nuvem estabelecidas para essa categoria, todas cobrando por página e todas com modelos pré-construídos para fatura, recibo e documento de identidade.

A dor do mercado

A cobertura de documento brasileiro é pior do que o discurso comercial sugere, e isso está escrito na documentação dos próprios fornecedores. A AWS declara que o Textract extrai informação de "passaportes, carteiras de motorista e outros documentos de identificação emitidos pelo governo dos Estados Unidos". O catálogo de preços do Google Document AI nomeia os processadores de identidade literalmente como "US driver license parser" e "US passport parser". A Nanonets escreve que o modelo de OCR pré-treinado dela é "optimized for US Drivers Licenses" e recomenda o modelo genérico para carteiras de qualquer outro país. O Azure é a exceção parcial: a versão 4.0 do modelo de identidade declara cobertura mundial, mas a tabela oficial nomeia Estados Unidos, Índia e Austrália, e joga o Brasil na categoria genérica "Other". E nenhum dos oito fornecedores internacionais que consultamos oferece modelo para boleto, nota fiscal eletrônica ou CPF (consulta em 2026-08-16).

O mesmo levantamento, em forma de quadro — cada linha é o que o próprio fornecedor publica, não a nossa leitura dele:

FornecedorO que a documentação dele declaraCobre documento brasileiro?
AWS TextractExtrai de "passaportes, carteiras de motorista e outros documentos de identificação emitidos pelo governo dos Estados Unidos"Não
Google Document AIProcessadores nomeados como "US driver license parser" e "US passport parser"Não
NanonetsModelo pré-treinado "optimized for US Drivers Licenses"; recomenda o genérico para os demais paísesNão
Azure AI Document IntelligenceVersão 4.0 declara cobertura mundial, mas a tabela oficial nomeia EUA, Índia e Austrália e joga o Brasil em "Other"Parcial, sem campos brasileiros garantidos
Os oito fornecedores internacionais consultados—Nenhum oferece modelo para boleto, nota fiscal eletrônica ou CPF

Consulta em 2026-08-16, nas páginas e documentações públicas dos próprios fornecedores.

Como a Catalisa endereça

Em vez de modelo especializado por tipo de documento, o building block usa modelo de visão de propósito geral com prompt declarado pelo cliente. Um tipo de documento novo é um template novo, criado por API, sem base rotulada e sem espera. Já há prompt dedicado a boleto brasileiro, com dezoito campos.

O resultado

Você troca precisão de pico por tempo até o primeiro resultado e por cobertura de cauda longa — que é exatamente o formato do problema de quem recebe documento brasileiro de origem variada. O preço dessa escolha é honesto: para um formulário fixo de altíssimo volume, um modelo especializado bem treinado continua sendo melhor.


05

Mercado e diferenciais

negócio

Panorama. O mercado de processamento inteligente de documento se organiza em três faixas. A primeira é a das nuvens — Textract, Document AI, Document Intelligence —, que cobram por página e entregam modelos pré-construídos mais a opção de treinar o seu. A segunda é a dos especialistas — Mindee, Klippa, Nanonets, e mais recentemente LlamaParse e Reducto —, que cobram por documento e competem em qualidade de extração e experiência de desenvolvedor. A terceira, no Brasil, é a das plataformas de identidade — Unico, Idwall, Caf, BigDataCorp —, que somam extração a antifraude, biometria e consulta a bases oficiais, e vendem o pacote.

Uma quarta faixa surgiu recentemente e é a que este building block ocupa: usar um modelo de visão de propósito geral com prompt declarado, em vez de modelo especializado. É mais flexível e chega mais rápido a um tipo de documento novo; é menos previsível em precisão do que um modelo bem treinado para um formulário fixo.

CritérioCatalisa Data ExtractionAWS TextractGoogle Document AIAzure AI Doc. IntelligenceMindeeBigDataCorpSerpro Datavalid
AbordagemModelo de visão com prompt declaradoModelos especializadosProcessadores especializadosModelos pré-construídos e personalizadosModelos por tipo de documentoDocumentoscopia e basesFonte primária estatal
Custo por mil documentos (2026-08-16)≈ US$ 1 em token do provedorUS$ 25 no Analyze ID; US$ 50 em formuláriosUS$ 30 no Form ParserUS$ 10 no pré-construído≈ US$ 44≈ R$ 170R$ 700 a R$ 1.140 na validação composta
Novo tipo de documentoUm template, por API, sem treinoExtrator genérico ou modelo próprioProcessador personalizado com treinoModelo personalizado com rotulagemModelo personalizadoCatálogo fechadoCatálogo fechado
RG e CNH brasileirosVia prompt, sem modelo dedicadoNão — declara documentos dos EUANão — processadores nomeados como dos EUAParcial — Brasil cai na categoria "Other"NãoSim, é a especialidadeSim, é a fonte
Boleto, nota fiscal e CPFPrompt dedicado a boletoNãoNãoNãoNãoCPF sim; boleto nãoCPF sim
Confiança por campoSimSimSimSimSimSimNão se aplica
Mapeamento e transformação de campoSim, com formato brasileiroVocê implementaVocê implementaVocê implementaParcialDependeNão
Credencial de IA do próprio clienteSim, cifrada por organizaçãoNão se aplicaNão se aplicaNão se aplicaNão se aplicaNão se aplicaNão se aplica
Antifraude e autenticidadeNãoNãoNãoNãoNãoSimSim — é a base oficial
Região de processamento no BrasilSim, via Vertex AI (§14)Não existe região da AWS na América do SulSim, São PauloSim, brazilsouthNãoSimSim, estatal
Multi-tenant nativoSim, pelo token do IAMVocê implementaVocê implementaVocê implementaVocê implementaDependeDepende

Preços consultados nas páginas oficiais em 2026-08-16: AWS Textract, Google Document AI, Azure AI Document Intelligence pela API de preços de varejo da Microsoft, Mindee, BigDataCorp e Loja Serpro. O custo da coluna da Catalisa é a estimativa do token do provedor de IA, calculada na §6 — não é preço do building block, que está em definição. Unico, Idwall, Certta e Serasa não publicam preço em página aberta.

Nossos diferenciais

  1. Tipo de documento novo não tem ciclo de treino. Criar um template é uma chamada de API com os campos descritos em português. Isso é difícil de copiar para quem construiu o produto em torno de modelos treinados — não pela técnica, mas porque o modelo de negócio deles depende do ciclo de rotulagem.
  2. A confiança é por campo, e é acionável. A nota individual é o que permite a regra de revisão por exceção. Um sinal só no documento inteiro não permite decidir qual campo olhar, e é a diferença entre reduzir o trabalho do backoffice e apenas mudá-lo de lugar.
  3. O mapeamento fala português. As transformações prontas resolvem o que realmente aparece em documento brasileiro: separador de milhar com ponto e decimal com vírgula, data em DD/MM/AAAA, valor com R$. Um extrator internacional devolve o texto cru e deixa a normalização com você.
  4. A credencial de IA é do cliente. Cada organização configura a própria conta no provedor. O consumo é faturado direto a ela, o modelo pode ser escolhido por ela, e a relação de tratamento de dado pessoal com o provedor de IA é dela — o que, sob a LGPD, é mais simples de sustentar do que a alternativa. Ver §14.

Uma tendência que muda o problema, e vale saber antes de comprar. O caminho principal do onboarding brasileiro está migrando de "fotografar o documento e fazer OCR" para "decodificar o QR Code e consultar a base oficial". Três normas empurram nessa direção: a Lei nº 14.534/2023 tornou o CPF o número único e suficiente de identificação; o Decreto nº 10.977/2022 instituiu a Carteira de Identidade Nacional com QR Code e MRZ, permitindo consulta direta ao registro civil; e a Carteira Digital de Trânsito traz QR Code na CNH — o próprio Serpro afirma que usá-lo "pode dispensar o uso de OCR".

NormaO que mudouEfeito sobre o OCR de identidade
Lei nº 14.534/2023O CPF passa a ser o número único e suficiente de identificaçãoReduz a necessidade de ler outros números do documento
Decreto nº 10.977/2022Institui a Carteira de Identidade Nacional com QR Code e MRZPermite consultar o registro civil direto, em vez de fotografar
Carteira Digital de TrânsitoQR Code na CNHO próprio Serpro afirma que usá-lo "pode dispensar o uso de OCR"

O OCR não desaparece: o mesmo Decreto nº 10.977/2022 mantém os RGs no modelo antigo válidos até 1º de março de 2032, com validade indeterminada para quem tinha 60 anos ou mais em 2022, e documentos estrangeiros continuam existindo. É uma janela que fecha devagar, e vale dimensioná-la ao decidir quanto investir em extração de identidade.

Quando escolher o concorrente. Se o seu problema é fraude documental — saber se aquele RG é verdadeiro, se a foto é da pessoa viva, se o CPF confere com a Receita —, procure Unico, Idwall, Certta (ex-CAF), Serasa ou BigDataCorp: eles fazem isso e este building block não faz nada disso. Ele lê o que está escrito e acredita. Se você precisa de dado de fonte primária, o Serpro Datavalid é o único que consulta a base oficial, com preço público em reais a partir de R$ 0,80 por validação — vale saber que boa parte dos fornecedores privados revende Serpro por baixo, então a tabela dele é o piso da sua negociação. Se o seu volume é alto e concentrado em um formulário de leiaute fixo, um modelo personalizado do Azure ou do Google Document AI supera um modelo de visão genérico em precisão. Se você precisa de texto e coordenada de tudo que está no documento, o Textract entrega isso melhor e mais barato. E se o requisito é residência de dados no Brasil, note que o Azure oferece região brazilsouth pelo mesmo preço e a AWS não tem nenhuma região na América do Sul para o Textract — a §14 explica a configuração que resolve isso do nosso lado.

O Data Extraction ganha quando o problema é muitos tipos de documento brasileiro, de origem variada, sem tempo para treinar modelo — não quando o problema é ler um formulário padronizado em altíssimo volume, e nunca quando o problema é fraude.


06

Modelo de cobrança e ROI

negócio

Precificação em definição. O Data Extraction não tem preço fechado. Ele é a peça de leitura da cadeia de documentos e a intenção é que acompanhe a contratação do conjunto. Não há valor a divulgar e este documento não estima nenhum.

O custo de IA não é nosso. A credencial do provedor é do cliente, e o consumo de token é faturado pelo provedor diretamente a ele. A Catalisa não revende inferência, não embute margem e não compartilha cota entre clientes. Isso também significa que o cliente escolhe o modelo e vê o custo de origem — o que, numa categoria em que o preço por token cai várias vezes por ano, é vantagem dele.

O que dispara custo.

DriverPor quê
Extrações concluídasCada uma é uma chamada ao modelo de visão com a imagem inteira no corpo
Tamanho e número de páginas do documentoImagem é cobrada em tokens pelo provedor, e documento grande consome mais
Tamanho do templateCada campo declarado entra no prompt; template com quarenta campos custa mais que um com cinco
Reprocessamentosretry refaz a chamada ao modelo e custa de novo. remap não — ele reaproveita o dado já extraído
ArmazenamentoCada extração guarda uma cópia do arquivo em S3, mais o dado bruto e o mapeado no banco

Quanto custa uma página, de verdade. O modelo padrão, gemini-2.5-flash, é cobrado a US$ 0,30 por milhão de tokens de entrada e US$ 2,50 por milhão de saída (tabela oficial, consulta em 2026-08-16). Imagem custa o mesmo que texto — não há tarifa separada de visão.

A conta de uma página, explicitada para você poder refazê-la:

ComponenteTokensCusto
Página de PDF258 (valor fixo declarado pelo Google)—
Prompt de instrução com o template≈ 500—
Entrada total≈ 758758 × US$ 0,30 ÷ 1M = US$ 0,000227
Saída em JSON≈ 300300 × US$ 2,50 ÷ 1M = US$ 0,00075
Total por página≈ US$ 0,00098 → cerca de US$ 0,98 por mil páginas

Comparando com a §5, isso é aproximadamente 10 vezes mais barato que o modelo pré-construído do Azure (US$ 10 por mil), 30 vezes mais barato que o Form Parser do Google e 50 vezes mais barato que o Textract em formulários.

Uma otimização que quase ninguém conhece. Uma página de PDF custa 258 tokens fixos. Uma foto de celular do mesmo documento é dividida em blocos de 258 tokens cada — uma imagem de 1600×1200 consome cerca de 1.032, quatro vezes mais. Converter a captura para PDF antes de enviar reduz o custo de entrada em cerca de 75%. Se o seu volume é alto, esta linha paga a leitura deste documento.

ROI. A conta de guardanapo, num cenário nomeado: operação que processa 9.000 documentos por mês e hoje gasta cerca de 4 minutos de uma pessoa por documento.

São 600 horas por mês de digitação — o equivalente a três a quatro pessoas em tempo integral, só para transformar imagem em campo. Com revisão por exceção, e supondo que 20% dos documentos caiam abaixo do limiar de confiança, o esforço humano cai para cerca de 120 horas: uma pessoa, com folga. O custo de IA nesse volume, pela conta acima, fica na casa de US$ 9 por mês — irrelevante frente a qualquer folha.

Hoje, sem o BBCom revisão por exceção
Documentos por mês9.0009.000
Documentos que um humano abre9.000 (100%)≈ 1.800 (20%, valor ilustrativo)
Tempo humano por mês≈ 600 horas≈ 120 horas
Equivalente em pessoas3 a 4 em tempo integral1, com folga
Custo de IA—≈ US$ 9/mês

Duas ressalvas para não vender a conta errada. Primeiro, a proporção de 20% é ilustrativa — ela depende do tipo de documento, da qualidade das fotos que os seus clientes mandam e do limiar que você escolher, e só um piloto com os seus documentos responde qual é a sua. Segundo, o ganho maior costuma não ser a folha: é o tempo de resposta ao cliente, que sai de horas para minutos e aparece direto na conversão do funil.

E um custo que não é de token. Se a sua avaliação de LGPD exigir processamento dentro do Brasil, a configuração recomendada na §14 usa o Vertex AI em São Paulo, que tem preço próprio e exige uma conta Google Cloud com projeto e conta de serviço. É trabalho de configuração, não de código — mas é trabalho, e deve entrar no cronograma do projeto.


07

Arquitetura

O building block tem duas metades: uma API síncrona, que aceita o trabalho e responde 202, e um processador assíncrono que consome uma fila do Redis e executa a extração em quatro passos.

As camadas

flowchart TD
  HTTP["HTTP — Bearer JWT emitido pelo IAM"] --> APP["Hono app<br/>basePath '/data-extraction' · applyCommonMiddleware"]

  subgraph rotas["routes/ — 22 rotas"]
    R1["/api/v1/data-extraction/provider-configs<br/>7 rotas"]
    R2["/api/v1/data-extraction/templates<br/>5 rotas"]
    R3["/api/v1/data-extraction/templates/:id/mappings<br/>5 rotas"]
    R4["/api/v1/data-extraction/extractions<br/>5 rotas"]
    R5["/health — sonda pública"]
  end

  APP --> rotas
  rotas -->|"Zod parse → ResultAsync → handleResult"| svc

  subgraph svc["services/"]
    S1["ProviderConfigService<br/>CRUD + testa credencial + instancia"]
    S2["TemplateService<br/>tipos de documento e campos esperados"]
    S3["FieldMapperService<br/>mapeamento e transformações"]
    S4["ExtractionService<br/>aceita o trabalho e enfileira"]
    S5["ExtractionQueueService<br/>XADD no stream do Redis"]
  end

  svc -->|"Redis Stream 'data-extraction:pipeline'<br/>grupo 'extraction-pipeline-processors'"| CONS

  subgraph CONS["ExtractionPipelineConsumer — só roda com EXTRACTION_CONSUMER_ENABLED=true"]
    P1["FILE_FETCH<br/>busca e guarda no S3"] --> P2["OCR_EXTRACT<br/>passa direto — não faz nada"]
    P2 --> P3["AI_STRUCTURE<br/>chama o modelo de visão"]
    P3 --> P4["FIELD_MAP<br/>aplica os mapeamentos"]
  end

  CONS --> S3B["S3 / MinIO<br/>extraction/{org}/{extração}/source"]
  CONS --> PROV["providers/ — createExtractionProvider()"]

  PROV --> G["GOOGLE_GEMINI — implementado"]
  PROV --> O["OPENAI — recusa 'not yet implemented'"]
  PROV --> A["ANTHROPIC — recusa 'not yet implemented'"]

  G -->|"HTTPS — imagem em base64"| API["API do Google Gemini<br/>gemini-2.5-flash, resposta em JSON"]

O caminho de uma extração, do upload ao registro

A API aceita o trabalho e devolve 202 numa conversa curta; o restante acontece depois, no processador. O diagrama abaixo separa as duas metades pela linha do 202:

sequenceDiagram
  autonumber
  participant C as "Sua aplicação"
  participant API as "API do Data Extraction"
  participant DB as "PostgreSQL — schema extraction"
  participant Q as "Redis Stream"
  participant W as "ExtractionPipelineConsumer"
  participant S3 as "S3 / MinIO"
  participant AI as "Provedor de IA — Gemini"

  C->>API: "POST /extractions { fileId | content | url }"
  Note over API: "exatamente UMA das três origens"
  API->>DB: "resolve a configuração de provedor<br/>(a informada, ou a padrão da organização)"
  API->>DB: "grava ExtractionRecord<br/>status PENDING, currentStep FILE_FETCH"
  API->>Q: "XADD na fila"
  API-->>C: "202 com o identificador"

  Note over Q,W: "a partir daqui, é o processador assíncrono"

  W->>Q: "lê a mensagem do grupo de consumidores"
  W->>S3: "FILE_FETCH — busca a origem e grava a cópia"
  Note over W,S3: "status FILE_FETCHING"
  W->>W: "OCR_EXTRACT — passa direto, não há OCR"
  Note over W: "status OCR_PROCESSING"
  W->>S3: "lê o arquivo"
  W->>AI: "AI_STRUCTURE — prompt do template + imagem em base64"
  AI-->>W: "JSON com extracted_data e confidence"
  Note over W,AI: "status AI_PROCESSING"
  W->>DB: "grava rawData, confiança, modelo e tokens"
  W->>DB: "FIELD_MAP — aplica os mapeamentos por prioridade"
  Note over W,DB: "status MAPPING"
  W->>DB: "grava mappedData e marca COMPLETED"
  W-->>C: "evento data-extraction.extraction.completed"

O que cada passo faz com cada origem

PassoEstadoO que acontece
FILE_FETCHFILE_FETCHINGfileId → busca no file-storage. content → valida base64 e tamanho. url → confere se a URL é segura e baixa com cliente protegido. Em todos os casos: grava em S3 e guarda a chave
OCR_EXTRACTOCR_PROCESSINGPassa direto — não há OCR nesta versão (§15)
AI_STRUCTUREAI_PROCESSINGLê o arquivo do S3, monta o prompt a partir do template, chama o modelo de visão e grava rawData, confiança, modelo e tokens
FIELD_MAPMAPPINGAplica os FieldMapping do template por ordem de prioridade, grava mappedData, marca COMPLETED e publica o evento

Atenção. Qualquer falha em qualquer passo leva o registro a FAILED, com a mensagem gravada e o evento data-extraction.extraction.failed publicado. E se EXTRACTION_CONSUMER_ENABLED estiver desligado, a conversa termina no 202: a metade de baixo do diagrama simplesmente não acontece, e a extração fica em PENDING para sempre (§15).

Decisões não óbvias

  • Toda extração é assíncrona, sempre. Não existe modo síncrono. A razão é o tempo: uma chamada a modelo de visão com imagem leva de segundos a dezenas de segundos, e prender uma conexão HTTP nisso transforma pico de volume em esgotamento de conexões. O custo dessa escolha é que o cliente precisa consultar ou ouvir evento — e é por isso que a §15 destaca com tanta ênfase o processador desligado: sem ele, o 202 é uma promessa que ninguém cumpre.
  • A fila é um Redis Stream com grupo de consumidores, e não uma lista. Stream dá confirmação explícita e permite recuperar mensagem pendente de um processo que morreu. Na subida, o consumidor executa uma reivindicação automática das entradas paradas há mais de 60 segundos.
  • A mensagem é confirmada mesmo quando o passo falha. É deliberado: reentrega automática de uma falha determinística — imagem ilegível, credencial errada — só multiplicaria o custo de token sem chance de sucesso. O reprocessamento é explícito, por POST /extractions/:id/retry, e conta as tentativas. O preço é que falha transitória também não é repetida sozinha.
  • Cada passo é uma mensagem separada. Um passo termina enfileirando o próximo. Isso mantém cada unidade de trabalho curta, permite observar onde a extração parou pelo campo currentStep, e permitiria escalar os passos de forma independente. O custo é mais idas ao Redis e ao banco por extração.
  • O arquivo é sempre copiado para o S3, mesmo quando já veio de lá. Assim o passo de IA tem uma origem única e o reprocessamento não depende de a origem continuar disponível — uma URL externa pode ter caducado. O custo é armazenamento duplicado, e é o motivo de a §14 tratar essa área do bucket como repositório de documento pessoal.
  • A URL externa passa por verificação antes do download. A origem url é conferida por uma função de segurança compartilhada e baixada por um cliente HTTP protegido, para que ninguém use este building block como ponte para alcançar endereços internos da sua rede.
  • A temperatura do modelo é 0,1 e a resposta é exigida em JSON. Extração não quer criatividade. O provedor é configurado para responder com tipo JSON, e ainda assim há um caminho de recuperação que procura um bloco de código na resposta quando o modelo insiste em envolver o JSON em texto.
  • A confiança geral é a média aritmética das notas por campo. É simples e é suficiente para triagem — mas note a consequência: um documento com dezenove campos ótimos e um campo crítico ilegível tem média alta. Use as notas por campo para decidir, não a média.

Monolito vs. standalone. O app.ts é montado no monolito em src/app.ts e responde em http://localhost:3000/data-extraction. O main.ts sobe o mesmo app na porta 3017 quando DEPLOYMENT_MODE=standalone, e também tenta iniciar o processador — que só sobe de fato se EXTRACTION_CONSUMER_ENABLED for verdadeiro. Essa é a diferença de comportamento mais importante entre os modos, e a fonte da limitação principal da §15.


08

Conceitos e modelo de dados

Glossário

TermoSignifica
ExtraçãoUm trabalho: um documento, um template opcional e um resultado. Corresponde ao modelo ExtractionRecord.
Template de extraçãoO que se espera de um tipo de documento: nome, documentType e a lista de campos esperados. Pode trazer um prompt próprio que substitui o gerado.
Campo esperadoDeclaração de um campo: name, type (string, number, date, boolean, array, object), description, required e format. A descrição vai para o prompt e é o que orienta o modelo.
documentTypeTexto livre que dá contexto ao prompt. Seis valores têm tratamento especial: receipt, invoice, id_document, bank_statement, contract e boleto.
Mapeamento de campoRegra que leva um campo do resultado bruto para o vocabulário do seu sistema, com transformação opcional. Origem e destino aceitam caminho com ponto para estrutura aninhada.
Dado bruto (rawData)O que o modelo devolveu, sem tradução.
Dado mapeado (mappedData)O resultado depois de aplicados os mapeamentos. Sem mapeamentos, é igual ao bruto.
Nota de confiançaValor de 0 a 1 por campo, informado pelo próprio modelo. overallConfidence é a média aritmética delas.
Passo (step)Uma das quatro etapas: FILE_FETCH, OCR_EXTRACT, AI_STRUCTURE, FIELD_MAP. O campo currentStep diz onde a extração está ou onde parou.
Origem (sourceType)De onde o documento veio: FILE_ID, BASE64 ou URL. Exatamente uma por extração.
Chave mestra de credenciaisDATA_EXTRACTION_CREDENTIAL_MASTER_KEY. Chave AES-256-GCM de 32 bytes, em 64 caracteres hexadecimais, que cifra as credenciais de IA de todos os tenants.

Modelo de dados — schema extraction no PostgreSQL. 5 modelos.

Modelo PrismaTabelaPropósitoCampos-chave
ExtractionProviderConfigextraction_provider_configsProvedor de IA e credenciais da organizaçãoÚnico (organizationId, name), providerType, credentials (cifrado), isDefault, isActive, settings, deletedAt
ExtractionTemplateextraction_templatesO que extrair de um tipo de documentoÚnico (organizationId, name), documentType, expectedFields, customPrompt, isActive, deletedAt
FieldMappingfield_mappingsTradução de campo, com transformaçãoÚnico (templateId, sourceField, targetField), transformType, transformConfig, isRequired, defaultValue, priority
ExtractionRecordextraction_recordsO trabalho e o resultadostatus, currentStep, sourceType, sourceRef, s3Key, rawData, mappedData, confidenceScores, providerModel, tokensUsed, processingTimeMs, retryCount, businessId, stepHistory
ExtractionWebhookSubscriptionextraction_webhook_subscriptionsAssinatura de notificaçãourl, secret, events, isActive. Sem nenhum endpoint — ver §15

Configurações, templates e extrações são isolados por organizationId. Mapeamentos herdam o isolamento do template e são removidos em cascata com ele.

Enumerações

EnumValores
ExtractionProviderTypeGOOGLE_GEMINI (implementado) · OPENAI · ANTHROPIC (declarados, recusam em execução — §15)
ExtractionStatusPENDING · PROCESSING · FILE_FETCHING · OCR_PROCESSING · AI_PROCESSING · MAPPING · COMPLETED · FAILED
FieldTransformTypeNONE · DATE_FORMAT · CURRENCY_NORMALIZE · NUMBER_PARSE · STRING_TRIM · REGEX_EXTRACT

Máquina de estados da extração

stateDiagram-v2
  [*] --> PENDING: "POST /extractions"

  PENDING --> FILE_FETCHING: "processador consome a fila"
  FILE_FETCHING --> OCR_PROCESSING: "arquivo gravado no S3"
  OCR_PROCESSING --> AI_PROCESSING: "passa direto — não há OCR"
  AI_PROCESSING --> MAPPING: "modelo devolveu o JSON"
  MAPPING --> COMPLETED: "mappedData gravado"

  PENDING --> FAILED: "falha"
  FILE_FETCHING --> FAILED: "falha"
  OCR_PROCESSING --> FAILED: "falha"
  AI_PROCESSING --> FAILED: "falha"
  MAPPING --> FAILED: "falha"

  FAILED --> PENDING: "POST /:id/retry — soma 1 a retryCount"
  COMPLETED --> COMPLETED: "POST /:id/remap — reaplica sem chamar a IA"

  note right of PENDING
    Gravado e enfileirado.
    Se o processador não estiver ligado,
    PARA AQUI para sempre (§15).
  end note

  note right of COMPLETED
    remap só aceita COMPLETED.
    retry só aceita FAILED.
  end note
EstadoQuando o registro está neleObservação
PENDINGGravado e enfileirado, ainda não consumidoCom o processador desligado, é onde toda extração fica para sempre (§15)
FILE_FETCHINGBuscando a origem e gravando no S3—
OCR_PROCESSINGPasso OCR_EXTRACTPassa direto — não há OCR nesta versão (§15)
AI_PROCESSINGChamando o modelo de visãoÉ o passo que custa token
MAPPINGAplicando os mapeamentos do template—
COMPLETEDConcluída, com rawData e mappedDataremap reaplica o mapeamento e o registro permanece COMPLETED
FAILEDFalha em qualquer passo acimaretry volta para PENDING e reprocessa do início, somando 1 a retryCount
PROCESSINGNuncaExiste no enum e nenhum caminho de código o atribui (§15)

Como um campo chega ao seu sistema

flowchart TD
  DOC["Documento — imagem ou PDF"] --> PROMPT["Prompt montado a partir do template"]

  PROMPT --> C1["contexto do documentType<br/>se for um dos seis conhecidos"]
  PROMPT --> C2["lista de campos esperados,<br/>com tipo e descrição"]
  PROMPT --> C3["formato exigido:<br/>extracted_data e confidence"]

  CUSTOM["customPrompt no template"] -.->|"SUBSTITUI tudo isso"| PROMPT

  C1 --> MODELO["Modelo de visão"]
  C2 --> MODELO
  C3 --> MODELO

  MODELO --> RAW["rawData = extracted_data"]
  MODELO --> CONF["confidenceScores = confidence"]

  RAW --> MAP["FieldMapping<br/>aplicados em ordem crescente de priority"]
  MAP --> M1["lê a origem — aceita caminho com ponto<br/>ex.: endereco.cidade"]
  M1 --> M2{"campo ausente?"}
  M2 -->|sim| M3["usa o defaultValue"]
  M2 -->|não| M4["aplica a transformação"]
  M3 --> M4
  M4 --> M5["escreve no destino<br/>também aceita caminho com ponto"]
  M5 --> MAPPED["mappedData — o que a sua esteira consome"]

  RAW -.->|"sem mapeamentos definidos,<br/>mappedData é igual a rawData"| MAPPED

Transformações disponíveis

TipoO que fazConfiguração
NONENada—
STRING_TRIMRemove espaços das pontas—
NUMBER_PARSEConverte texto em número. Com locale: "pt-BR", entende 1.234,56locale
CURRENCY_NORMALIZERemove R$, €, £, ¥ e espaços, e converte. Com locale: "pt-BR", entende o formato brasileirolocale
DATE_FORMATConverte entre formatos de datafrom, to — por exemplo DD/MM/YYYY para YYYY-MM-DD
REGEX_EXTRACTExtrai parte do texto por expressão regularpattern, group

09

Referência da API

Prefixo HTTP: /data-extraction. Em monolito, a base é http://localhost:3000. Em standalone, a porta é 3017; em staging, https://data-extraction.bb.stg.catalisa.app.

Atenção ao prefixo. O caminho real repete o nome do módulo: o basePath do app é /data-extraction e os routers são montados em /api/v1/data-extraction. A rota completa é /data-extraction/api/v1/data-extraction/.... Copie do quadro abaixo em vez de deduzir.

Todas as 22 rotas exigem, sem exceção: authMiddleware (Bearer JWT do IAM), requirePermission e o middleware local requireOrganization, que devolve 403 quando o token não carrega organizationId.

Configurações de provedor — /data-extraction/api/v1/data-extraction/provider-configs

MétodoRotaDescriçãoPermissão
POST.../provider-configsCria a configuração, testa a credencial e cifra. Responde 201DATA_EXTRACTION_ADMIN
GET.../provider-configsLista, paginadoDATA_EXTRACTION_READ
GET.../provider-configs/:idBusca (sem as credenciais)DATA_EXTRACTION_READ
PATCH.../provider-configs/:idAtualiza. Trocar credencial dispara novo testeDATA_EXTRACTION_ADMIN
DELETE.../provider-configs/:idExclusão lógica. Responde 204DATA_EXTRACTION_ADMIN
POST.../provider-configs/:id/set-defaultMarca como padrãoDATA_EXTRACTION_ADMIN
POST.../provider-configs/:id/testTesta a conexão com o provedorDATA_EXTRACTION_ADMIN

Filtros em GET: filter[providerType], filter[isActive]. Paginação por page[number] e page[size].

Templates — /data-extraction/api/v1/data-extraction/templates

MétodoRotaDescriçãoPermissão
POST.../templatesCria template. Responde 201DATA_EXTRACTION_ADMIN
GET.../templatesLista, paginadoDATA_EXTRACTION_READ
GET.../templates/:idBuscaDATA_EXTRACTION_READ
PATCH.../templates/:idAtualizaDATA_EXTRACTION_ADMIN
DELETE.../templates/:idExclusão lógica. Responde 204DATA_EXTRACTION_ADMIN

Filtros em GET: filter[documentType], filter[isActive].

Mapeamentos — /data-extraction/api/v1/data-extraction/templates/:templateId/mappings

MétodoRotaDescriçãoPermissão
POST.../templates/:templateId/mappingsCria mapeamento. Responde 201DATA_EXTRACTION_ADMIN
GET.../templates/:templateId/mappingsLista os mapeamentos do templateDATA_EXTRACTION_READ
GET.../templates/:templateId/mappings/:idBuscaDATA_EXTRACTION_READ
PATCH.../templates/:templateId/mappings/:idAtualizaDATA_EXTRACTION_ADMIN
DELETE.../templates/:templateId/mappings/:idRemove. Responde 204DATA_EXTRACTION_ADMIN

Extrações — /data-extraction/api/v1/data-extraction/extractions

MétodoRotaDescriçãoPermissão
POST.../extractionsCria a extração e enfileira. Responde 202DATA_EXTRACTION_WRITE
GET.../extractionsLista, paginadoDATA_EXTRACTION_READ
GET.../extractions/:idBusca — é a rota de acompanhamentoDATA_EXTRACTION_READ
POST.../extractions/:id/retryReprocessa. Só aceita FAILEDDATA_EXTRACTION_WRITE
POST.../extractions/:id/remapReaplica os mapeamentos. Só aceita COMPLETEDDATA_EXTRACTION_WRITE

Filtros em GET: filter[status], filter[businessId], filter[templateId].

Saúde

MétodoRotaDescrição
GET/data-extraction/healthSonda de disponibilidade. Pública, não contabilizada nas 22 rotas

POST /data-extraction/api/v1/data-extraction/provider-configs

Cria a configuração. Ao contrário de outros building blocks com provedor, esta rota testa a credencial antes de gravar: uma chave inválida é recusada na criação.

Request

json
{
  "name": "Gemini Produção",
  "providerType": "GOOGLE_GEMINI",
  "credentials": { "apiKey": "<sua chave da API Gemini>" },
  "settings": { "model": "gemini-2.5-flash", "temperature": 0.1, "maxTokens": 8192 },
  "isDefault": true
}
{
  "name": "Gemini Produção",
  "providerType": "GOOGLE_GEMINI",
  "credentials": { "apiKey": "<sua chave da API Gemini>" },
  "settings": { "model": "gemini-2.5-flash", "temperature": 0.1, "maxTokens": 8192 },
  "isDefault": true
}
CampoTipoObrigatórioDescrição
namestring (1–100)SimÚnico por organização
providerTypeGOOGLE_GEMINI | OPENAI | ANTHROPICSimSó GOOGLE_GEMINI funciona hoje (§15)
credentialsobjeto de textosSimCifrado antes de gravar
isDefaultbooleanoNão—
isActivebooleanoNãoPadrão true
settingsobjetoNãomodel, maxTokens, temperature

Credenciais aceitas para o Google Gemini

CombinaçãoUso
{ apiKey }Chave da API Gemini. É o caminho mais simples. Confirme que o projeto tem faturamento ativo — sem isso você cai na camada gratuita, que os termos do fornecedor proíbem para dado pessoal (§14)
{ serviceAccountKey, vertexProjectId?, vertexRegion? }Conta de serviço para Vertex AI. Região padrão us-central1 — informe southamerica-east1 se quiser processamento no Brasil (§14)
{ vertexProjectId, refreshToken, clientId, clientSecret }Vertex AI com OAuth de usuário
{ refreshToken, clientId, clientSecret }OAuth de usuário na API pública

Ajustes (settings) — padrões: modelo gemini-2.5-flash, maxTokens 8192, temperature 0,1.

Para documento de identidade, a configuração recomendada é conta de serviço com vertexRegion: "southamerica-east1" e model: "gemini-2.5-flash". É a única combinação com compromisso público de processamento dentro do Brasil — ver §14.

Resposta 201 — devolve a configuração sem o campo credentials, que não volta em nenhuma rota.

Erros

StatusCódigoQuando
400VALIDATIONCorpo reprovado; credenciais em combinação inválida; falha no teste de conexão
403—Sem DATA_EXTRACTION_ADMIN, ou token sem organizationId
409CONFLICTNome já usado na organização
500—DATA_EXTRACTION_CREDENTIAL_MASTER_KEY ausente ou malformada

POST /data-extraction/api/v1/data-extraction/templates

Request

json
{
  "name": "CNH",
  "description": "Carteira Nacional de Habilitação",
  "documentType": "id_document",
  "expectedFields": [
    { "name": "nome", "type": "string", "required": true, "description": "Nome completo do condutor" },
    { "name": "cpf", "type": "string", "required": true, "description": "CPF, com pontos e traço" },
    { "name": "data_nascimento", "type": "date", "required": true, "format": "YYYY-MM-DD" },
    { "name": "numero_registro", "type": "string", "description": "Número do registro da CNH" },
    { "name": "validade", "type": "date", "format": "YYYY-MM-DD" },
    { "name": "categoria", "type": "string", "description": "Categoria de habilitação, por exemplo AB" }
  ]
}
{
  "name": "CNH",
  "description": "Carteira Nacional de Habilitação",
  "documentType": "id_document",
  "expectedFields": [
    { "name": "nome", "type": "string", "required": true, "description": "Nome completo do condutor" },
    { "name": "cpf", "type": "string", "required": true, "description": "CPF, com pontos e traço" },
    { "name": "data_nascimento", "type": "date", "required": true, "format": "YYYY-MM-DD" },
    { "name": "numero_registro", "type": "string", "description": "Número do registro da CNH" },
    { "name": "validade", "type": "date", "format": "YYYY-MM-DD" },
    { "name": "categoria", "type": "string", "description": "Categoria de habilitação, por exemplo AB" }
  ]
}
CampoTipoObrigatórioDescrição
namestring (1–100)SimÚnico por organização
descriptionstring (máx. 1000)Não—
documentTypestring (1–50)SimContexto do prompt. Seis valores têm tratamento especial
expectedFieldslistaSimPode ser vazia; nesse caso o prompt fica genérico
customPromptstring (máx. 5000)NãoSubstitui integralmente o prompt gerado, inclusive as instruções de formato
isActivebooleanoNãoPadrão true

A description de cada campo é o que mais influencia a qualidade da extração — ela vai literalmente para o prompt. "CPF, com pontos e traço" produz resultado melhor e mais estável do que apenas "cpf".

Se usar customPrompt, você assume a responsabilidade de instruir o modelo a devolver o objeto com as chaves extracted_data e confidence. Sem isso, a resposta não é interpretada e a extração falha.


POST /data-extraction/api/v1/data-extraction/extractions

Aceita o trabalho e responde 202. A extração ainda não aconteceu quando você recebe a resposta.

Request

json
{
  "fileId": "8f2c1a90-0000-0000-0000-000000000000",
  "templateId": "3a2b1c00-0000-0000-0000-000000000000",
  "documentType": "id_document",
  "businessId": "proposta-2026-000481",
  "metadata": { "origem": "app-cliente" }
}
{
  "fileId": "8f2c1a90-0000-0000-0000-000000000000",
  "templateId": "3a2b1c00-0000-0000-0000-000000000000",
  "documentType": "id_document",
  "businessId": "proposta-2026-000481",
  "metadata": { "origem": "app-cliente" }
}
CampoTipoObrigatórioDescrição
fileIdUUIDUma das trêsArquivo no file-storage
contentstringUma das trêsArquivo em base64
contentMimeTypestring (máx. 100)NãoTipo do conteúdo em base64
urlURL (máx. 2048)Uma das trêsEndereço público do arquivo. Passa por verificação de segurança
configIdUUIDNãoConfiguração a usar. Omitido, usa a padrão
templateIdUUIDNãoSem ele, a extração é genérica e não há mapeamento
documentTypestring (máx. 50)NãoSobrepõe o do template
businessIdstringNãoIdentificador da operação de origem
metadataobjetoNãoCampo livre

Exatamente uma das três origens. Zero ou duas resultam em 400.

Resposta 202

json
{
  "data": {
    "type": "extraction",
    "id": "d41f7b02-0000-0000-0000-000000000000",
    "links": { "self": "/api/v1/data-extraction/extractions/d41f7b02-0000-0000-0000-000000000000" },
    "attributes": {
      "status": "PENDING",
      "currentStep": "FILE_FETCH",
      "sourceType": "FILE_ID",
      "retryCount": 0,
      "businessId": "proposta-2026-000481",
      "createdAt": "2026-08-16T14:03:11.000Z"
    }
  }
}
{
  "data": {
    "type": "extraction",
    "id": "d41f7b02-0000-0000-0000-000000000000",
    "links": { "self": "/api/v1/data-extraction/extractions/d41f7b02-0000-0000-0000-000000000000" },
    "attributes": {
      "status": "PENDING",
      "currentStep": "FILE_FETCH",
      "sourceType": "FILE_ID",
      "retryCount": 0,
      "businessId": "proposta-2026-000481",
      "createdAt": "2026-08-16T14:03:11.000Z"
    }
  }
}

Erros

StatusCódigoQuando
400—Nenhuma ou mais de uma origem; corpo reprovado no Zod
403—Sem DATA_EXTRACTION_WRITE, ou token sem organizationId
413—Corpo acima de 1 MB — teto prático de content (§15)

A configuração de provedor não é exigida na criação: se a organização não tiver uma padrão, a extração é criada mesmo assim e falha depois, no passo de IA.


GET /data-extraction/api/v1/data-extraction/extractions/:id

A rota de acompanhamento.

Resposta 200, extração concluída

json
{
  "data": {
    "type": "extraction",
    "id": "d41f7b02-0000-0000-0000-000000000000",
    "attributes": {
      "status": "COMPLETED",
      "currentStep": "FIELD_MAP",
      "rawData": {
        "nome": "MARIA SOUZA",
        "cpf": "123.456.789-00",
        "data_nascimento": "1988-03-12",
        "validade": "2030-05-15",
        "categoria": "AB"
      },
      "mappedData": {
        "fullName": "MARIA SOUZA",
        "taxId": "12345678900",
        "birthDate": "1988-03-12"
      },
      "confidenceScores": {
        "nome": 0.98, "cpf": 0.99, "data_nascimento": 0.95,
        "validade": 0.91, "categoria": 0.72
      },
      "providerModel": "gemini-2.5-flash",
      "tokensUsed": 1842,
      "processingTimeMs": 4231,
      "retryCount": 0,
      "completedAt": "2026-08-16T14:03:26.000Z"
    }
  }
}
{
  "data": {
    "type": "extraction",
    "id": "d41f7b02-0000-0000-0000-000000000000",
    "attributes": {
      "status": "COMPLETED",
      "currentStep": "FIELD_MAP",
      "rawData": {
        "nome": "MARIA SOUZA",
        "cpf": "123.456.789-00",
        "data_nascimento": "1988-03-12",
        "validade": "2030-05-15",
        "categoria": "AB"
      },
      "mappedData": {
        "fullName": "MARIA SOUZA",
        "taxId": "12345678900",
        "birthDate": "1988-03-12"
      },
      "confidenceScores": {
        "nome": 0.98, "cpf": 0.99, "data_nascimento": 0.95,
        "validade": 0.91, "categoria": 0.72
      },
      "providerModel": "gemini-2.5-flash",
      "tokensUsed": 1842,
      "processingTimeMs": 4231,
      "retryCount": 0,
      "completedAt": "2026-08-16T14:03:26.000Z"
    }
  }
}

Resposta 200, extração falhada — status: "FAILED", currentStep com o passo em que parou e errorMessage com o motivo.

Use confidenceScores campo a campo para decidir o que revisar. A média geral esconde exatamente o caso que importa: um único campo crítico com nota baixa no meio de vários campos ótimos.


POST /data-extraction/api/v1/data-extraction/extractions/:id/retry

Volta a extração para PENDING, soma 1 a retryCount, limpa a mensagem de erro e reenfileira desde o início. Refaz a chamada ao modelo e custa tokens de novo.

StatusQuando
200Reenfileirada
400Can only retry failed extractions — o estado não é FAILED
404Extração inexistente ou de outra organização

Não há limite de tentativas nem espera progressiva. Se a causa for determinística — imagem ilegível, credencial errada —, repetir só gasta.


POST /data-extraction/api/v1/data-extraction/extractions/:id/remap

Reaplica os mapeamentos sobre o dado bruto já extraído. Não chama o modelo e não custa tokens.

Request (corpo opcional)

json
{ "templateId": "outro-template-uuid" }
{ "templateId": "outro-template-uuid" }

Sem templateId, usa o da extração.

StatusQuando
200mappedData atualizado
400Can only remap completed extractions; sem dado bruto; sem template; ou o template não tem mapeamento definido
404Extração inexistente ou de outra organização

É a rota certa quando você errou o mapeamento, não a extração. Corrigir o mapeamento e reaplicar é gratuito; reprocessar não é.


10

Início rápido

Do zero a um documento lido. Comandos escritos para staging; não executados nesta redação.

Antes de começar: confirme com a operação que EXTRACTION_CONSUMER_ENABLED está ligado no ambiente. Sem isso, o passo 5 fica em PENDING para sempre — ver §15.

1. Autenticar no IAM

bash
TOKEN=$(curl -s -X POST https://iam.bb.stg.catalisa.app/iam/api/v1/users/login \
  -H "Content-Type: application/json" \
  -d '{
    "email": "admin@catalisa.app",
    "password": "root123456",
    "organizationId": "b0000000-0000-0000-0000-000000000001"
  }' | jq -r .accessToken)

BASE=https://data-extraction.bb.stg.catalisa.app/data-extraction/api/v1/data-extraction
TOKEN=$(curl -s -X POST https://iam.bb.stg.catalisa.app/iam/api/v1/users/login \
  -H "Content-Type: application/json" \
  -d '{
    "email": "admin@catalisa.app",
    "password": "root123456",
    "organizationId": "b0000000-0000-0000-0000-000000000001"
  }' | jq -r .accessToken)

BASE=https://data-extraction.bb.stg.catalisa.app/data-extraction/api/v1/data-extraction

2. Configurar o provedor de IA

bash
CFG=$(curl -s -X POST "$BASE/provider-configs" \
  -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{
    "name": "Gemini Homologação",
    "providerType": "GOOGLE_GEMINI",
    "credentials": { "apiKey": "SUBSTITUA_PELA_SUA_CHAVE" },
    "isDefault": true
  }')

echo "$CFG" | jq '.data.attributes'
CFG=$(curl -s -X POST "$BASE/provider-configs" \
  -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{
    "name": "Gemini Homologação",
    "providerType": "GOOGLE_GEMINI",
    "credentials": { "apiKey": "SUBSTITUA_PELA_SUA_CHAVE" },
    "isDefault": true
  }')

echo "$CFG" | jq '.data.attributes'

A criação já testa a credencial. Se a chave estiver errada, você recebe 400 aqui — e é bom que seja assim.

3. Criar o template

bash
TPL=$(curl -s -X POST "$BASE/templates" \
  -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{
    "name": "Boleto Bancário",
    "documentType": "boleto",
    "expectedFields": [
      { "name": "beneficiary_name", "type": "string", "required": true, "description": "Nome do beneficiário" },
      { "name": "payer_name", "type": "string", "required": true, "description": "Nome do pagador" },
      { "name": "due_date", "type": "date", "required": true, "format": "YYYY-MM-DD" },
      { "name": "amount", "type": "number", "required": true, "description": "Valor do documento em reais" },
      { "name": "digitable_line", "type": "string", "required": true, "description": "Linha digitável de 47 dígitos" }
    ]
  }')

TPL_ID=$(echo "$TPL" | jq -r '.data.id')
TPL=$(curl -s -X POST "$BASE/templates" \
  -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{
    "name": "Boleto Bancário",
    "documentType": "boleto",
    "expectedFields": [
      { "name": "beneficiary_name", "type": "string", "required": true, "description": "Nome do beneficiário" },
      { "name": "payer_name", "type": "string", "required": true, "description": "Nome do pagador" },
      { "name": "due_date", "type": "date", "required": true, "format": "YYYY-MM-DD" },
      { "name": "amount", "type": "number", "required": true, "description": "Valor do documento em reais" },
      { "name": "digitable_line", "type": "string", "required": true, "description": "Linha digitável de 47 dígitos" }
    ]
  }')

TPL_ID=$(echo "$TPL" | jq -r '.data.id')

4. Enviar o documento

bash
EXT=$(curl -s -X POST "$BASE/extractions" \
  -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{
    "content": "'"$(base64 -w0 boleto.png)"'",
    "contentMimeType": "image/png",
    "templateId": "'"$TPL_ID"'",
    "documentType": "boleto",
    "businessId": "teste-001"
  }')

EXT_ID=$(echo "$EXT" | jq -r '.data.id')
echo "$EXT" | jq '.data.attributes | {status, currentStep}'
EXT=$(curl -s -X POST "$BASE/extractions" \
  -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{
    "content": "'"$(base64 -w0 boleto.png)"'",
    "contentMimeType": "image/png",
    "templateId": "'"$TPL_ID"'",
    "documentType": "boleto",
    "businessId": "teste-001"
  }')

EXT_ID=$(echo "$EXT" | jq -r '.data.id')
echo "$EXT" | jq '.data.attributes | {status, currentStep}'

Resposta esperada: 202 com {"status": "PENDING", "currentStep": "FILE_FETCH"}.

5. Acompanhar até concluir

bash
for i in $(seq 1 20); do
  R=$(curl -s "$BASE/extractions/$EXT_ID" -H "Authorization: Bearer $TOKEN")
  S=$(echo "$R" | jq -r '.data.attributes.status')
  echo "$S — passo: $(echo "$R" | jq -r '.data.attributes.currentStep')"
  case "$S" in
    COMPLETED) echo "$R" | jq '.data.attributes | {rawData, mappedData, confidenceScores, tokensUsed}'; break ;;
    FAILED)    echo "$R" | jq '.data.attributes | {currentStep, errorMessage}'; break ;;
  esac
  sleep 3
done
for i in $(seq 1 20); do
  R=$(curl -s "$BASE/extractions/$EXT_ID" -H "Authorization: Bearer $TOKEN")
  S=$(echo "$R" | jq -r '.data.attributes.status')
  echo "$S — passo: $(echo "$R" | jq -r '.data.attributes.currentStep')"
  case "$S" in
    COMPLETED) echo "$R" | jq '.data.attributes | {rawData, mappedData, confidenceScores, tokensUsed}'; break ;;
    FAILED)    echo "$R" | jq '.data.attributes | {currentStep, errorMessage}'; break ;;
  esac
  sleep 3
done

Se o estado nunca sair de PENDING, o processador não está rodando. É a limitação principal da §15, e não é problema da sua chamada.

6. Traduzir para o vocabulário do seu sistema

bash
curl -s -X POST "$BASE/templates/$TPL_ID/mappings" \
  -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{
    "sourceField": "amount",
    "targetField": "valorDocumento",
    "transformType": "CURRENCY_NORMALIZE",
    "transformConfig": { "locale": "pt-BR" },
    "priority": 1
  }' | jq

curl -s -X POST "$BASE/extractions/$EXT_ID/remap" \
  -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" -d '{}' \
  | jq '.data.attributes.mappedData'
curl -s -X POST "$BASE/templates/$TPL_ID/mappings" \
  -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{
    "sourceField": "amount",
    "targetField": "valorDocumento",
    "transformType": "CURRENCY_NORMALIZE",
    "transformConfig": { "locale": "pt-BR" },
    "priority": 1
  }' | jq

curl -s -X POST "$BASE/extractions/$EXT_ID/remap" \
  -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" -d '{}' \
  | jq '.data.attributes.mappedData'

O remap reaproveita o dado já extraído — não gasta token nenhum.

Credenciais de staging conforme AMBIENTES.md. Nunca use documento real de cliente em ambiente de homologação.


11

Receitas

Ler um documento que já está no file-storage

O caminho recomendado, e o único que não esbarra no limite de 1 MB do corpo.

1. Suba o arquivo no file-storage

bash
FILE_ID=$(curl -s -X POST https://storage.bb.stg.catalisa.app/file-storage/api/v1/files \
  -H "Authorization: Bearer $TOKEN" -F "file=@comprovante.jpg" | jq -r '.data.id')
FILE_ID=$(curl -s -X POST https://storage.bb.stg.catalisa.app/file-storage/api/v1/files \
  -H "Authorization: Bearer $TOKEN" -F "file=@comprovante.jpg" | jq -r '.data.id')

Resposta esperada — um UUID em FILE_ID.

2. Crie a extração apontando para o arquivo

bash
curl -s -X POST "$BASE/extractions" \
  -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{"fileId":"'"$FILE_ID"'","templateId":"'"$TPL_ID"'","businessId":"proposta-2026-000481"}' | jq
curl -s -X POST "$BASE/extractions" \
  -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{"fileId":"'"$FILE_ID"'","templateId":"'"$TPL_ID"'","businessId":"proposta-2026-000481"}' | jq

Resposta esperada — 202, com sourceType igual a FILE_ID:

json
{
  "data": {
    "type": "extraction",
    "id": "d41f7b02-0000-0000-0000-000000000000",
    "attributes": { "status": "PENDING", "currentStep": "FILE_FETCH", "sourceType": "FILE_ID" }
  }
}
{
  "data": {
    "type": "extraction",
    "id": "d41f7b02-0000-0000-0000-000000000000",
    "attributes": { "status": "PENDING", "currentStep": "FILE_FETCH", "sourceType": "FILE_ID" }
  }
}

Armadilhas.

  • O fileId precisa pertencer à mesma organização do token, ou o passo de busca falha.
  • Use o mesmo businessId do document-template e do e-signature. É o fio que costura a operação nos três serviços.
  • O arquivo é copiado para uma área própria no S3. Isso é intencional — o reprocessamento não depende de a origem continuar existindo —, e implica armazenamento duplicado.

Montar a regra de revisão por exceção

O padrão que muda a economia do processo. Aplique na sua esteira depois de receber o evento de conclusão.

bash
curl -s "$BASE/extractions/$EXT_ID" -H "Authorization: Bearer $TOKEN" | jq '
  .data.attributes as $a
  | {
      abaixoDoLimiar: [ $a.confidenceScores | to_entries[] | select(.value < 0.9) | .key ],
      valores: $a.mappedData
    }'
curl -s "$BASE/extractions/$EXT_ID" -H "Authorization: Bearer $TOKEN" | jq '
  .data.attributes as $a
  | {
      abaixoDoLimiar: [ $a.confidenceScores | to_entries[] | select(.value < 0.9) | .key ],
      valores: $a.mappedData
    }'

Armadilhas.

  • Não use a média. Um campo crítico com nota 0,4 no meio de dezenove campos ótimos produz média alta. Decida campo a campo.
  • A nota vem do próprio modelo e é uma autoavaliação, não uma medida calibrada. Trate-a como ordenação relativa — "este campo é mais duvidoso que aquele" —, não como probabilidade.
  • Calibre o limiar com os seus documentos. O valor que funciona para boleto impresso não funciona para foto de holerite amassado.
  • Campo que o modelo não achou volta com valor nulo e confiança 0. Verifique nulo antes de comparar o limiar.

Traduzir campos aninhados e formatos brasileiros

Objetivo: entregar à sua esteira o vocabulário e os tipos que ela espera, sem tratamento no meio do caminho.

PassoCampo de origemViraTransformação
1valor_total = "R$ 1.234,56"pagamento.valor = 1234.56CURRENCY_NORMALIZE, locale: pt-BR
2vencimento = "15/08/2026"pagamento.vencimento = "2026-08-15"DATE_FORMAT, DD/MM/YYYY → YYYY-MM-DD
3cpf_pagador = "123.456.789-00"pagador.documentoREGEX_EXTRACT — leia a armadilha abaixo

1. Valor em reais vira número

bash
curl -s -X POST "$BASE/templates/$TPL_ID/mappings" \
  -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{"sourceField":"valor_total","targetField":"pagamento.valor",
       "transformType":"CURRENCY_NORMALIZE","transformConfig":{"locale":"pt-BR"},"priority":1}'
curl -s -X POST "$BASE/templates/$TPL_ID/mappings" \
  -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{"sourceField":"valor_total","targetField":"pagamento.valor",
       "transformType":"CURRENCY_NORMALIZE","transformConfig":{"locale":"pt-BR"},"priority":1}'

Resposta esperada: 201 com o mapeamento criado.

2. Data brasileira vira ISO

bash
curl -s -X POST "$BASE/templates/$TPL_ID/mappings" \
  -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{"sourceField":"vencimento","targetField":"pagamento.vencimento",
       "transformType":"DATE_FORMAT","transformConfig":{"from":"DD/MM/YYYY","to":"YYYY-MM-DD"},"priority":2}'
curl -s -X POST "$BASE/templates/$TPL_ID/mappings" \
  -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{"sourceField":"vencimento","targetField":"pagamento.vencimento",
       "transformType":"DATE_FORMAT","transformConfig":{"from":"DD/MM/YYYY","to":"YYYY-MM-DD"},"priority":2}'

3. Só os dígitos do CPF

bash
curl -s -X POST "$BASE/templates/$TPL_ID/mappings" \
  -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{"sourceField":"cpf_pagador","targetField":"pagador.documento",
       "transformType":"REGEX_EXTRACT","transformConfig":{"pattern":"[0-9]+","group":0},"priority":3}'
curl -s -X POST "$BASE/templates/$TPL_ID/mappings" \
  -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{"sourceField":"cpf_pagador","targetField":"pagador.documento",
       "transformType":"REGEX_EXTRACT","transformConfig":{"pattern":"[0-9]+","group":0},"priority":3}'

Armadilhas.

  • REGEX_EXTRACT com [0-9]+ devolve apenas o primeiro grupo de dígitos: de 123.456.789-00 sai 123. Para juntar tudo, o padrão precisa capturar o número inteiro — teste antes de confiar.
  • sourceField e targetField aceitam caminho com ponto para estrutura aninhada. Se o modelo devolveu uma lista, o índice não é suportado.
  • A ordem de aplicação é a de priority crescente. Mapeamentos que escrevem no mesmo destino se sobrescrevem em silêncio.
  • Só os campos com mapeamento entram em mappedData. Tudo que você não mapear some do resultado traduzido — o dado continua em rawData.
  • A combinação de templateId, sourceField e targetField é única. Repetir devolve 409.

Melhorar a extração sem trocar de modelo

O maior ganho de qualidade vem do template, não do provedor.

bash
curl -s -X PATCH "$BASE/templates/$TPL_ID" \
  -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{
    "expectedFields": [
      { "name": "renda_liquida", "type": "number", "required": true,
        "description": "Valor líquido a receber, em reais, na linha final do holerite. NÃO é o salário bruto nem o total de proventos." },
      { "name": "competencia", "type": "date", "format": "YYYY-MM",
        "description": "Mês de competência do holerite, no formato ano-mês" }
    ]
  }' | jq
curl -s -X PATCH "$BASE/templates/$TPL_ID" \
  -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{
    "expectedFields": [
      { "name": "renda_liquida", "type": "number", "required": true,
        "description": "Valor líquido a receber, em reais, na linha final do holerite. NÃO é o salário bruto nem o total de proventos." },
      { "name": "competencia", "type": "date", "format": "YYYY-MM",
        "description": "Mês de competência do holerite, no formato ano-mês" }
    ]
  }' | jq

Armadilhas.

  • A description vai literalmente para o prompt. Descrição que diz o que o campo não é costuma render mais que sinônimo.
  • format também entra no prompt e é o que faz o modelo devolver a data no padrão certo — sem ele, cada documento volta num formato.
  • customPrompt substitui tudo, inclusive as instruções de formato de resposta. Se usá-lo, replique a exigência das chaves extracted_data e confidence, ou a extração falha.
  • Mudar o template não afeta extrações já concluídas. Para reaplicar, é retry — e aí custa token.

Investigar uma extração que falhou

bash
curl -s "$BASE/extractions/$EXT_ID" -H "Authorization: Bearer $TOKEN" \
  | jq '.data.attributes | {status, currentStep, errorMessage, retryCount, sourceType}'
curl -s "$BASE/extractions/$EXT_ID" -H "Authorization: Bearer $TOKEN" \
  | jq '.data.attributes | {status, currentStep, errorMessage, retryCount, sourceType}'
currentStepCausa provávelO que fazer
FILE_FETCHArquivo inexistente, base64 inválido, URL recusada pela verificação de segurança, ou tamanho acima do limiteConfira a origem. URL interna ou de rede privada é recusada de propósito
AI_STRUCTURENenhuma configuração padrão; credencial expirada; provedor não implementado; o modelo não devolveu JSON interpretávelRode POST /provider-configs/:id/test
FIELD_MAPFalha ao aplicar transformaçãoRevise transformConfig e use remap depois de corrigir
Continua PENDINGO processador não está rodandoVerifique EXTRACTION_CONSUMER_ENABLED no ambiente (§15)

Armadilha. retry não tem espera progressiva nem teto de tentativas. Antes de repetir, descubra se a causa é transitória — repetir erro determinístico só gasta token.


12

Integração com outros building blocks

O Data Extraction é a última peça da cadeia de documentos da Catalisa, e a única que trabalha no sentido contrário. O document-template gera o documento que sai; o e-signature coleta a assinatura; o Data Extraction lê o que chega.

Building blockComo se relacionaObrigatório
IAMEmite o token e define o organizationId que isola configurações, templates e extraçõesSim
File StorageOrigem do documento por fileId, e destino recomendado dos arquivos recebidosNão
E-SignatureFornece o documento assinado que volta para conferênciaNão
Document TemplateFecha a cadeia: gera o documento que, preenchido, volta para ser lidoNão
CustomersDestino natural dos dados extraídos de documento de identidadeNão
Decision PlatformConsome os campos extraídos como entrada da esteira de decisãoNão
Webhooks EngineEntrega os eventos data-extraction.* a sistemas externosNão
Audit TrailRegistra quem criou extração e quem leu resultado com dado pessoalNão

A cadeia de documentos, ponta a ponta

flowchart TD
  subgraph ida["O documento que SAI da plataforma"]
    DT["Document Template<br/>gera a CCB"] --> FS1["File Storage<br/>guarda o PDF"]
    FS1 --> ES["E-Signature<br/>coleta a assinatura"]
    ES --> ASS["Documento assinado"]
  end

  ASS -->|"o cliente devolve documento e comprovantes"| FS2

  subgraph volta["O documento que CHEGA à plataforma"]
    FS2["File Storage<br/>recebe a foto do cliente"] -->|fileId| DE["Data Extraction<br/>lê o documento e devolve JSON<br/>com confiança por campo"]
    DE -->|mappedData| DP["Decision Platform<br/>decide com o dado já estruturado"]
    DE -->|"dados cadastrais"| CU["Customers<br/>cadastro do cliente"]
  end

  BID["businessId = 'proposta-2026-000481'<br/>o mesmo em todos"] -.-> DT
  BID -.-> ES
  BID -.-> DE
  BID -.-> DP

Este diagrama é o argumento comercial da Catalisa nesta família: gerar, assinar e ler são o mesmo problema visto de três ângulos, e a cadeia fecha um ciclo. O documento que a plataforma emitiu volta preenchido e é lido pela mesma plataforma; o comprovante que o cliente manda entra pela mesma porta.

Quem monta isso com três fornecedores diferentes gasta a maior parte do esforço em amarrar identidade, isolamento por cliente e correlação — e depois descobre que responder "onde está a proposta 481" exige juntar três bases que não se conhecem. Aqui as três peças compartilham o token do IAM, o organizationId do tenant e o businessId da operação. GET /extractions?filter[businessId]=proposta-2026-000481 responde o que foi lido; a mesma chamada, com o mesmo filtro, responde nos outros dois serviços o que foi gerado e o que foi assinado.

Vale registrar a fronteira com honestidade: a cadeia está desenhada e as peças se encaixam pelo businessId, mas o encadeamento não é automático. Não há orquestração pronta que dispare a extração quando um arquivo chega. Quem monta o fluxo é a sua aplicação, ou o decision-platform, ouvindo os eventos.


13

Configuração e operação

Variáveis de ambiente

VariávelDescriçãoObrigatóriaPadrão
DATA_EXTRACTION_CREDENTIAL_MASTER_KEYChave AES-256-GCM para cifrar credenciais de IA. Exatamente 64 caracteres hexadecimais. Gere com openssl rand -hex 32Sim, para usar o módulo—
EXTRACTION_CONSUMER_ENABLEDLiga o processador assíncrono. Sem ele, nenhuma extração sai de PENDINGSim, na práticafalse
EXTRACTION_PIPELINE_BATCH_SIZEMensagens lidas por cicloNão10
EXTRACTION_PIPELINE_BLOCK_MSEspera na leitura da filaNão5000
EXTRACTION_MAX_FILE_SIZE_MBTamanho máximo do arquivoNão10
DATABASE_URLPostgreSQL. O building block usa o schema extractionSim—
REDIS_URLRedis. Não é opcional — a fila de processamento vive neleSim—
S3_*Endpoint, credenciais e bucket, para guardar os documentosSim—
JWT_SECRETSegredo HS256 compartilhado com o IAM. Mínimo 44 caracteresSim—
DEPLOYMENT_MODEmonolith ou standaloneNãomonolith
PORTPorta em standaloneNão3000 (a topologia expõe 3017)

EXTRACTION_CONSUMER_ENABLED é a variável que mais importa neste building block. Ela é false por padrão e o serviço sobe normalmente sem ela, registrando apenas uma linha informativa no log. A API aceita trabalho e responde 202; ninguém processa. Confirme-a no deploy — e ver §15.

Dependências de infraestrutura

DependênciaPara quê
PostgreSQLSchema extraction — configurações, templates, mapeamentos e extrações
Redis StreamsFila data-extraction:pipeline, com o grupo extraction-pipeline-processors
S3 ou MinIOCópia de trabalho de cada documento, em extraction/{organização}/{extração}/source
IAMVerificação do token; o organizationId vem do claim assinado
Provedor de IAHoje, a API do Google Gemini

Limites e quotas

LimiteValorOnde
Corpo da requisição1 MBapplyCommonMiddleware — é o teto prático de content em base64
Tamanho do arquivo10 MB por padrãoEXTRACTION_MAX_FILE_SIZE_MB — inalcançável por content, alcançável por fileId e url
Espera ao baixar de URL30 segundosCliente HTTP protegido
Tokens de saída do modelo8192 por padrãosettings.maxTokens
customPrompt5.000 caracteresZod
Recuperação de mensagem paradaApós 60 segundos de inatividade, na subidaConsumidor
Limite de taxa global10.000 requisições por minuto por IP, quando ligadoRATE_LIMIT_GLOBAL_MAX

Eventos publicados

EventoQuando
data-extraction.extraction.startedExtração criada e enfileirada
data-extraction.extraction.completedMapeamento concluído e estado COMPLETED
data-extraction.extraction.failedFalha em qualquer passo, com o passo e o motivo
data-extraction.extraction.retryReprocessamento solicitado

Os tipos extraction.step.started e extraction.step.completed existem no catálogo e não são publicados por nenhum caminho de código.

Catálogo de erros

O envelope de erro é { "error": "<código>", "message": "<texto>", "details": <opcional> }. As rotas que usam validação com safeParse — extrações, templates, mapeamentos e configurações — devolvem, em caso de corpo inválido, { "error": { ...formato do Zod... } }, que é um formato diferente. Trate os dois.

StatusCódigoSignificaO que fazer
400VALIDATIONCorpo reprovado; origem ambígua; estado incompatível com retry ou remap; teste de credencial falhouLeia a message
401—Token ausente, inválido ou expiradoRenove no IAM
403—Falta a permissão exigida, ou o token não carrega organizationIdConfira o papel e a organização
404NOT_FOUNDRecurso inexistente, excluído, ou de outra organizaçãoConfira o identificador. "De outra organização" e "inexistente" são a mesma resposta, de propósito
409CONFLICTNome de configuração ou de template repetido; mapeamento duplicadoEscolha outro
413—Corpo acima de 1 MBUse fileId ou url
429—Limite de taxa estouradoAplique recuo exponencial
500INTERNALFalha de infraestrutura, ou chave mestra ausenteVerifique a message e os logs

Erros do provedor de IA aparecem em errorMessage da extração, não como resposta HTTP — a chamada ao modelo acontece depois do 202.

Observabilidade.

  • GET /data-extraction/health responde a sonda de disponibilidade. Ela não verifica se o processador está rodando — um serviço com o processador desligado responde saudável.
  • O melhor indicador operacional é a contagem de extrações em PENDING com mais de alguns minutos: se ela cresce, o processador não está consumindo.
  • Cada extração concluída grava providerModel, tokensUsed e processingTimeMs. É a base para acompanhar custo de IA e latência sem depender do painel do provedor.
  • retryCount acumula por extração e é o sinal mais direto de qualidade de origem: muitas tentativas indicam documento ruim ou template mal descrito.
  • O processador registra cada passo com o identificador da extração e o passo, e recupera na subida as mensagens paradas há mais de 60 segundos.

14

Segurança e compliance

Esta seção é mais longa que a dos outros building blocks de propósito. Documento de identidade é dado pessoal, e a decisão de enviá-lo a um provedor de IA de terceiro é uma decisão de LGPD que precisa ser tomada conscientemente, não por omissão.

Isolamento entre tenants e credenciais cifradas

Isolamento entre tenants. O organizationId vem do claim assinado do JWT e nunca do corpo. Todas as 22 rotas passam pelo middleware local requireOrganization, que devolve 403 quando o claim está ausente. Os serviços conferem a organização do recurso antes de devolvê-lo, e um recurso de outra organização responde 404, não 403 — a distinção permitiria descobrir a existência dele. No S3, cada arquivo fica em extraction/{organizationId}/{extractionId}/source, o que mantém a separação também no armazenamento.

Credenciais do provedor de IA. O que você envia em credentials — chave de API, chave de conta de serviço, tokens de OAuth — é serializado e cifrado com AES-256-GCM antes de tocar o banco, com vetor de inicialização de 12 bytes sorteado a cada operação e etiqueta de autenticação de 16 bytes. A chave mestra DATA_EXTRACTION_CREDENTIAL_MASTER_KEY é validada como 64 caracteres hexadecimais e é do serviço, cifrando as credenciais de todos os tenants — guarde-a em SOPS e trate um vazamento dela como comprometimento de todas as credenciais. O campo cifrado nunca volta em nenhuma resposta: a função que monta o corpo de resposta simplesmente não o inclui. Não há rotação de chave implementada (§15).

O documento é enviado a um provedor de IA de terceiro

O documento é enviado a um provedor de IA de terceiro. Sim, e isto precisa estar claro.

O passo de estruturação lê o arquivo do S3, converte em base64 e envia a imagem inteira, no corpo da requisição, para a API do provedor configurado. Com a configuração padrão, esse provedor é o Google, e o destino é a API do Gemini ou o Vertex AI, conforme o tipo de credencial. Não há edição, mascaramento nem recorte antes do envio: o que vai é o documento completo, com foto, número e todos os demais campos.

A natureza do dado, com a distinção que importa. O art. 5º, II da LGPD traz um rol taxativo de dado sensível, que inclui "dado genético ou biométrico, quando vinculado a uma pessoa natural". O número do RG ou da CNH não está nesse rol — é dado pessoal comum, tratado pelo art. 7º. A imagem facial, quando usada para identificação ou autenticação, é dado biométrico e portanto sensível, sujeita ao art. 11.

A distinção é operacional, não acadêmica: extrair texto de uma CNH é art. 7º; comparar o rosto da CNH com uma selfie é art. 11. Este building block faz apenas a primeira coisa — ele não faz comparação facial nem biometria. Mas a imagem enviada ao provedor contém o rosto, e um fluxo de onboarding típico faz as duas coisas com fornecedores diferentes, caindo nos dois regimes. Registre isso no seu mapeamento de tratamento.

Um alerta que evita erro caro: legítimo interesse (art. 7º, IX) não serve para dado sensível — ele não consta do rol do art. 11. Para o componente biométrico, as bases realistas são o art. 11, II, "a" (cumprimento de obrigação legal ou regulatória) e o art. 11, II, "g" (prevenção à fraude e segurança do titular em processos de identificação e autenticação de cadastro em sistemas eletrônicos).

flowchart TD
  DOC["Imagem de uma CNH"] --> Q{"o que se faz com ela?"}
  Q -->|"extrair o texto — nome, CPF, validade"| A7["Dado pessoal comum<br/>art. 7º da LGPD"]
  Q -->|"comparar o rosto com uma selfie"| A11["Dado biométrico — SENSÍVEL<br/>art. 11 da LGPD"]

  A7 --> BB["É isto que este building block faz"]
  A11 --> OUTRO["Outro fornecedor —<br/>este BB não faz biometria"]

  A11 --> AVISO["Legítimo interesse (art. 7º, IX)<br/>NÃO serve aqui"]
  AVISO --> BASES["Bases realistas:<br/>art. 11, II, 'a' — obrigação legal ou regulatória<br/>art. 11, II, 'g' — prevenção à fraude"]

  BB -.->|"mas a imagem enviada contém o rosto"| A11

Atenção. Um fluxo de onboarding típico faz as duas coisas, com fornecedores diferentes, e cai nos dois regimes ao mesmo tempo. Registre isso no seu mapeamento de tratamento.

PontoSituação
Papel das partesO cliente da Catalisa é o controlador. A Catalisa é operadora. O provedor de IA é suboperador, e a relação contratual com ele é do cliente, porque a credencial é dele
Transferência internacionalCom a configuração padrão, o processamento ocorre fora do Brasil. Isso é transferência internacional, regida pelos arts. 33 a 36
Base legalPrecisa ser definida pelo cliente para a finalidade específica de leitura automatizada do documento. O building block não presume nenhuma
RetençãoO documento fica no S3 e o dado extraído fica no banco indefinidamente. Não há política de retenção nem expurgo automático (§15)

Transferência internacional: o que a ANPD já decidiu

Transferência internacional: o que a ANPD já decidiu, e o que muda a escolha.

O art. 33 permite a transferência em hipóteses fechadas. As duas que importam aqui são o inciso I — países ou organismos com grau de proteção adequado reconhecido — e o inciso II, "b" — cláusulas-padrão contratuais. Dois atos da autoridade mudam o cálculo:

  • A Resolução CD/ANPD nº 19, de 23 de agosto de 2024 aprovou o Regulamento de Transferência Internacional e o texto das cláusulas-padrão. O art. 16 é taxativo: a validade da transferência amparada nelas "pressupõe a adoção integral e sem alteração do texto disponibilizado no Anexo II". O prazo para adequar contratos existentes era de doze meses da publicação — vencido em 23 de agosto de 2025. Não localizamos nenhuma resolução que o tenha prorrogado.
  • A Resolução nº 32, de 26 de janeiro de 2026 reconheceu a União Europeia como organismo que proporciona grau de proteção adequado, autorizando transferências com base no art. 33, I para todos os Estados membros, para os países do EEE e para as instituições da própria União.

Os Estados Unidos não foram reconhecidos. Não localizamos decisão de adequação para os EUA, nem reconhecimento de equivalência das cláusulas europeias. A consequência prática é direta e reordena a escolha de fornecedor: enviar documento a uma API americana exige cláusulas-padrão da ANPD adotadas integralmente, com prazo de adequação já vencido; enviar a um processador na União Europeia passou, desde janeiro de 2026, a ser amparado pelo art. 33, I — instrumento muito mais simples.

flowchart TD
  ONDE{"onde o documento é processado?"}
  ONDE -->|"São Paulo — southamerica-east1"| BR["NÃO há transferência internacional<br/>dispensa cláusulas-padrão e toda a cadeia"]
  ONDE -->|"União Europeia ou EEE"| UE["Art. 33, I<br/>Resolução nº 32, de 26/01/2026<br/>grau de proteção adequado reconhecido"]
  ONDE -->|"Estados Unidos"| US["Art. 33, II, 'b'<br/>cláusulas-padrão da Resolução nº 19/2024,<br/>adotadas integralmente e sem alteração"]
  ONDE -->|"padrão do código — us-central1"| US

  US --> PRAZO["Prazo de adequação<br/>venceu em 23/08/2025"]

  BR:::melhor
  classDef melhor stroke-width:3px

Atenção — o padrão do código cai no caminho mais caro. Sem vertexRegion explícito, o provedor usa us-central1, ou seja, Estados Unidos. Quem não souber disso caracteriza transferência internacional sem perceber, sob o instrumento cujo prazo de adequação já venceu.

A política de dados do provedor implementado

A política de dados do provedor implementado, e por que a camada importa mais que o preço.

Os termos da API Gemini, consultados em 2026-08-16, separam duas camadas com consequências opostas:

Camada gratuitaCamada pagaVertex AI
Usa o seu conteúdo para desenvolver produtos e treinar modelosSimNãoNão, por restrição contratual
Revisores humanos podem ler entrada e saídaSimNãoSó em monitoramento de abuso
Retenção declaradaNão quantificada"limited period of time", sem número publicadoCache de 24 horas, desativável; retenção zero é alcançável
Onde processaQualquer paísQualquer paísRegião escolhida, se usar endpoint regional
Instrumento contratualTermos de usoAditivo de tratamento de dadosAditivo mais termos específicos do serviço

Os próprios termos da camada gratuita contêm a frase que encerra a discussão: "Do not submit sensitive, confidential, or personal information to the Unpaid Services." Enviar documento de titular para a camada gratuita contraria o contrato do fornecedor, antes mesmo de qualquer análise de LGPD.

Há ainda duas armadilhas nesses termos que mudam a análise. A primeira: o que separa as camadas é existir cobrança ativa no projeto, e não o fato de você estar efetivamente pagando — o próprio texto diz que o acesso é considerado pago "mesmo quando oferecido sem custo", desde que a conta tenha um projeto de nuvem com faturamento ativo. A segunda, e é desconfortável: os termos estendem as regras da camada paga a todos os serviços para quem está no Espaço Econômico Europeu, na Suíça e no Reino Unido. O Brasil não está nessa lista. Um usuário brasileiro na camada gratuita tem os dados usados para treino; um europeu, não.

A configuração recomendada

A configuração recomendada, e ela resolve o problema.

Existe uma combinação que mantém o processamento dentro do Brasil e, com isso, elimina a transferência internacional — dispensando cláusulas-padrão, o prazo vencido e toda a cadeia de suboperadores:

ItemValor
CredencialserviceAccountKey — conta de serviço do Vertex AI
vertexRegionsouthamerica-east1 (São Paulo) — o padrão é us-central1, então você precisa informar
settings.modelgemini-2.5-flash, na variante de 128 mil de contexto
EndpointRegional, nunca global — endpoints globais não dão garantia de residência
Cache implícitoDesativado no projeto
Monitoramento de abusoSolicitar exceção de registro de prompt ao fornecedor

O detalhe que quase ninguém conhece: de todos os modelos Gemini, apenas o gemini-2.5-flash na variante de 128 mil de contexto tem compromisso público de processamento de aprendizado de máquina dentro do Brasil. A variante de 1 milhão do mesmo modelo não tem; o Flash-Lite não tem; o Pro não tem; nenhum modelo da geração seguinte tem (tabela oficial de residência de dados do Google Cloud, consulta em 2026-08-16). É por isso que gemini-2.5-flash é o padrão deste building block.

O que isso obriga você a fazer, e é responsabilidade de quem opera:

  1. Definir a base legal para a leitura automatizada de documento, na política de privacidade e no registro de operações de tratamento (art. 37). Para onboarding bancário, o enquadramento mais defensável costuma combinar o art. 7º, II com o art. 11, II, "a", ancorados em obrigação regulatória — o seu jurídico decide.
  2. Nunca usar a camada gratuita do provedor para documento de titular. Os termos do próprio fornecedor proíbem.
  3. Configurar região explicitamente se residência importa. O padrão do código é us-central1.
  4. Enquadrar a transferência internacional caso opte por processar fora do Brasil — hoje, isso significa cláusulas-padrão da ANPD adotadas integralmente e sem alteração.
  5. Verificar ativamente o operador. O art. 39 diz que o controlador "verificará a observância das próprias instruções" — não basta contratar, é preciso auditar. E lembre que o art. 42, § 1º, I equipara o operador ao controlador quando ele descumpre a lei, com responsabilidade solidária.
  6. Documentar a cadeia de suboperadores. A cláusula sobre transferências posteriores do Anexo II da Resolução nº 19/2024 exige autorização expressa para subcontratação e responsabiliza o importador pelas irregularidades do terceiro.

A alternativa honesta. Se a sua avaliação concluir que nem a configuração brasileira resolve, este building block não é o produto certo para documento de identidade. Use-o para documentos de menor sensibilidade — boleto, nota fiscal, extrato — e trate identidade com fornecedor nacional especializado. Vale saber que, para comprovante de renda, o mercado brasileiro largamente abandonou o OCR: as duas soluções que venceram evitam o documento, consultando dado oficial ou fazendo análise forense do arquivo. Essas recomendações estão aqui porque são a resposta certa em alguns casos, e omiti-las seria vender errado.

Verificação de destino ao baixar de URL. A origem url passa por uma verificação de segurança antes do download, e o download usa um cliente HTTP protegido. É o que impede que alguém use este building block como ponte para alcançar endereços internos da sua rede. Não desabilite essa verificação.

O que fica gravado, e por quanto tempo

O que fica gravado, e por quanto tempo.

OndeO queRetenção
S3, em extraction/{organização}/{extração}/sourceO documento inteiro, como enviadoIndefinida. Sem expurgo automático
extraction_records.rawDataTodos os campos extraídos — nome, CPF, endereço, rendaIndefinida
extraction_records.mappedDataOs mesmos dados, traduzidosIndefinida
extraction_records.sourceRefPara origem em base64, o conteúdo do arquivo inteiro fica gravado neste campo de textoIndefinida
extraction_provider_configs.credentialsCredenciais do provedor, cifradasAté a exclusão

Duas consequências práticas. A primeira: o banco de extrações é um repositório de dado pessoal, e às vezes sensível — trate-o com o mesmo controle de acesso, cifragem em repouso e política de retenção que você aplica ao cadastro de clientes. A segunda, e menos óbvia: quando a origem é content em base64, o arquivo é gravado também em texto no registro da extração, além da cópia no S3. Prefira fileId, que não tem esse efeito.

Permissões. Três permissões: DATA_EXTRACTION_READ para leitura, DATA_EXTRACTION_WRITE para criar e reprocessar extração, e DATA_EXTRACTION_ADMIN para configurar provedor, template e mapeamento. Note que DATA_EXTRACTION_READ dá acesso ao resultado da extração, que contém os dados pessoais lidos — é uma permissão sensível, e não deve ser tratada como "somente leitura, então é inofensiva". Hoje, apenas o papel ADMIN recebe essas três permissões no catálogo padrão do IAM (§15).

Exclusão lógica. Configurações e templates usam deletedAt. Registros de extração não têm exclusão lógica nem rota de exclusão — uma vez criados, permanecem. Atender a pedido de eliminação da LGPD exige expurgo manual, no banco e no S3 (§15).


15

Limitações conhecidas

LimitaçãoImpactoSituação
O processador assíncrono não está ligado nos ambientes publicadosEXTRACTION_CONSUMER_ENABLED é false por padrão e não aparece nas pilhas de staging nem de produção. Sem ele, toda extração criada fica em PENDING para sempre, e a API responde 202 normalmenteBloqueante — é a primeira coisa a verificar antes de qualquer demonstração
Só o Google Gemini está implementadoOPENAI e ANTHROPIC existem no enum, mas as classes são placeholders que recusam com "not yet implemented". A criação da configuração falha no teste de conexão, então o erro aparece cedo. O ADR-0004 declara os três como implementados e está desatualizadoRoadmap — não anuncie multi-provedor
Não há OCRO passo OCR_EXTRACT existe, muda o estado e não faz nada. Toda a leitura é feita pelo modelo de visão. PDF puramente textual é enviado como imagem, sem extração prévia de textoPor design nesta versão; o passo está reservado
Corpo limitado a 1 MBcontent em base64 só comporta arquivo de cerca de 750 KB, embora EXTRACTION_MAX_FILE_SIZE_MB permita 10 MB. Use fileId ou url para arquivos maioresPor design do middleware comum; ajustável na infraestrutura
Base64 fica gravado em texto no registroCom origem content, o arquivo inteiro é gravado em sourceRef, além da cópia no S3 — duplicando o armazenamento de dado pessoal. Prefira fileIdRoadmap
Sem exclusão de extraçãoNão há rota de exclusão nem exclusão lógica no registro. Documento e dado extraído permanecem indefinidamente; expurgo para LGPD é manual, no banco e no S3Roadmap — é a lacuna mais relevante para compliance
ExtractionWebhookSubscription não tem nenhum endpointO modelo existe no banco, com URL, segredo e eventos, e nenhuma rota o expõe. Notificação é pelo webhooks-engine, ouvindo os eventosEspecificado, não implementado
stepHistory nunca é preenchidoA coluna existe para guardar o histórico de passos com duração e erro, e nenhum código escreve nelaEspecificado, não implementado
PROCESSING nunca é atribuídoO estado existe no enum e nenhum caminho o usaResquício
Sem reentrega automática de falhaA mensagem é confirmada mesmo quando o passo falha. Falha transitória exige retry manualPor design, para não repetir custo de token em falha determinística
retry sem teto nem espera progressivaNada impede reprocessar indefinidamente uma extração que sempre falha, gastando token a cada vezRoadmap
A confiança geral é média simplesUm campo crítico ruim some na média de vários campos bons. Decida campo a campoPor design; a média é apenas indicativa
A nota de confiança é autoavaliação do modeloNão é medida calibrada. Serve para ordenar, não como probabilidadeLimitação da abordagem
Sem verificação de autenticidade e sem antifraudeO building block lê o que está escrito e acredita. Documento adulterado é lido normalmente. Não use como controle antifraudeFora de escopo — é o território de fornecedores de identidade
Sem modelo especializado por documento brasileiroHá prompt dedicado a boleto e contexto para cinco outros tipos, mas não há modelo treinado para RG ou CNH. A precisão depende do modelo de visão genéricoPor design da abordagem
A região padrão do Vertex AI é fora do BrasilSem vertexRegion explícito, o código usa us-central1. Quem não souber disso caracteriza transferência internacional sem perceber (§14)Por design do provedor; documente na sua configuração
Sem rotação da chave mestraTrocar DATA_EXTRACTION_CREDENTIAL_MASTER_KEY inutiliza todas as credenciais já cifradasRoadmap
Permissões só no papel ADMINDATA_EXTRACTION_* não está atribuída a nenhum papel intermediário no catálogo padrão do IAMConfiguração pendente
Modo de autenticação não convencional no provedor GoogleAlém de chave de API, conta de serviço e OAuth, o provedor tem um quarto caminho que usa endpoints internos do Google e um identificador de cliente montado. Não é caminho documentado publicamente pelo fornecedor e não deve ser usado em produção — prefira chave de API, conta de serviço ou Vertex AIDeve ser revisto
O extrator de templateId das rotas de mapeamento usa o texto da URLO identificador do template é obtido por correspondência sobre a URL da requisição, e não pelo roteador. É frágil a mudança de caminhoRoadmap

16

Perguntas frequentes

Criei uma extração e ela não sai de PENDING. O que houve?

O processador assíncrono não está rodando. Ele depende de EXTRACTION_CONSUMER_ENABLED=true, que é false por padrão e não está definido nas pilhas de staging nem de produção (§15). O serviço sobe normalmente sem ele e a sonda de saúde responde que está tudo bem — o único sinal é a fila parada. É a primeira coisa a verificar, antes de investigar qualquer outra hipótese.

Quais provedores de IA vocês suportam?

Um: Google Gemini, no modelo gemini-2.5-flash por padrão, com quatro formas de autenticação. OPENAI e ANTHROPIC aparecem no schema mas são placeholders — a criação da configuração falha no teste de conexão, então você descobre na hora. O ADR-0004 do projeto diz que os três estão implementados e está desatualizado; o código é a fonte de verdade.

O documento do meu cliente é enviado para fora?

Sim, a imagem completa é enviada em base64 para a API do provedor configurado — e sai do Brasil, salvo se você configurar explicitamente o contrário. O padrão do código é a região us-central1, o que caracteriza transferência internacional sob os arts. 33 a 36 da LGPD.

Existe uma configuração que resolve: conta de serviço do Vertex AI, vertexRegion igual a southamerica-east1, modelo gemini-2.5-flash na variante de 128 mil de contexto e endpoint regional. É a única combinação de modelo Gemini com compromisso público de processamento dentro do Brasil, e ela elimina a transferência internacional. A §14 traz o quadro completo e o que ainda cabe a você fazer.

O provedor de IA treina modelo com os meus documentos?

Depende da camada, e a diferença é radical. Na camada gratuita da API Gemini, os próprios termos declaram que o Google usa o conteúdo enviado para desenvolver produtos e tecnologias de aprendizado de máquina, e que revisores humanos podem ler entrada e saída. O mesmo documento instrui, literalmente: "Do not submit sensitive, confidential, or personal information to the Unpaid Services." Nunca use a camada gratuita com documento de titular — antes de qualquer análise de LGPD, isso contraria o contrato do fornecedor.

Na camada paga, os termos afirmam que o Google não usa prompts nem respostas para melhorar produtos, e processa sob aditivo de tratamento de dados. No Vertex AI, a restrição de treino é contratual e explícita. Duas ressalvas: o prazo de retenção da camada paga é descrito apenas como "limited period of time", sem número publicado; e o que separa gratuito de pago é existir faturamento ativo no projeto, não o valor da fatura. Como a credencial é sua, essa relação contratual é sua — vantagem, porque você controla o instrumento; responsabilidade, porque ninguém a assume por você.

Preciso mesmo das cláusulas-padrão da ANPD?

Só se você processar fora do Brasil e fora da União Europeia. Desde a Resolução nº 32, de 26 de janeiro de 2026, a ANPD reconhece a União Europeia como organismo com grau de proteção adequado, o que permite usar o art. 33, I — mecanismo simples, sem contrato específico. Os Estados Unidos não foram reconhecidos, então processar lá exige as cláusulas-padrão da Resolução nº 19/2024, adotadas integralmente e sem alteração, cujo prazo de adequação venceu em agosto de 2025. Processar em São Paulo, pela configuração da §14, dispensa tudo isso.

Isso serve para detectar documento falso?

Não, e é importante ser categórico. O building block lê o que está escrito e acredita. Um RG adulterado é lido normalmente, com confiança alta. Para verificação de autenticidade, biometria ou prova de vida, o mercado tem fornecedores especializados — Unico, Idwall, Caf, BigDataCorp. Este building block é complementar a eles, não substituto.

Preciso de template?

Não, mas ajuda muito. Sem template, a extração é genérica: o modelo decide o que é relevante e você recebe o que ele achou. Com template, você declara os campos, os tipos e as descrições — e as descrições são o que mais melhora a qualidade. Sem template também não há mapeamento, então mappedData fica igual a rawData.

Qual a diferença entre retry e remap?

retry reprocessa desde o início e chama o modelo de novo, gastando token. Serve quando a extração falhou. remap reaplica os mapeamentos sobre o dado já extraído, sem chamar o modelo e sem custo. Serve quando a extração deu certo e você errou a tradução. Se a dúvida é qual usar, a pergunta é: o problema está no que foi lido, ou no que foi traduzido?

Quanto custa uma extração?

O custo de IA é do provedor e é faturado direto a você, porque a credencial é sua. O que o building block faz é registrar tokensUsed e providerModel em cada extração, para que você acompanhe. O consumo depende do tamanho da imagem, do número de campos declarados no template e do modelo escolhido. O preço do building block em si está em definição.

Posso usar isto para digitalizar um acervo de documentos antigos?

Tecnicamente sim, mas é o uso errado. Não há processamento em lote, não há paralelismo configurável e cada documento é uma chamada a um modelo de visão — o que é caro para volume de digitalização em massa. Para acervo, um OCR tradicional custa uma fração. Este building block foi feito para leitura estruturada dentro de um fluxo de negócio, não para digitalização.

Como faço para apagar os dados de um titular que pediu eliminação?

Manualmente, hoje. Não há rota de exclusão de extração, não há exclusão lógica no registro e não há expurgo automático do arquivo no S3 (§15). O procedimento envolve remover as linhas de extraction_records e os objetos correspondentes em extraction/{organização}/{extração}/source. É a limitação mais relevante para quem opera sob obrigação de eliminação, e está no roadmap.


Padrão: PADRAO-DOCUMENTACAO.md · Índice: INDEX.md