Catalisa.Building Blocks
Catálogo/Financeiro/Open Finance

Open Finance

Produção

Extrato, saldo e identidade bancária do seu cliente, com o consentimento dele

23
Endpoints
6
Entidades
1
Provedores
Tenant
Escopo
3016
Porta
2026-01
Desde

Seu cliente autoriza no banco dele e você passa a ver o extrato, o saldo e a identidade bancária dentro da sua esteira — sem pedir PDF, sem print de tela e sem o cliente digitar nada que você já poderia consultar.

Para quem é
  • Financeiras e fintechs que usam extrato bancário para decidir crédito e hoje pedem PDF ao cliente
  • Plataformas de gestão financeira que precisam ler contas de vários bancos por cliente
  • Operações de cobrança e conciliação que precisam enxergar a movimentação real antes de negociar
Substitui
  • A integração ponto a ponto com a API do agregador, refeita em cada serviço
  • Tabela própria de contas, transações e identidade copiada na mão do agregador
  • Cofre caseiro para guardar as credenciais do agregador
O que não é
  • Um agregador de Open Finance — quem é participante regulado é o provedor contratado, hoje a Pluggy
  • Uma autorização do Banco Central nem uma licença de instituição de pagamento
  • Um iniciador de pagamento — este building block só lê dados, não movimenta dinheiro
  • Um motor de decisão de crédito (isso é o Decision Platform)
O que dá para fazer

24 endpoints em 6 recursos.

Explorar a API →
01

Resumo executivo

O Open Finance traz para dentro da sua aplicação o que o seu cliente autorizou o banco dele a compartilhar: quais contas ele tem, quanto tem nelas, o que entrou e saiu, e quem é o titular segundo o próprio banco. O cliente autoriza uma vez, na tela do banco dele, e a partir daí os dados chegam pela sua API em vez de chegarem por PDF anexado num e-mail.

Na prática isso resolve a etapa mais frágil de qualquer análise de crédito no Brasil: pedir comprovante de renda e extrato ao próprio candidato. Documento enviado pelo cliente pode ser editado, chega em formatos diferentes de cada banco e leva dias. O extrato via Open Finance vem do banco, categorizado, e leva minutos.

Um esclarecimento que muda tudo no material comercial: a Catalisa não é participante do Open Finance Brasil. Quem é instituição participante regulada é o provedor agregador contratado — hoje a Pluggy, cuja entidade Pluggy Brasil Instituição de Pagamento Ltda. está ativa no diretório oficial com os papéis de Dados e Pagamentos. Este building block é a camada de software que fala com esse provedor, guarda o resultado com escopo de tenant e entrega para o resto da plataforma. O enquadramento regulatório completo está na §14 e é a primeira coisa a ler antes de qualquer proposta.

Está em produção desde janeiro de 2026, no stack de produção em open-finance.bb.catalisa.app.

AtributoValor
Identificadoropen-finance
CategoriaFinanceiro
EscopoTenant (exige organizationId no token em todas as rotas autenticadas)
Porta (standalone)3016
Path alias@open-finance
Prefixo HTTP/open-finance
Schema no PostgreSQLopenfinance
StatusProdução desde 2026-01
Depende dePostgreSQL, Redis (eventos), IAM, e uma conta ativa no provedor agregador

02

O problema

negócio

O cenário. Uma financeira precisa saber se o candidato ao crédito tem renda, se ela é recorrente e se o dinheiro sai antes do fim do mês. A informação existe — está no banco do candidato. O que não existe é um caminho para chegar até ela sem passar pelo próprio candidato.

O que trava hoje.

  • O comprovante vem do interessado. Pedir extrato em PDF a quem quer o crédito é pedir a prova a quem tem interesse no resultado. Detectar adulteração é trabalho manual, caro e falho.
  • Cada banco entrega um formato. PDF, OFX, CSV, imagem escaneada. Cada um exige um parser, e o parser quebra quando o banco muda o cabeçalho do relatório.
  • A resposta demora dias, e o cliente desiste. Entre pedir o documento e recebê-lo passam-se dias. Nesse intervalo, o concorrente aprovou.
  • Integrar o agregador direto vira dívida técnica. A API do agregador tem o vocabulário dele — item, connector, execution status. Cada serviço que integra copia esse vocabulário para dentro, e trocar de fornecedor deixa de ser uma opção.
  • As credenciais do agregador ficam por aí. clientId e clientSecret acabam em variável de ambiente, iguais para todos os clientes da plataforma, sem separação e sem rotação.
  • O consentimento tem prazo e ninguém controla. A autorização vale no máximo 12 meses pela regra do Open Finance Brasil. Sem controle, o dado envelhece em silêncio e a base vira histórico de qualidade desconhecida.
flowchart LR
  D(["A informação existe — está no banco do candidato"])
  D --> C["Único caminho hoje:<br/>pedir ao próprio candidato"]
  C --> P1["Prova pedida a quem<br/>tem interesse no resultado"]
  C --> P2["PDF, OFX, CSV, imagem —<br/>um parser por banco"]
  C --> P3["Dias de espera —<br/>o concorrente aprova antes"]
  C --> P4["Integrar o agregador direto<br/>vira dívida técnica"]
  C --> P5["clientId e clientSecret<br/>em variável de ambiente"]
  C --> P6["Consentimento vence em 12 meses<br/>e ninguém controla"]

O custo de não resolver. O custo direto é o tempo entre o pedido e a decisão. O indireto é o crédito concedido com base em documento que ninguém validou. E há o custo de oportunidade de ignorar um ecossistema que já é enorme: na semana de referência de 31/07/2026, o painel oficial do Open Finance Brasil registrava 239,8 milhões de consentimentos ativos na ótica dos receptores e mais de 8,1 bilhões de chamadas de dados cadastrais e transacionais em uma única semana (Dashboard do Cidadão, dados atualizados em 13/08/2026). O comportamento de compartilhar dado bancário deixou de ser exceção.


03

Proposta de valor

negócio
AntesDepois
O candidato envia extrato em PDF, que alguém confere na mãoO candidato autoriza no banco dele e o extrato chega pela API, categorizado
Cada banco tem um formato e um parserUm formato canônico único, igual para todas as instituições
A credencial do agregador é uma só, em variável de ambienteUma credencial por organização, cifrada com AES-256-GCM no banco
Trocar de agregador é reescrever quem consomeTrocar de agregador é implementar uma interface; quem consome não muda
A conexão bancária não se liga a nadaO item se liga a uma pessoa do Customers por personId

O dado vem da fonte. O extrato é entregue pelo banco ao provedor sob consentimento do titular, não digitado nem anexado pelo interessado.

Um vocabulário só, para todas as instituições. CanonicalAccount, CanonicalTransaction e CanonicalIdentity são tipos nossos. Quem consome nunca vê o formato do provedor.

Credencial por organização, cifrada. Cada cliente da plataforma pode usar a própria conta no provedor. As credenciais são cifradas com AES-256-GCM antes de tocar o banco e nunca voltam em resposta de API.

A conexão tem dono e tem prazo. Todo item pertence a uma organização, pode ser ligado a uma pessoa e carrega consentExpiresAt — a data em que a autorização do titular deixa de valer.

flowchart LR
  ORG["Organização<br/>do token do IAM"] --> CFG["Provider config<br/>credencial cifrada"]
  CFG --> IT["Item<br/>a conexão autorizada"]
  IT -->|personId| PES["Pessoa no Customers"]
  IT --> EXP["consentExpiresAt<br/>no máximo 12 meses"]
  IT --> DADOS["Contas · transações · identidade<br/>em formato canônico"]

04

Casos de uso reais

negócio

Caso 1 — Análise de crédito sem pedir extrato ao candidato Cenário ilustrativo

Contexto

Financeira de crédito pessoal, cerca de 4 mil propostas por mês, ticket médio baixo e margem que não suporta análise manual.

A dor

A esteira parava na etapa "enviar extrato dos últimos três meses". Metade dos candidatos não enviava; da outra metade, boa parte enviava foto de tela. O tempo médio entre proposta e decisão era de três dias, e a maior parte disso era espera por documento.

A solução com o BB

A esteira chama POST /connect/token, abre o widget do provedor no navegador do candidato e ele autoriza no próprio banco. O retorno traz o identificador do item, que a esteira registra com POST /items/sync informando o personId do Customers. Em seguida, POST /items/:id/sync-data traz contas, transações e identidade de uma vez. O motor lê o extrato por GET /accounts/:accountId/transactions com filtro de período.

O resultado

A etapa de documento sai do caminho crítico. A decisão passa a depender de dado vindo do banco, e o candidato não precisa achar, baixar e anexar nada.

flowchart LR
  subgraph A["Antes — dias de espera"]
    A1["Proposta"] --> A2["Envie o extrato<br/>dos últimos 3 meses"]
    A2 --> A3["Metade não envia<br/>parte envia foto de tela"]
    A3 --> A4["Análise manual"]
    A4 --> A5["Decisão em ~3 dias"]
  end
  subgraph D["Depois — minutos"]
    D1["Proposta"] --> D2["POST /connect/token<br/>widget do provedor"]
    D2 --> D3["Candidato autoriza no banco"]
    D3 --> D4["POST /items/sync com personId"]
    D4 --> D5["POST /items/:id/sync-data"]
    D5 --> D6["GET /accounts/:accountId/transactions"]
    D6 --> D7["Decisão com dado da fonte"]
  end

Caso 2 — Renegociação de dívida com base no que a pessoa de fato tem Cenário ilustrativo

Contexto

Operação de cobrança que negocia parcelamento por telefone e por mensagem.

A dor

A proposta de parcela era chutada a partir do valor da dívida, sem qualquer noção da capacidade real de pagamento. Acordo alto demais quebra no segundo mês; acordo baixo demais deixa dinheiro na mesa.

A solução com o BB

Com o consentimento do devedor, GET /items/:itemId/accounts traz saldo e limite de crédito, e GET /accounts/:accountId/transactions traz a movimentação. A operação passa a propor parcela olhando o padrão de entrada e saída, não o tamanho da dívida.

O resultado

A proposta deixa de ser um chute. E, como cada OpenFinanceTransaction guarda categoria, nome e CNPJ do estabelecimento, dá para separar despesa recorrente de gasto eventual.

flowchart LR
  C["Consentimento do devedor"] --> S["GET /items/:itemId/accounts<br/>saldo e limite de crédito"]
  C --> T["GET /accounts/:accountId/transactions<br/>movimentação real"]
  S --> P["Capacidade de pagamento observada"]
  T --> P
  T --> R["Categoria, merchantName e merchantCnpj<br/>separam recorrente de eventual"]
  R --> P
  P --> O["Parcela proposta pelo que entra e sai,<br/>não pelo tamanho da dívida"]

Caso 3 — Verificar a titularidade da conta antes de pagar Cenário ilustrativo

Contexto

Plataforma que faz repasse a prestadores e precisa confirmar que a conta informada pertence mesmo ao prestador.

A dor

A conferência era manual: alguém comparava o CPF do cadastro com o nome digitado no campo de conta. Erro de digitação virava repasse devolvido, e má-fé virava repasse para o titular errado.

A solução com o BB

Depois de o prestador conectar a conta, GET /items/:itemId/identity devolve nome completo, tipo e número do documento, data de nascimento e endereço como o banco tem registrado. O cadastro é conferido contra a fonte, não contra o que foi digitado.

O resultado

A conferência vira automação. Divergência entre o CPF do cadastro e o do banco vira exceção tratada, em vez de repasse errado descoberto depois.

flowchart LR
  P["Prestador conecta a conta"] --> I["GET /items/:itemId/identity"]
  I --> D["Nome completo, tipo e número do documento,<br/>data de nascimento e endereço<br/>como o BANCO tem registrado"]
  D --> Q{"Confere com o cadastro?"}
  Q -->|sim| OK["Repasse liberado"]
  Q -->|não| EX["Exceção tratada<br/>antes de o dinheiro sair"]

Caso 4 — O ecossistema brasileiro chegou a uma escala que muda a conversa Referência de mercado

Contexto

O painel oficial do Open Finance Brasil registrava, na semana de 31/07/2026, 239,8 milhões de consentimentos ativos (ótica dos receptores) e 8,1 bilhões de chamadas de dados cadastrais e transacionais em uma semana; a iniciação de pagamento saltou de 79 para 134 milhões de chamadas semanais em cerca de cinco semanas (Dashboard do Cidadão).

A dor do mercado

Essa escala não é acessível a qualquer empresa. Participar do ecossistema exige ser instituição autorizada a funcionar pelo Banco Central — é o que diz o art. 1º da Resolução Conjunta nº 1, de 4 de maio de 2020. Uma empresa não autorizada não vira participante; ela entra como parceira contratada de uma participante, pelo art. 36 da mesma resolução.

Como a Catalisa endereça

Este building block assume que você é a parte não autorizada e desenha para esse cenário: você contrata um agregador que é participante regulado, guarda as credenciais dele cifradas por organização e opera sobre um formato canônico que não te amarra a esse fornecedor. Não vendemos autorização do BACEN, e não fingimos ter uma.

O resultado

O acesso ao ecossistema fica claro no contrato e no material comercial: quem responde ao regulador é o agregador, e a §14 explica exatamente onde termina a responsabilidade de cada um.

flowchart LR
  V["Você — entidade NÃO autorizada pelo BACEN"] -->|"art. 36 — contratação de parceria"| AG["Agregador — instituição de pagamento autorizada"]
  AG -->|"art. 1º — participante do Open Finance Brasil"| ECO["Ecossistema Open Finance Brasil"]
  CAT["Catalisa Open Finance"] -->|"camada de software sobre o participante"| V
  ECO --> RISK["Risco regulatório fica com o agregador"]
  V --> RISK2["Risco contratual e de continuidade fica com quem contrata"]

05

Mercado e diferenciais

negócio

Panorama. O mercado brasileiro de acesso a dados bancários é disputado por participantes locais, e isso não é acaso: participar do Open Finance Brasil exige autorização do Banco Central. Pluggy, Belvo e Klavi estão os três ativos no diretório oficial, com papéis de Dados e de Pagamentos. Os grandes nomes internacionais — Plaid, Tink (da Visa), TrueLayer e Yapily — não operam no Brasil: consulta às páginas de cobertura dos quatro em 2026-08-16 não encontrou o país em nenhuma delas, e nenhum deles publica preço em número.

Dentro do Brasil, os três agregadores vendem essencialmente a mesma coisa em formatos diferentes: acesso a dados por API, com preço a partir de um mínimo mensal. A Pluggy é a única que publica o piso — R$ 2.500/mês em Dados e R$ 500/mês em Pagamentos (pluggy.ai/precos) — e a Belvo publica o plano Launch a R$ 6.000/mês no Brasil (belvo.com/pt-br/planos-precos).

Este building block não compete com nenhum deles. Ele fica em cima de um deles. A comparação relevante para quem compra não é "Catalisa contra Pluggy", é "usar este building block sobre um agregador" contra "integrar o agregador direto" contra "virar participante você mesmo".

CritérioCatalisa Open Finance (sobre um agregador)Integrar o agregador diretoParticipação direta no Open Finance
Quem é o participante reguladoO agregador contratadoO agregador contratadoVocê, se autorizado pelo BACEN
Custo de acessoO do agregador + a plataformaO do agregadorAutorização, certificação, diretor responsável, operação
Quem podeQualquer empresaQualquer empresaSó instituição autorizada pelo BACEN
Vocabulário de dadosCanônico próprioO do fornecedor, dentro do seu códigoO do padrão Open Finance Brasil
Trocar de fornecedorImplementar uma interfaceReescrever quem consomeNão se aplica
Credencial por cliente da plataformaSim, cifrada por organizaçãoUma só, no ambienteNão se aplica
Isolamento multi-tenantNativo, pelo token do IAMVocê constróiVocê constrói
Persistência do extratoInclusa, com escopo de tenantVocê constróiVocê constrói
Ligação com o cadastro de pessoaspersonId do CustomersVocê constróiVocê constrói
Iniciação de pagamentoNão (§15)Depende do agregadorSim, com o papel adequado

Nossos diferenciais

  1. A credencial é do cliente, não da plataforma. OpenFinanceProviderConfig guarda clientId e clientSecret cifrados com AES-256-GCM por organização. Cada cliente da plataforma pode ter a própria conta no agregador — o que significa que o contrato de parceria do art. 36 é dele com o agregador, e não uma cadeia opaca de subcontratação. É difícil de copiar porque exige que a multi-tenancy chegue até a credencial externa, não só até a linha do banco.
  2. O formato canônico é a barreira contra o aprisionamento. CanonicalAccount, CanonicalTransaction, CanonicalIdentity e CanonicalConnector são tipos nossos, e a interface OpenFinanceProvider define exatamente o que um agregador precisa saber fazer. Trocar de fornecedor é uma classe nova; quem consome não muda uma linha.
  3. A conexão entra na esteira que já existe. POST /items/:id/link-person amarra o item ao cadastro do Customers, e cada sincronização publica evento no barramento. O extrato vira entrada de decisão sem integração nova.

Quando escolher o concorrente. Se a sua empresa é uma instituição autorizada pelo Banco Central e o volume justifica, participe diretamente: você elimina o custo por requisição e o intermediário, e ganha acesso ao padrão completo. Se você tem um único produto e uma única equipe consumindo os dados, integre o agregador direto — a camada canônica só se paga quando há mais de um consumidor ou quando trocar de fornecedor é uma possibilidade real. Se o que você precisa é iniciação de pagamento — Pix por Open Finance, Pix Automático, cobrança —, este building block não faz isso e a Pluggy e a Belvo fazem: vá direto a elas. E se a sua necessidade é dado de vínculo empregatício, dado fiscal ou estimativa de renda, o catálogo da Belvo é mais largo que o que expomos aqui. Este building block ganha quando há vários clientes na mesma plataforma, cada um com o próprio acesso, e o extrato precisa conversar com o resto da esteira.


06

Modelo de cobrança e ROI

negócio

Unidade de cobrança. Precificação em definição. Não há medição de uso implementada neste building block (§15). O que existe hoje é o custo repassado do agregador, que é contratado por quem vai usar.

O que dispara custo. O custo real está no agregador, não aqui:

DriverOnde pesa
Conexões ativas por organizaçãoO agregador cobra mínimo mensal e excedente por requisição
Sincronizações de dadosPOST /items/:id/sync-data percorre contas, transações e identidade — é a chamada mais cara do conjunto
Chamadas ao provedorListagem de conectores, atualização de item, teste de conexão

Comparação de custo — cenário: plataforma com 20 organizações clientes, cada uma com cerca de 500 conexões ativas e sincronização semanal. Preços consultados em 2026-08-16.

Com este building blockIntegrando o agregador diretoParticipação direta
Piso do fornecedorPluggy a partir de R$ 2.500/mês em Dados, ou Belvo a partir de R$ 6.000/mês no LaunchO mesmoZero de fornecedor
Excedente por requisiçãoDo agregador, valor não publicadoO mesmoNão se aplica
Camada de tenant, credencial cifrada e persistênciaInclusaVocê constróiVocê constrói
Custo regulatórioNenhum direto — o participante é o agregadorNenhum diretoAutorização BACEN, certificação, diretor responsável, conformidade contínua
Quem contrata o agregadorCada organização pode contratar a própriaVocê contrata uma vezNão se aplica

Estimativa para orientar conversa, não proposta comercial. O preço por requisição acima do mínimo não é publicado por nenhum dos agregadores — pergunte na negociação, porque é ele que define o custo em escala. Confira as tabelas públicas na data da sua análise.

ROI. A conta não fecha comparando com o agregador, porque o agregador continua sendo contratado nos dois cenários. Ela fecha em dois lugares. O primeiro é o processo substituído: uma etapa de "envie o extrato" que atrasa a decisão em dias e derruba parte do funil. O segundo é a camada que não precisa ser escrita — credencial cifrada por organização, formato canônico, persistência com escopo de tenant, ligação com o cadastro de pessoas e tratamento de webhook. É o tipo de código que parece pequeno na estimativa e grande no calendário.


07

Arquitetura

As camadas e o caminho da requisição

flowchart TD
  NAV["Navegador do cliente final"]
  APPCLI["Sua aplicação"]
  BANCO["Banco do cliente<br/>tela de consentimento"]

  APPCLI -->|"1 · POST /connect/token"| HONO
  APPCLI -->|"3 · POST /items/sync"| HONO
  NAV -->|"2 · abre o widget do provedor com o token"| BANCO
  BANCO -->|"autoriza"| NAV

  subgraph HONO["Hono app · basePath('/open-finance')"]
    R1["/api/v1/provider-configs"]
    R2["/api/v1/connect · /api/v1/connectors"]
    R3["/api/v1/items · /api/v1/accounts"]
    R4["/api/v1/transactions"]
    R5["/api/v1/webhooks/:providerType — público"]
  end

  HONO -->|"authMiddleware → requirePermission → requireOrganization"| SVC

  subgraph SVC["services/"]
    S1["ProviderConfigService · credencial"]
    S2["ItemService · conexão e sync"]
    S3["AccountService · TransactionService"]
    S4["IdentityService · WebhookHandler"]
  end

  SVC -->|"decryptCredentials (AES-256-GCM)"| FAB
  SVC -->|Prisma| PG["PostgreSQL<br/>schema 'openfinance' · 6 tabelas"]

  subgraph FAB["createOpenFinanceProvider()"]
    F1["PLUGGY → PluggyProvider"]
    F2["BELVO → não implementado"]
  end

  FAB -->|"mapeamento para o formato canônico"| PLU["api.pluggy.ai"]
  PLU -->|"webhook do provedor"| R5

O fluxo de consentimento, que é o coração do módulo

O consentimento não é uma caixa que o seu sistema marca: é um ato do titular, praticado na tela do banco dele. A sua aplicação nunca vê senha bancária — ela vê um token de widget na ida e um identificador de item na volta.

sequenceDiagram
  autonumber
  participant App as Sua aplicação
  participant OF as Open Finance (Catalisa)
  participant Wid as Widget do provedor
  participant Cli as Cliente (titular)
  participant Banco as Banco do cliente

  App->>OF: POST /api/v1/connect/token
  OF-->>App: accessToken
  App->>Wid: entrega o token ao front e abre o widget
  Wid->>Cli: escolhe o banco
  Cli->>Banco: AUTORIZA no ambiente do próprio banco
  Note over Cli,Banco: É aqui que o consentimento é dado — com prazo e finalidade
  Banco-->>Wid: autorização concedida
  Wid-->>App: itemId
  App->>OF: POST /api/v1/items/sync (externalItemId, personId)
  OF-->>App: item criado, com consentExpiresAt gravado
  App->>OF: POST /api/v1/items/:id/sync-data
  OF-->>App: contas, transações e identidade
  Banco-->>OF: o provedor avisa mudanças por webhook
  Note over OF: POST /api/v1/webhooks/pluggy

Atenção. O consentimento tem prazo. Pela Resolução Conjunta nº 1/2020, art. 10, § 1º, III, o prazo é "limitado a doze meses". A data vem do provedor e fica em OpenFinanceItem.consentExpiresAt. Passado o prazo, é preciso novo consentimento — a norma não prevê renovação automática (art. 10, § 2º). Ver §14 e §15.

stateDiagram-v2
  direction LR
  [*] --> SemConsentimento
  state "Sem consentimento" as SemConsentimento
  state "Consentimento concedido — consentExpiresAt gravado" as Concedido
  state "Consentimento vencido — máximo de 12 meses" as Vencido
  state "Consentimento revogado" as Revogado

  SemConsentimento --> Concedido: titular autoriza no banco
  Concedido --> Concedido: sync-data e refresh dentro do prazo
  Concedido --> Vencido: passa a data de consentExpiresAt
  Concedido --> Revogado: DELETE do item, revogação imediata pelo art. 15, § 3º
  Vencido --> Concedido: NOVO consentimento, do zero pelo connect/token
  Revogado --> Concedido: NOVO consentimento, do zero pelo connect/token

Atenção. O building block grava e devolve consentExpiresAt, mas não bloqueia a leitura de um item vencido nem emite alerta. A transição para "vencido" acontece no calendário, não no código — o controle é do seu processo (§15).

Decisões não óbvias. São seis, e cada uma tem consequência direta em segurança ou em custo.

A credencial é validada antes de ser guardada

POST /provider-configs constrói o provedor e chama testConnection() antes de cifrar e persistir. Credencial errada falha na criação, e não seis horas depois na primeira sincronização de um cliente.

flowchart LR
  A["POST /provider-configs"] --> B["Constrói o provedor"]
  B --> C["testConnection() no agregador"]
  C -->|falha| D["400 — nada é gravado"]
  C -->|sucesso| E["Cifra com AES-256-GCM"]
  E --> F["Grava no banco"]

Cifragem AES-256-GCM com formato iv:authTag:ciphertext

GCM é autenticado: adulterar o registro no banco quebra a decifragem em vez de produzir credencial silenciosamente errada. A chave mestra vem de OPENFINANCE_CREDENTIAL_MASTER_KEY, lida direto de process.env para funcionar mesmo em provedores instanciados fora do container.

O token do provedor é cacheado com folga

O PluggyAuth guarda a chave de API por 2 horas menos 5 minutos e serializa refreshes concorrentes numa única promessa. Sem isso, uma rajada de requisições dispararia N autenticações simultâneas contra o provedor.

O tenant do webhook vem do item, nunca da configuração usada para interpretar

O provedor não envia organização no evento. O handler procura o item pelo identificador externo em todas as configurações daquele provedor e usa a organização e a configuração donas do item. Evento sem referência de item é recusado, em vez de ser atribuído a uma organização arbitrária: perder um evento órfão raro é melhor que misturar dado financeiro entre clientes.

flowchart TD
  W["Webhook do provedor<br/>sem organizationId"] --> Q{"Tem referência de item?"}
  Q -->|não| REJ["400 — evento recusado"]
  Q -->|sim| B["Procura o item pelo externalId<br/>em TODAS as configs daquele provedor"]
  B --> R{"Item encontrado?"}
  R -->|não| REJ
  R -->|sim| T["Usa a organização e a config DONAS do item"]
  T --> P["Processa e grava em openfinance_webhook_events"]

O formato canônico é o contrato interno

Nenhum serviço, rota ou repositório conhece o vocabulário da Pluggy. A tradução acontece em pluggy.mapper.ts, e é o único lugar a mudar quando o provedor muda.

Exclusão do item é lógica no nosso lado e definitiva no provedor

DELETE /items/:id chama deleteItem no provedor — o que encerra a conexão lá — e depois marca deletedAt aqui. As contas, transações e identidade já sincronizadas permanecem no banco. Isso é deliberado, para não perder histórico usado em decisão já tomada, e tem consequência de LGPD que a §14 detalha.

flowchart LR
  D["DELETE /items/:id"] --> P["deleteItem no provedor<br/>conexão encerrada lá"]
  P --> L["deletedAt no item aqui<br/>exclusão lógica"]
  L --> K["Contas, transações e identidade<br/>PERMANECEM no banco"]
  K --> G["Pedido de eliminação sob LGPD<br/>exige expurgo à parte — §14"]

Monolito vs. standalone. Em monolito, os serviços vêm do container TypeDI e a chamada é direta. Em standalone — o modo de produção — o Open Finance sobe na porta registrada 3016 e é alcançado por HTTP. Em nenhum dos dois modos o building block chama outros building blocks: ele só precisa do IAM para validar o token.


08

Conceitos e modelo de dados

Glossário

TermoSignifica
Provider configA credencial de uma organização no agregador, cifrada. Uma organização pode ter várias; uma é a padrão.
ConnectorUma instituição financeira disponível no agregador. Tem identificador numérico, nome, país, logotipo e a lista de produtos que oferece.
ItemUma conexão entre um titular e uma instituição, criada quando o cliente autoriza. É a unidade do consentimento e carrega consentExpiresAt.
External IDO identificador do recurso no provedor. Guardado junto de cada registro para permitir ressincronização.
Connect tokenToken de curta duração que o front usa para abrir o widget de autorização do provedor.
ConsentimentoA autorização do titular, dada no ambiente do banco dele. Tem finalidade determinada e prazo limitado a 12 meses.
SincronizaçãoA operação que busca no provedor e grava aqui. refresh atualiza o item; sync-data percorre contas, transações e identidade.

Modelo de dados — schema openfinance no PostgreSQL.

Modelo PrismaTabelaPropósitoCampos-chave
OpenFinanceProviderConfigopenfinance_provider_configsCredencial da organização no agregadororganizationId, providerType, credentials (cifrado), isDefault, isActive, deletedAt; único (organizationId, name)
OpenFinanceItemopenfinance_itemsA conexão autorizadaorganizationId, configId, externalId, personId, status, connectorId, connectorName, lastSyncAt, consentExpiresAt, deletedAt; único (configId, externalId)
OpenFinanceAccountopenfinance_accountsConta bancária do itemaccountType, name, number, balance, creditLimit, availableCredit, bankData; único (itemId, externalId); cascata a partir do item
OpenFinanceTransactionopenfinance_transactionsLançamento da contatransactionType, description, amount, date, category, merchantName, merchantCnpj; único (accountId, externalId); cascata a partir da conta
OpenFinanceIdentityopenfinance_identitiesTitular segundo o bancofullName, documentNumber (CPF/CNPJ), birthDate, email, phoneNumber, address; um por item, cascata
OpenFinanceWebhookEventopenfinance_webhook_eventsEventos recebidos do provedoreventType, externalEventId, payload, processed, processedAt, error

Enumerações

EnumValores
OpenFinanceProviderTypePLUGGY · BELVO (declarado, não implementado — ver §15)
OpenFinanceItemStatusPENDING · LOGIN_ERROR · OUTDATED · UPDATING · UPDATED · WAITING_USER_INPUT · WAITING_USER_ACTION
OpenFinanceAccountTypeCHECKING · SAVINGS · CREDIT_CARD · INVESTMENT · LOAN
OpenFinanceTransactionTypeCREDIT · DEBIT

Como as seis tabelas se ligam

erDiagram
  OpenFinanceProviderConfig ||--o{ OpenFinanceItem : "credencial da organização"
  OpenFinanceItem ||--o{ OpenFinanceAccount : "cascata"
  OpenFinanceItem ||--o| OpenFinanceIdentity : "um por item, cascata"
  OpenFinanceAccount ||--o{ OpenFinanceTransaction : "cascata"
  OpenFinanceProviderConfig {
    string organizationId
    string providerType
    string credentials "cifrado AES-256-GCM"
    boolean isDefault
    boolean isActive
  }
  OpenFinanceItem {
    string organizationId
    string configId
    string externalId
    string personId "pessoa no Customers"
    string status
    int connectorId
    datetime lastSyncAt
    datetime consentExpiresAt
  }
  OpenFinanceAccount {
    string accountType
    string number
    decimal balance
    decimal creditLimit
    json bankData
  }
  OpenFinanceTransaction {
    string transactionType
    decimal amount
    datetime date
    string category
    string merchantCnpj
  }
  OpenFinanceIdentity {
    string fullName
    string documentNumber
    datetime birthDate
    string address
  }
  OpenFinanceWebhookEvent {
    string eventType
    string externalEventId
    json payload
    boolean processed
  }

OpenFinanceWebhookEvent aparece solto de propósito: ele guarda a carga bruta do evento e não tem chave estrangeira para o item — é por isso que ele não cai por cascata num expurgo (§14).

Estados de um item

stateDiagram-v2
  [*] --> PENDING: POST /items/sync
  state "PENDING — criado, ainda sem dado" as PENDING
  state "UPDATING — buscando" as UPDATING
  state "WAITING_USER_INPUT — precisa de MFA" as WAITING_USER_INPUT
  state "WAITING_USER_ACTION — o banco exige ação do titular" as WAITING_USER_ACTION
  state "LOGIN_ERROR — credencial recusada" as LOGIN_ERROR
  state "UPDATED — dado ok" as UPDATED
  state "OUTDATED — dado velho" as OUTDATED

  PENDING --> UPDATING
  PENDING --> WAITING_USER_INPUT
  PENDING --> WAITING_USER_ACTION
  PENDING --> LOGIN_ERROR
  UPDATING --> UPDATED
  UPDATED --> OUTDATED: passa do prazo do provedor

Atenção. Nenhuma transição é decidida aqui: o status vem do provedor e é espelhado. POST /items/:id/refresh é o que puxa o estado novo. WAITING_USER_ACTION aparece quando o banco exige ação do titular.

StatusO que significaO que fazer
PENDINGItem criado, ainda sem dadoAguarde e chame refresh
UPDATINGO provedor está buscandoAguarde
UPDATEDDado okPode chamar sync-data
OUTDATEDO provedor considera o dado velhoChame refresh e, se preciso, sync-data
WAITING_USER_INPUTPrecisa de MFALeve o titular de volta ao widget
WAITING_USER_ACTIONO banco exige ação do titularLeve o titular de volta ao widget
LOGIN_ERRORCredencial recusadaRefaça a conexão pelo connect/token

09

Referência da API

Prefixo: /open-finance. Em staging, a base é https://open-finance.bb.stg.catalisa.app; em produção, https://open-finance.bb.catalisa.app.

Todas as rotas abaixo, exceto o webhook, aplicam nesta ordem: authMiddleware → requirePermission(...) → requireOrganization.

Configuração de provedor — /open-finance/api/v1/provider-configs

MétodoRotaDescriçãoPermissão
POST/open-finance/api/v1/provider-configsCria a configuração; testa a credencial antes de cifrar e gravarOPENFINANCE_ADMIN
GET/open-finance/api/v1/provider-configsLista, paginadoOPENFINANCE_READ
GET/open-finance/api/v1/provider-configs/:idBusca uma configuraçãoOPENFINANCE_READ
PATCH/open-finance/api/v1/provider-configs/:idAtualiza nome, credenciais, ajustes ou statusOPENFINANCE_ADMIN
DELETE/open-finance/api/v1/provider-configs/:idExclusão lógica. Responde 204OPENFINANCE_ADMIN
POST/open-finance/api/v1/provider-configs/:id/set-defaultMarca como padrão da organizaçãoOPENFINANCE_ADMIN
POST/open-finance/api/v1/provider-configs/:id/testTesta a conexão com o provedorOPENFINANCE_ADMIN

Conexão — /open-finance/api/v1/connect e /connectors

MétodoRotaDescriçãoPermissão
POST/open-finance/api/v1/connect/tokenGera o token do widget de autorização. Responde 201OPENFINANCE_WRITE
GET/open-finance/api/v1/connectorsLista as instituições disponíveis. Filtros: filter[name], filter[types], filter[countries], configIdOPENFINANCE_READ
GET/open-finance/api/v1/connectors/:idDetalhe de uma instituição. O :id é numéricoOPENFINANCE_READ

Itens — /open-finance/api/v1/items

MétodoRotaDescriçãoPermissão
POST/open-finance/api/v1/items/syncRegistra o item criado no widget. Responde 201OPENFINANCE_WRITE
GET/open-finance/api/v1/itemsLista itens. Filtros: filter[status], filter[personId], filter[businessId]OPENFINANCE_READ
GET/open-finance/api/v1/items/:idBusca um itemOPENFINANCE_READ
POST/open-finance/api/v1/items/:id/refreshAtualiza estado e consentExpiresAt a partir do provedorOPENFINANCE_WRITE
POST/open-finance/api/v1/items/:id/sync-dataSincroniza contas, transações e identidadeOPENFINANCE_WRITE
POST/open-finance/api/v1/items/:id/link-personLiga o item a uma pessoa do CustomersOPENFINANCE_WRITE
DELETE/open-finance/api/v1/items/:idEncerra a conexão no provedor e faz exclusão lógica aqui. Responde 204OPENFINANCE_ADMIN

Contas, transações e identidade

MétodoRotaDescriçãoPermissão
GET/open-finance/api/v1/items/:itemId/accountsContas do item. Filtro: filter[accountType]OPENFINANCE_READ
GET/open-finance/api/v1/accounts/:idBusca uma contaOPENFINANCE_READ
GET/open-finance/api/v1/accounts/:accountId/transactionsLançamentos da conta. Filtros: filter[startDate], filter[endDate], filter[category]OPENFINANCE_READ
GET/open-finance/api/v1/transactions/:idBusca um lançamentoOPENFINANCE_READ
GET/open-finance/api/v1/items/:itemId/identityIdentidade do titular no itemOPENFINANCE_READ

Webhook do provedor

MétodoRotaDescriçãoPermissão
POST/open-finance/api/v1/webhooks/:providerTypeRecebe eventos do agregador. :providerType é pluggy (aceito sem diferenciar maiúsculas)Nenhuma — rota pública, sem authMiddleware e sem requireOrganization

Saúde

MétodoRotaDescrição
GET/open-finance/healthNome do serviço e versão

Paginação. As listagens usam page[number] e page[size]. O padrão é 20, exceto em transações, onde é 50, e em conectores, onde é 50.


POST /open-finance/api/v1/provider-configs

Cria a credencial da organização no agregador. Aceita o corpo direto ou embrulhado em data.attributes.

Request

json
{
  "name": "Pluggy Produção",
  "providerType": "PLUGGY",
  "credentials": {
    "clientId": "...",
    "clientSecret": "..."
  },
  "isDefault": true,
  "isActive": true
}
{
  "name": "Pluggy Produção",
  "providerType": "PLUGGY",
  "credentials": {
    "clientId": "...",
    "clientSecret": "..."
  },
  "isDefault": true,
  "isActive": true
}
CampoTipoObrigatórioDescrição
namestring (1 a 100)SimÚnico dentro da organização
providerTypePLUGGY | BELVOSimO schema aceita os dois; só PLUGGY funciona (§15)
credentialsobject de stringsSimPara a Pluggy: clientId e clientSecret. Cifrado antes de gravar
isDefaultbooleanNãoUsado quando a requisição não informa configId
isActivebooleanNãoPadrão true
settingsobjectNãoAjustes específicos do provedor

Resposta 201 — o registro criado. As credenciais nunca voltam, em nenhuma resposta.

Erros

StatusQuando
400Corpo reprovado no Zod; credenciais recusadas pelo provedor no teste de conexão; providerType sem implementação
403Falta OPENFINANCE_ADMIN ou falta organizationId no token
409Já existe configuração com esse nome na organização

POST /open-finance/api/v1/connect/token

Gera o token que o seu front usa para abrir o widget de autorização do provedor. É o passo 1 do fluxo de consentimento.

Request

json
{
  "configId": "e0d1c2b3-...",
  "webhookUrl": "https://open-finance.bb.catalisa.app/open-finance/api/v1/webhooks/pluggy",
  "clientUserId": "b1000000-0000-0000-0000-000000000001"
}
{
  "configId": "e0d1c2b3-...",
  "webhookUrl": "https://open-finance.bb.catalisa.app/open-finance/api/v1/webhooks/pluggy",
  "clientUserId": "b1000000-0000-0000-0000-000000000001"
}
CampoTipoObrigatórioDescrição
configIdstring (UUID)NãoOmitido, usa a configuração padrão da organização
webhookUrlstring (URL)NãoPara onde o provedor avisa as mudanças deste item
clientUserIdstringNãoRastreia quem iniciou a conexão. Omitido, usa o sub do token

Resposta 201

json
{
  "data": {
    "type": "openfinance-connect-token",
    "attributes": { "accessToken": "...", "expiresAt": null }
  }
}
{
  "data": {
    "type": "openfinance-connect-token",
    "attributes": { "accessToken": "...", "expiresAt": null }
  }
}

expiresAt volta nulo porque o provedor não devolve a data. Os tokens de conexão da Pluggy são de curta duração — trate como válido por poucos minutos e gere um novo a cada abertura do widget, em vez de reaproveitar.


POST /open-finance/api/v1/items/sync

Passo 6 do fluxo: registra aqui o item que o widget criou no provedor. Idempotente por (configId, externalItemId) — chamar de novo atualiza em vez de duplicar.

Request

json
{
  "configId": "e0d1c2b3-...",
  "externalItemId": "a1b2c3d4-...",
  "personId": "f5e4d3c2-...",
  "businessId": "proposta-88213",
  "metadata": { "origem": "esteira-credito" }
}
{
  "configId": "e0d1c2b3-...",
  "externalItemId": "a1b2c3d4-...",
  "personId": "f5e4d3c2-...",
  "businessId": "proposta-88213",
  "metadata": { "origem": "esteira-credito" }
}
CampoTipoObrigatórioDescrição
externalItemIdstringSimO identificador do item devolvido pelo widget
configIdstring (UUID)NãoOmitido, usa a configuração padrão
personIdstring (UUID)NãoPessoa no Customers
businessIdstringNãoSeu identificador de negócio
metadataobjectNãoCampo livre

Resposta 201 — o item, já com status, connectorName, lastSyncAt e consentExpiresAt.

Depois disso, chame POST /items/:id/sync-data para trazer contas, transações e identidade. O sync sozinho não traz dado bancário.


GET /open-finance/api/v1/accounts/:accountId/transactions

Query string

ParâmetroDescrição
page[number]Página, padrão 1
page[size]Tamanho, padrão 50
filter[startDate]Data inicial
filter[endDate]Data final
filter[category]Categoria atribuída pelo provedor

Resposta 200

json
{
  "data": [
    {
      "type": "openfinance-transaction",
      "id": "9f8e7d6c-...",
      "attributes": {
        "transactionType": "DEBIT",
        "description": "PIX ENVIADO",
        "amount": -150.0,
        "currencyCode": "BRL",
        "date": "2026-08-14T00:00:00.000Z",
        "category": "Transfers",
        "merchantName": "Mercado Exemplo",
        "merchantCnpj": "00000000000191"
      }
    }
  ],
  "meta": { "totalItems": 45, "totalPages": 1, "currentPage": 1, "pageSize": 50 }
}
{
  "data": [
    {
      "type": "openfinance-transaction",
      "id": "9f8e7d6c-...",
      "attributes": {
        "transactionType": "DEBIT",
        "description": "PIX ENVIADO",
        "amount": -150.0,
        "currencyCode": "BRL",
        "date": "2026-08-14T00:00:00.000Z",
        "category": "Transfers",
        "merchantName": "Mercado Exemplo",
        "merchantCnpj": "00000000000191"
      }
    }
  ],
  "meta": { "totalItems": 45, "totalPages": 1, "currentPage": 1, "pageSize": 50 }
}

Atenção ao campo links.self das respostas. Ele é montado como /api/v1/open-finance/..., com os segmentos invertidos em relação ao caminho real, que é /open-finance/api/v1/.... Não navegue pelos links — monte as URLs a partir desta seção. É um defeito conhecido, registrado na §15.


10

Início rápido

Do zero ao primeiro extrato, em staging. Este caminho exige uma conta ativa no agregador — sem credencial válida, o passo 2 falha por design.

flowchart LR
  P1["1 · IAM<br/>token"] --> P2["2 · provider-configs<br/>credencial do agregador"]
  P2 --> P3["3 · connectors<br/>quais bancos existem"]
  P3 --> P4["4 · connect/token<br/>o consentimento começa"]
  P4 --> P5["5 · items/sync + sync-data<br/>registra e traz os dados"]
  P5 --> P6["6 · accounts/:id/transactions<br/>o extrato"]
  P6 --> P7["7 · consentExpiresAt<br/>anote o vencimento"]

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://open-finance.bb.stg.catalisa.app
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://open-finance.bb.stg.catalisa.app

2. Cadastrar a credencial do agregador

bash
CONFIG=$(curl -s -X POST "$BASE/open-finance/api/v1/provider-configs" \
  -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{
    "name": "Pluggy Sandbox",
    "providerType": "PLUGGY",
    "credentials": {"clientId":"<seu-client-id>","clientSecret":"<seu-client-secret>"},
    "isDefault": true
  }')

CONFIG_ID=$(echo "$CONFIG" | jq -r '.data.id')
CONFIG=$(curl -s -X POST "$BASE/open-finance/api/v1/provider-configs" \
  -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{
    "name": "Pluggy Sandbox",
    "providerType": "PLUGGY",
    "credentials": {"clientId":"<seu-client-id>","clientSecret":"<seu-client-secret>"},
    "isDefault": true
  }')

CONFIG_ID=$(echo "$CONFIG" | jq -r '.data.id')

Se a credencial estiver errada, esta chamada responde 400 agora — a conexão é testada antes de a configuração ser gravada.

3. Ver quais instituições estão disponíveis

bash
curl -s "$BASE/open-finance/api/v1/connectors?page[size]=5" \
  -H "Authorization: Bearer $TOKEN" \
  | jq '.data[] | {id, nome: .attributes.name, mfa: .attributes.hasMFA}'
curl -s "$BASE/open-finance/api/v1/connectors?page[size]=5" \
  -H "Authorization: Bearer $TOKEN" \
  | jq '.data[] | {id, nome: .attributes.name, mfa: .attributes.hasMFA}'

4. Gerar o token do widget — o consentimento começa aqui

bash
curl -s -X POST "$BASE/open-finance/api/v1/connect/token" \
  -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d "{\"configId\":\"$CONFIG_ID\"}" | jq -r '.data.attributes.accessToken'
curl -s -X POST "$BASE/open-finance/api/v1/connect/token" \
  -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d "{\"configId\":\"$CONFIG_ID\"}" | jq -r '.data.attributes.accessToken'

Esse token vai para o seu front, que abre o widget do provedor. O cliente escolhe o banco e autoriza no ambiente do próprio banco. Nenhuma credencial bancária passa pela sua aplicação nem pela nossa. Ao final, o widget devolve o itemId.

5. Registrar o item e trazer os dados

bash
ITEM=$(curl -s -X POST "$BASE/open-finance/api/v1/items/sync" \
  -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d "{\"configId\":\"$CONFIG_ID\",\"externalItemId\":\"<itemId-do-widget>\"}")

ITEM_ID=$(echo "$ITEM" | jq -r '.data.id')
echo "$ITEM" | jq '.data.attributes | {status, connectorName, consentExpiresAt}'

curl -s -X POST "$BASE/open-finance/api/v1/items/$ITEM_ID/sync-data" \
  -H "Authorization: Bearer $TOKEN" | jq '.data.attributes.lastSyncAt'
ITEM=$(curl -s -X POST "$BASE/open-finance/api/v1/items/sync" \
  -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d "{\"configId\":\"$CONFIG_ID\",\"externalItemId\":\"<itemId-do-widget>\"}")

ITEM_ID=$(echo "$ITEM" | jq -r '.data.id')
echo "$ITEM" | jq '.data.attributes | {status, connectorName, consentExpiresAt}'

curl -s -X POST "$BASE/open-finance/api/v1/items/$ITEM_ID/sync-data" \
  -H "Authorization: Bearer $TOKEN" | jq '.data.attributes.lastSyncAt'

6. Ler o extrato

bash
ACCOUNT_ID=$(curl -s "$BASE/open-finance/api/v1/items/$ITEM_ID/accounts" \
  -H "Authorization: Bearer $TOKEN" | jq -r '.data[0].id')

curl -s "$BASE/open-finance/api/v1/accounts/$ACCOUNT_ID/transactions?page[size]=10" \
  -H "Authorization: Bearer $TOKEN" | jq '.data[] | .attributes | {date, description, amount}'
ACCOUNT_ID=$(curl -s "$BASE/open-finance/api/v1/items/$ITEM_ID/accounts" \
  -H "Authorization: Bearer $TOKEN" | jq -r '.data[0].id')

curl -s "$BASE/open-finance/api/v1/accounts/$ACCOUNT_ID/transactions?page[size]=10" \
  -H "Authorization: Bearer $TOKEN" | jq '.data[] | .attributes | {date, description, amount}'

7. Anotar quando o consentimento vence

bash
curl -s "$BASE/open-finance/api/v1/items/$ITEM_ID" \
  -H "Authorization: Bearer $TOKEN" | jq '.data.attributes.consentExpiresAt'
curl -s "$BASE/open-finance/api/v1/items/$ITEM_ID" \
  -H "Authorization: Bearer $TOKEN" | jq '.data.attributes.consentExpiresAt'

Guarde essa data no seu lado e trate o vencimento. O building block não bloqueia leitura de item com consentimento vencido (§15).

Credenciais de staging, conforme AMBIENTES.md. Nunca cole clientSecret de produção em documentação ou script — guarde com SOPS (SECRETS-SOPS-REFERENCE.md).

Os comandos acima não foram executados durante a redação deste documento; valide no seu ambiente antes de copiar para um runbook.


11

Receitas

Manter o extrato atualizado sem estourar o custo do agregador

Objetivo: dado fresco o suficiente para decidir, sem pagar por sincronização desnecessária.

flowchart TD
  Q(["Preciso de dado atual"])
  Q --> W{"O provedor já avisou<br/>por webhook?"}
  W -->|sim| SD["POST /items/:id/sync-data<br/>caro, mas justificado"]
  W -->|não| N{"Preciso de transação nova<br/>ou só do estado do item?"}
  N -->|"só o estado"| RF["POST /items/:id/refresh<br/>barato"]
  N -->|"transação nova"| SD
  RF --> ST["status · lastSyncAt · consentExpiresAt"]
  SD --> DT["contas · transações · identidade"]

1. O barato — só atualiza estado e consentExpiresAt

bash
curl -s -X POST "$BASE/open-finance/api/v1/items/$ITEM_ID/refresh" \
  -H "Authorization: Bearer $TOKEN" | jq '.data.attributes | {status, lastSyncAt, consentExpiresAt}'
curl -s -X POST "$BASE/open-finance/api/v1/items/$ITEM_ID/refresh" \
  -H "Authorization: Bearer $TOKEN" | jq '.data.attributes | {status, lastSyncAt, consentExpiresAt}'

Resposta esperada: um objeto com o status vindo do provedor, o lastSyncAt recém-atualizado e a data do consentimento. O formato é este — os valores abaixo são ilustrativos, porque dependem do seu item:

json
{
  "status": "UPDATED",
  "lastSyncAt": "2026-08-16T12:04:11.000Z",
  "consentExpiresAt": "2027-02-10T00:00:00.000Z"
}
{
  "status": "UPDATED",
  "lastSyncAt": "2026-08-16T12:04:11.000Z",
  "consentExpiresAt": "2027-02-10T00:00:00.000Z"
}

2. O caro — percorre contas, transações e identidade

bash
curl -s -X POST "$BASE/open-finance/api/v1/items/$ITEM_ID/sync-data" \
  -H "Authorization: Bearer $TOKEN"
curl -s -X POST "$BASE/open-finance/api/v1/items/$ITEM_ID/sync-data" \
  -H "Authorization: Bearer $TOKEN"

Resposta esperada: o item com lastSyncAt novo — e, a partir daí, as contas e transações já disponíveis nas rotas de leitura.

Armadilhas.

  • refresh não traz transação nova. Ele atualiza o item. Quem traz dado é o sync-data.
  • sync-data percorre todas as contas e, para cada uma, uma página de até 100 transações. Em item com muitas contas, é a chamada mais cara do conjunto — e o agregador cobra por requisição acima do mínimo mensal.
  • Prefira reagir ao webhook transactions.updated a sincronizar por relógio. Sincronizar de hora em hora em milhares de itens é a forma mais rápida de descobrir o custo marginal do seu contrato.
  • A gravação é do tipo insere-ou-atualiza por (accountId, externalId), então sincronizar de novo não duplica lançamento.

Cadastrar o webhook do provedor e conferir que ele chega

Objetivo: parar de perguntar ao provedor e passar a ser avisado.

Aponte o webhook do agregador para:

texto
https://open-finance.bb.catalisa.app/open-finance/api/v1/webhooks/pluggy
https://open-finance.bb.catalisa.app/open-finance/api/v1/webhooks/pluggy

Você também pode informar webhookUrl na criação do token de conexão, para que o provedor avise sobre aquele item específico.

Depois de um evento chegar, confirme que ele foi processado:

sql
SELECT event_type, processed, processed_at, error
FROM openfinance.openfinance_webhook_events
ORDER BY created_at DESC LIMIT 10;
SELECT event_type, processed, processed_at, error
FROM openfinance.openfinance_webhook_events
ORDER BY created_at DESC LIMIT 10;

Armadilhas.

  • Evento sem referência de item é recusado com 400. É proposital: sem o item não há como saber a qual organização o evento pertence, e atribuir a uma configuração arbitrária misturaria dado financeiro entre clientes. O provedor reenvia, e o que sobra é reconciliado na próxima sincronização.
  • Item desconhecido também é recusado. Se o webhook chegar antes de você ter chamado POST /items/sync, ele não tem onde pousar. Registre o item logo após o widget fechar.
  • A rota é pública e o caminho contém o tipo do provedor. Trate a URL como endereço operacional e monitore o volume.

Ligar a conexão bancária ao cadastro de pessoas

bash
curl -s -X POST "$BASE/open-finance/api/v1/items/$ITEM_ID/link-person" \
  -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{"personId":"f5e4d3c2-..."}'

# depois, listar tudo que aquela pessoa conectou
curl -s "$BASE/open-finance/api/v1/items?filter[personId]=f5e4d3c2-..." \
  -H "Authorization: Bearer $TOKEN" | jq '.data[] | .attributes.connectorName'
curl -s -X POST "$BASE/open-finance/api/v1/items/$ITEM_ID/link-person" \
  -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{"personId":"f5e4d3c2-..."}'

# depois, listar tudo que aquela pessoa conectou
curl -s "$BASE/open-finance/api/v1/items?filter[personId]=f5e4d3c2-..." \
  -H "Authorization: Bearer $TOKEN" | jq '.data[] | .attributes.connectorName'

Armadilhas. O personId também pode ser informado já no POST /items/sync, o que evita uma chamada. E vale conferir a identidade: GET /items/:itemId/identity traz o documento como o banco tem registrado, e divergência com o cadastro é exceção a tratar, não detalhe a ignorar.

Rotacionar a credencial do agregador sem derrubar a operação

bash
curl -s -X PATCH "$BASE/open-finance/api/v1/provider-configs/$CONFIG_ID" \
  -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{"credentials":{"clientId":"<novo>","clientSecret":"<novo>"}}'

curl -s -X POST "$BASE/open-finance/api/v1/provider-configs/$CONFIG_ID/test" \
  -H "Authorization: Bearer $TOKEN" | jq
curl -s -X PATCH "$BASE/open-finance/api/v1/provider-configs/$CONFIG_ID" \
  -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{"credentials":{"clientId":"<novo>","clientSecret":"<novo>"}}'

curl -s -X POST "$BASE/open-finance/api/v1/provider-configs/$CONFIG_ID/test" \
  -H "Authorization: Bearer $TOKEN" | jq

Armadilhas.

  • A credencial antiga é substituída na hora. Combine a janela com quem administra a conta no agregador.
  • Rotacionar a chave mestra OPENFINANCE_CREDENTIAL_MASTER_KEY é outra coisa e não está automatizado: todas as configurações existentes ficam indecifráveis. Não troque essa variável sem um plano de recadastro.
  • Uma alternativa a rotacionar é criar uma configuração nova, testá-la, marcá-la como padrão com set-default e só então desativar a antiga. Itens existentes continuam apontando para a configuração antiga pelo configId.

Encerrar uma conexão a pedido do titular

bash
curl -s -o /dev/null -w "%{http_code}\n" -X DELETE \
  "$BASE/open-finance/api/v1/items/$ITEM_ID" \
  -H "Authorization: Bearer $TOKEN"
# 204
curl -s -o /dev/null -w "%{http_code}\n" -X DELETE \
  "$BASE/open-finance/api/v1/items/$ITEM_ID" \
  -H "Authorization: Bearer $TOKEN"
# 204

Armadilhas — leia com atenção. Essa chamada encerra a conexão no provedor e marca deletedAt no item aqui. Ela não apaga as contas, transações e identidade já sincronizadas: esses registros continuam no banco. Se o pedido do titular for de eliminação de dados, e não apenas de revogação de acesso, o expurgo precisa ser feito à parte. Veja a §14.


12

Integração com outros building blocks

Building blockComo se relacionaObrigatório
IAMEmite o token; exige OPENFINANCE_READ/WRITE/ADMIN e organizationIdSim
CustomersO item se liga a uma pessoa por personId, e a identidade bancária confere o cadastroNão, mas é o uso natural
Decision PlatformConsome extrato e saldo como entrada de regra de decisãoNão
Pricing EngineUsa o comportamento financeiro como insumo de precificação por riscoNão
Webhooks EngineRepassa os eventos openfinance.* ao sistema do clienteNão
Audit TrailRegistra quem consultou dado bancário — recomendado, dada a sensibilidadeNão
Data StoreGuarda o estado da esteira que consome o extratoNão

Eventos publicados

EventoQuando
openfinance.provider_config.created / .updated / .deletedCiclo de vida da credencial
openfinance.item.syncedItem registrado a partir do widget
openfinance.item.refreshedEstado do item atualizado
openfinance.item.person_linkedItem ligado a uma pessoa
openfinance.item.deletedConexão encerrada
openfinance.data.syncedContas, transações e identidade sincronizadas
openfinance.webhook.<tipo>Evento recebido do provedor, repassado ao barramento
flowchart TD
  CLI["Seu cliente"] -->|"autoriza no banco"| AGG["Agregador (Pluggy)"]
  AGG -->|"dados sob consentimento"| OF["Open Finance<br/>contas · transações · identidade"]
  OF -->|personId| CUS["Customers"]
  OF -->|"openfinance.data.synced"| WHE["Webhooks Engine"]
  WHE --> SIS["Sistema do cliente"]
  OF -->|"extrato e saldo"| DP["Decision Platform<br/>aprova ou recusa"]
  DP --> PE["Pricing Engine<br/>preço por risco"]

O argumento comercial está na segunda metade do diagrama: o extrato não para numa tela de consulta. Ele entra na decisão e no preço, com a mesma identidade e a mesma organização do começo ao fim — e é isso que um agregador, sozinho, não entrega.


13

Configuração e operação

Variáveis de ambiente

VariávelDescriçãoObrigatóriaPadrão
OPENFINANCE_CREDENTIAL_MASTER_KEYChave mestra da cifragem das credenciais. 64 caracteres hexadecimais (32 bytes). Gere com openssl rand -hex 32Sim, para usar este módulo—
DATABASE_URLPostgreSQL, schema openfinanceSim—
REDIS_URLPublicação dos eventos openfinance.*Sim—
MODULE_IAM_URLEndereço do IAM em standaloneSim—
PORTPorta no modo standaloneNão3000 no main.ts; 3016 é a porta registrada em DEFAULT_MODULE_PORTS
PROVIDER_WEBHOOK_SIGNATURE_ENFORCEMENToff ou on. Controla o comportamento quando um provedor não oferece verificação de assinaturaNãooff

A chave mestra não é rotacionável automaticamente. Trocá-la torna todas as configurações existentes indecifráveis. Guarde com SOPS e trate como segredo de longo prazo.

As credenciais do agregador não são variável de ambiente: elas vivem cifradas no banco, por organização, e entram pela API.

Dependências de infraestrutura

DependênciaPara quê
PostgreSQL, schema openfinanceAs seis tabelas
RedisPublicação de eventos
Saída para a internetChamadas ao agregador (https://api.pluggy.ai por padrão)
URL pública alcançávelRecepção do webhook do agregador

Limites

LimiteValor
Nome da configuração1 a 100 caracteres, único por organização
Página padrão em listagens20 itens
Página padrão em transações50 itens
Página padrão em conectores50 itens
Transações por conta em sync-data100 por conta e por execução
Cache do token do agregador2 horas menos 5 minutos
Prazo máximo do consentimento12 meses, por norma — o valor real vem do provedor

Catálogo de erros

StatusCódigoSignificaO que fazer
400VALIDATIONCorpo reprovado no Zod; credenciais recusadas no teste; providerType sem implementação; webhook sem referência de itemLeia a mensagem — ela distingue os casos
400—connectorId não numérico na rota de conectorUse o identificador numérico do conector
401UNAUTHORIZEDToken ausente ou inválidoRenove no IAM
403FORBIDDENFalta a permissão de Open Finance, ou falta organizationId no tokenAutentique informando a organização
404NOT_FOUNDRecurso inexistente, excluído logicamente ou de outra organizaçãoConfira o identificador e o tenant
409CONFLICTJá existe configuração com esse nomeEscolha outro nome
500INTERNALFalha na chamada ao agregadorVeja o log; use POST /provider-configs/:id/test para isolar

Observabilidade.

  • GET /open-finance/health devolve nome e versão. Não testa o agregador — para isso, POST /provider-configs/:id/test.
  • Todo evento recebido do provedor é gravado em openfinance_webhook_events com processed, processedAt e error. É a trilha para responder "esse aviso chegou?".
  • OpenFinanceItem.lastSyncAt e status respondem "esse dado está fresco?". status = OUTDATED significa que o provedor considera o dado velho.
  • Nenhuma credencial aparece em log. Os erros de autenticação com o agregador registram o código de status, não o segredo.

14

Segurança e compliance

Quem é o participante regulado — leia antes de vender

A Catalisa não é instituição participante do Open Finance Brasil, e este building block não a torna uma. O art. 1º da Resolução Conjunta nº 1, de 4 de maio de 2020 é explícito: a implementação do Sistema Financeiro Aberto cabe a "instituições financeiras, instituições de pagamento e demais instituições autorizadas a funcionar pelo Banco Central do Brasil".

Quem é participante, no arranjo deste módulo, é o agregador contratado. A entidade Pluggy Brasil Instituição de Pagamento Ltda. (CNPJ 37.943.755/0001-30) consta como ativa no diretório oficial de participantes, com os papéis de Dados e de Pagamentos.

Uma empresa não autorizada que consome dados via API de um agregador se enquadra no art. 36 — Da Contratação de Parceria, que admite "a contratação de parceria por parte das instituições de que trata o art. 1º com entidades não autorizadas a funcionar pelo Banco Central do Brasil". Disso decorrem consequências concretas:

O que a norma dizConsequência prática
Art. 36, § 1º: o compartilhamento "pressupõe prévio e expresso consentimento do cliente"O consentimento é condição, não formalidade
Art. 36, § 5º, I: é vedada a parceria "entre instituições autorizadas a funcionar pelo Banco Central"Se a sua empresa é autorizada pelo BACEN, essa via não serve — você precisa participar diretamente
Art. 36, § 5º, II: é vedado ao parceiro "atuar em nome da instituição contratante"O parceiro não é preposto nem correspondente do agregador
Art. 36, § 6º e art. 37A contratação exige parecer do diretor responsável do participante e diligência documentada sobre o parceiro
Art. 52O Banco Central "poderá vetar ou impor restrições ao compartilhamento de que trata o art. 36"

Em resumo: o risco regulatório fica com o agregador; o risco contratual e de continuidade fica com quem contrata. Se o Banco Central vetar um compartilhamento, quem perde o acesso é você. O material comercial pode dizer "opere sobre o Open Finance Brasil por meio de um participante regulado". Não pode dizer "somos participantes do Open Finance".

Um segundo ponto de honestidade técnica: os agregadores brasileiros oferecem conexão via Open Finance e também acesso direto a instituições fora do escopo regulado. A Pluggy anuncia "Open Finance + acesso direto" (pluggy.ai). Nem toda conexão feita por este building block é, portanto, uma conexão de Open Finance regulado. O enquadramento de cada conector precisa ser confirmado com o agregador antes de ser apresentado como tal.

Consentimento: prazo, renovação e revogação

O consentimento é dado pelo titular no ambiente do banco dele. Nenhuma credencial bancária passa pela sua aplicação nem pela nossa: o widget do provedor abre com um token de curta duração e a autenticação acontece do lado da instituição.

RegraO que diz a normaComo aparece aqui
PrazoArt. 10, § 1º, III: prazo "compatível com as finalidades", limitado a doze mesesO valor vem do provedor e é gravado em OpenFinanceItem.consentExpiresAt
FinalidadeArt. 10, § 1º, II: o consentimento se refere a finalidades determinadasDefinida no seu contrato com o titular, fora deste módulo
RenovaçãoArt. 10, § 2º: alterar finalidade, prazo, instituição ou escopo exige novo consentimentoNão há renovação automática. Ao vencer, o fluxo recomeça no POST /connect/token
Como não obterArt. 10, § 3º: vedado por contrato de adesão, com opção pré-marcada, ou de forma presumidaA coleta é do widget do provedor; a sua interface não deve pré-marcar nada
RevogaçãoArt. 15: a qualquer tempo, pelo mesmo canal em que foi concedidoDELETE /items/:id encerra a conexão no provedor
Prazo da revogaçãoArt. 15, § 3º: imediata para compartilhamento de dados; até 1 dia para iniciação de pagamentoO encerramento no provedor é síncrono na chamada
VedaçãoArt. 15, § 2º: é vedado ao transmissor propor a revogação, salvo suspeita de fraudeRegra do participante, não deste módulo

Duas limitações que precisam estar claras. Primeiro: o vencimento do consentimento não é obrigado por este building block. consentExpiresAt é gravado e devolvido, mas nenhuma rotina bloqueia a leitura de um item vencido, e nenhum alerta é emitido. Guarde essa data do seu lado e trate o vencimento no seu processo. Segundo: revogar não apaga. O DELETE encerra a conexão no provedor e faz exclusão lógica do item, mas contas, transações e identidade já sincronizadas permanecem no banco. Isso é deliberado, para não perder o insumo de uma decisão já tomada — mas significa que um pedido de eliminação sob a LGPD exige expurgo à parte.

O que é armazenado, e por quanto tempo

Este é o building block que guarda o dado mais sensível do catálogo. O inventário é curto e vale conhecê-lo inteiro:

TabelaO que guardaSensibilidade
openfinance_identitiesNome completo, CPF ou CNPJ, data de nascimento, e-mail, telefone e endereço, como o banco registraDado pessoal direto
openfinance_accountsTipo de conta, número, saldo, limite e crédito disponível, código do banco e agênciaDado financeiro
openfinance_transactionsDescrição, valor, data, categoria, nome e CNPJ do estabelecimentoDado financeiro comportamental — permite inferir hábitos, saúde, filiação e localização
openfinance_itemsA conexão, o titular ligado e o prazo do consentimentoMetadado de consentimento
openfinance_provider_configsCredenciais do agregador, cifradas com AES-256-GCMSegredo de acesso
openfinance_webhook_eventsCarga bruta do evento do provedorPode conter fragmentos dos dados acima

Retenção: não há política automática. Nada expira, nada é apagado por rotina. Os registros permanecem enquanto a linha existir no banco. Para atender a um pedido de eliminação hoje, o caminho é manual: apagar as linhas de openfinance_items do titular — openfinance_accounts, openfinance_transactions e openfinance_identities caem por cascata — e limpar as cargas correspondentes em openfinance_webhook_events, que não cai por cascata. Defina essa política antes de entrar em produção com dado real.

Isolamento entre tenants

O organizationId vem do token, nunca do corpo. Todas as 22 rotas autenticadas aplicam requireOrganization. Os acessos são verificados assim:

  • Configurações e itens comparam organizationId da linha com o do token e devolvem 404 quando difere — não 403, para não confirmar que o recurso existe.
  • Contas, transações e identidade não guardam organizationId próprio: a verificação sobe pela cadeia até o item, e o item pertence à organização. O método getByIdWithOrgValidation existe exatamente para isso.
  • O webhook é a exceção que exigiu cuidado especial: o provedor não envia organização no evento. O handler resolve o tenant a partir do item referenciado, procurando em todas as configurações daquele tipo de provedor, e recusa o evento quando não há item. Atribuir a organização a partir da configuração usada para interpretar o payload misturaria dado financeiro entre clientes.

Credenciais e criptografia

As credenciais do agregador são cifradas com AES-256-GCM antes de tocar o banco, no formato iv:authTag:ciphertext com vetor de inicialização de 12 bytes gerado por chamada. GCM é modo autenticado: adulterar o registro quebra a decifragem em vez de produzir credencial silenciosamente errada. A chave mestra vem de OPENFINANCE_CREDENTIAL_MASTER_KEY, validada como 64 caracteres hexadecimais. As credenciais nunca voltam em resposta de API e nunca aparecem em log.

Webhook do provedor

A rota POST /api/v1/webhooks/:providerType é pública por necessidade — quem chama é o agregador. Duas coisas importam aqui. A primeira é que a Pluggy não oferece esquema de assinatura de webhook; a recomendação do próprio provedor é restringir por endereço de origem e usar URL única e difícil de adivinhar. A segunda é que o comportamento da plataforma diante de um provedor sem assinatura é controlado por PROVIDER_WEBHOOK_SIGNATURE_ENFORCEMENT (§13). Combine URL específica por ambiente com restrição de origem na borda.

Autorização

PermissãoDá acesso a
OPENFINANCE_READLeitura de configurações, conectores, itens, contas, transações e identidade
OPENFINANCE_WRITEToken de conexão, registro e atualização de item, sincronização, ligação com pessoa
OPENFINANCE_ADMINCiclo de vida da credencial do agregador e exclusão de item

Dada a sensibilidade, conceda OPENFINANCE_READ com o mesmo critério com que se concede acesso a extrato bancário — porque é exatamente isso —, e registre os acessos com o Audit Trail.


15

Limitações conhecidas

LimitaçãoImpactoSituação
Só a Pluggy está implementadaOpenFinanceProviderType declara PLUGGY e BELVO, e o schema de criação aceita os dois — mas a fábrica só constrói o provedor da Pluggy. Criar configuração BELVO falha com 400 e a mensagem "Unsupported Open Finance provider type".Declarado, não implementado — não anuncie multi-provedor
Sem iniciação de pagamentoEste building block só lê dados. Não inicia Pix, não faz cobrança e não movimenta dinheiro, embora os agregadores contratados ofereçam isso.Fora do escopo atual
O vencimento do consentimento não é obrigadoconsentExpiresAt é gravado e devolvido, mas nenhuma rotina bloqueia leitura de item vencido nem emite alerta. Controle no seu lado.Roadmap — é a lacuna de conformidade mais relevante (§14)
Revogar não apaga o dado já sincronizadoDELETE /items/:id encerra a conexão no provedor e faz exclusão lógica do item; contas, transações e identidade permanecem no banco.Por design, com consequência de LGPD — expurgo é manual
Sem política de retençãoNenhum dado expira. Extrato de 2026 continua lá em 2030.Roadmap
Campo links.self com os segmentos invertidosAs respostas montam /api/v1/open-finance/..., enquanto o caminho real é /open-finance/api/v1/.... Um cliente que navegue pelos links recebe 404.Defeito conhecido — monte as URLs a partir da §9
Sem endpoint de listagem global de contas ou transaçõesSó há listagem aninhada: contas por item e transações por conta. Não existe "todas as transações da organização".Não implementado
sync-data traz 100 transações por contaUma execução não pagina até o fim do histórico. Contas com muito volume exigem execuções repetidas.Conhecido
sync-data é síncronoA operação percorre contas, transações e identidade dentro da requisição HTTP. Item grande pode se aproximar do tempo limite do gateway.Conhecido — não há fila
Sem medição de usoNão há contagem de conexões ativas nem de chamadas ao agregador por organização. É o que bloqueia a precificação (§6).Roadmap
Chave mestra sem rotação automatizadaTrocar OPENFINANCE_CREDENTIAL_MASTER_KEY torna todas as configurações indecifráveis. Não há recadastro assistido.Não implementado
expiresAt do token de conexão sempre nuloO provedor não devolve a data, e o mapeamento grava undefined. Trate o token como válido por poucos minutos.Limitação do provedor
Sem verificação de assinatura no webhook da PluggyO provedor não oferece esquema de assinatura; a recomendação dele é restrição por origem e URL única.Limitação do provedor (§14)
Nem toda conexão é Open Finance reguladoOs agregadores oferecem também acesso direto a instituições fora do escopo regulado. O building block não distingue os dois na resposta.Confirme o enquadramento de cada conector com o agregador
A documentação anterior deste módulo estava incorretaA versão anterior deste README informava porta 3018, schema open_finance, a variável OPEN_FINANCE_ENCRYPTION_KEY e endpoints inexistentes (GET /accounts, GET /accounts/:id/balance, GET /transactions, GET /identity/:itemId). Nada disso existe.Corrigido nesta versão — confira integrações feitas com base na versão antiga

16

Perguntas frequentes

A Catalisa é participante do Open Finance Brasil?

Não. Participar exige ser instituição autorizada a funcionar pelo Banco Central (art. 1º da Resolução Conjunta nº 1/2020). Quem é participante é o agregador contratado — hoje a Pluggy, ativa no diretório oficial com os papéis de Dados e Pagamentos. A Catalisa entrega a camada de software sobre esse participante. O enquadramento completo, com os artigos, está na §14, e ele deve ser lido antes de qualquer proposta comercial.

Então eu preciso de autorização do BACEN para usar isso?

Não, e é justamente esse o desenho. Uma empresa não autorizada consome dados como parceira contratada de uma participante, pelo art. 36 da mesma resolução. O que você precisa é de um contrato com o agregador. A ressalva importante é o inverso: se a sua empresa é autorizada pelo Banco Central, o art. 36, § 5º, I veda essa via — você precisa participar diretamente.

Quanto tempo vale a autorização do meu cliente?

No máximo 12 meses, pela norma. A data efetiva vem do provedor e fica em consentExpiresAt no item. Não existe renovação automática: alterar prazo, finalidade, instituição ou escopo exige novo consentimento (art. 10, § 2º). E atenção — o building block guarda a data mas não bloqueia leitura de item vencido, então o controle é seu (§15).

O meu cliente digita a senha do banco na minha tela?

Não, em nenhum momento. O fluxo abre o widget do provedor, e a autenticação acontece no ambiente da instituição financeira. Nem a sua aplicação nem a nossa vê credencial bancária. O que volta para você é um identificador de item.

Quais bancos estão disponíveis?

Depende do agregador contratado. GET /connectors responde com a lista real da sua conta, incluindo país, tipo, se exige segundo fator e quais produtos oferece. A Pluggy anuncia mais de 130 instituições (pluggy.ai); confirme na sua conta, porque a cobertura muda e pode variar por plano.

Posso usar a minha própria conta na Pluggy?

Pode, e essa é a forma recomendada. As credenciais são cadastradas por organização em POST /provider-configs, cifradas com AES-256-GCM antes de tocar o banco. Cada cliente da plataforma pode ter a própria conta no agregador, o que deixa a relação contratual com o participante clara — e permite que cada um negocie o próprio contrato.

Como eu apago o dado bancário de um cliente que pediu?

Hoje, manualmente. DELETE /items/:id encerra a conexão no provedor e faz exclusão lógica do item, mas contas, transações e identidade continuam no banco. Para eliminação, apague as linhas de openfinance_items do titular — o restante cai por cascata — e limpe as cargas correspondentes em openfinance_webhook_events, que não cai. Não há automação para isso (§15); defina o procedimento antes de subir dado real.

Dá para trocar de agregador depois?

A arquitetura foi feita para isso: a interface OpenFinanceProvider define o contrato e os tipos canônicos isolam quem consome. Trocar é implementar uma classe. Hoje, porém, só a Pluggy está implementada — BELVO está declarado no enum e não funciona (§15). Trate como capacidade arquitetural, não como funcionalidade disponível.

O que acontece se o cliente trocar a senha do banco?

O item vai para LOGIN_ERROR e as sincronizações passam a falhar. O caminho é refazer a conexão: novo POST /connect/token, o cliente autoriza de novo e você chama POST /items/sync com o novo identificador. Monitore o status dos itens — OUTDATED e LOGIN_ERROR são os dois que exigem ação.

Isso serve para iniciar um Pix?

Não. Este building block só lê dados. Iniciação de pagamento existe no ecossistema e os agregadores oferecem, mas não está implementada aqui (§15). Para movimentar dinheiro, veja o Payments e o BaaS.

Qual o volume real do Open Finance no Brasil hoje?

Na semana de referência de 31/07/2026, o painel oficial registrava 239,8 milhões de consentimentos ativos na ótica dos receptores e mais de 8,1 bilhões de chamadas de dados cadastrais e transacionais em uma única semana (Dashboard do Cidadão, atualizado em 13/08/2026). O número de consentimentos ativos vinha crescendo cerca de 3,1 milhões por semana. Consulte o painel na data da sua apresentação — ele é público e atualizado semanalmente.


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