Catalisa.Building Blocks
Catálogo/Documentos/Service Attestations

Service Attestations

Alpha

O serviço foi prestado? Quem paga confirma — lido direto do sistema de escala

13
Endpoints
5
Entidades
2
Provedores
Tenant
Escopo
3042
Porta
2026-10
Desde

Em vez de pedir ao prestador um PDF dizendo que trabalhou, o bloco lê o sistema de escala e repasse de quem paga e avisa quando um repasse nasce, muda ou é cancelado.

Para quem é
  • Fundo ou fintech que antecipa recebível de prestador de serviço (médico, plantonista, terceirizado)
  • Operação de crédito que precisa de lastro confirmado pela fonte, não declarado pelo cedente
Substitui
  • Conferência manual de demonstrativo de repasse em PDF ou planilha
  • Integração ponto a ponto com cada sistema de escala dentro do produto
O que não é
  • Motor de antecipação ou de crédito — isso é o painel, que consome os eventos
  • Sistema de escala — o bloco só LÊ a fonte, nunca escreve nela
  • Cadastro de pessoa ou empresa — isso é People e Companies
O que dá para fazer

10 endpoints em 1 recurso.

Explorar a API →
01

Resumo executivo

O Service Attestations conecta uma organização ao sistema de escala e repasse de quem paga (hoje, o Pega Plantão) e guarda, para cada serviço prestado e cada repasse gerado, o que a fonte afirmou: quem prestou, para quem, quando, por quanto e quando será pago. Cada atestado tem versão e hash; quando a fonte muda um valor ou retira um item, o bloco percebe e avisa por evento.

É agnóstico: não sabe de antecipação, médico ou guia. Um "atestado" é só "a fonte afirma que este serviço foi prestado por este sujeito, para este pagador, por este valor". O painel decide o que fazer com isso (no primeiro uso, a antecipação de plantão confirmado).

Enquanto a credencial real do Pega não existe, tudo roda com o provedor simulado (MOCK), determinístico, com o mesmo formato. Trocar para o real é criar uma conexão PEGA_PLANTAO.

AtributoValor
Identificadorservice-attestations
CategoriaDocumentos
EscopoTenant-scoped (por organização; sem subconta)
Porta (standalone)3042
Path alias@service-attestations
StatusAlpha desde 2026-10
Depende deIAM (autenticação)

02

O problema

negócio

O cenário. Um fundo antecipa ao médico o repasse que a organização social (OS) vai pagar no dia 20. O lastro é "o médico trabalhou e a OS vai pagar".

O que trava hoje.

  • A prova é um PDF ou print enviado pelo próprio cedente.
  • O valor muda depois (glosa, plantão cancelado) e ninguém fica sabendo até o vencimento.
  • Cada produto que quisesse ler a escala teria de embutir um cliente HTTP do provedor.

O custo de não resolver. Antecipar sobre lastro declarado é risco de fraude e de glosa sem aviso.


03

Proposta de valor

negócio
AntesDepois
PDF de demonstrativo enviado pelo cedenteRepasse lido da fonte do pagador, com data e valor
Mudança descoberta no vencimentoEvento changed/cancelled na leitura seguinte
Integração por produtoUma porta, vários provedores, um contrato

Lastro confirmado pela fonte. O dado vem de quem paga, não de quem recebe.

Mudança vira evento. Versão + hash por atestado; o consumidor não precisa reler tudo.

Trocar de fonte não muda o consumidor. Simulado e real devolvem o mesmo canônico.


04

Casos de uso reais

negócio

Caso 1 — Antecipação de plantão confirmado Cenário ilustrativo

Contexto

Fundo que antecipa repasses de médicos PJ de uma OS que usa o Pega Plantão. A dor. Comprovante de serviço declarado; glosa descoberta no vencimento. A solução. Conexão PEGA_PLANTAO com o CNPJ da OS em payerDocument; o painel assina service-attestations.attestation.* e lê GET /attestations/:id para casar a nota com o repasse. O resultado. Só antecipa sobre repasse confirmado; cancelamento chega como evento.

Caso 2 — Dossiê com evidência de plantões Cenário ilustrativo

Contexto

Mesa de crédito montando dossiê do cedente. A dor. Sem histórico de serviço prestado. A solução. GET /attestations?kind=shift&subjectDocument=<CPF> devolve os plantões lidos. O resultado. Evidência datada e com origem.

Caso 3 — Demonstração e homologação sem credencial Cenário ilustrativo

Contexto

Staging e demos antes de a fonte real liberar acesso. A solução. Conexão MOCK; settings.mockOverrides provoca mudança e cancelamento sob demanda. O resultado. O fluxo inteiro (eventos, painel, dossiê) roda igual ao real.


05

Mercado e diferenciais

negócio

Panorama. Quem antecipa recebível de serviço confere demonstrativo à mão ou integra cada fonte dentro do próprio produto.

CritérioCatalisaIntegração por cliente
Fonte novaUm driver novoProjeto novo
Versão e cancelamentoNativos, com eventoRaramente
Multi-tenancyPor organização, credencial cifradaVariável

Nossos diferenciais

  1. Canônico + versão + evento. Difícil de copiar sem o motor de leitura e a trilha.
  2. Simulado com o mesmo formato da fonte. Homologa o fluxo inteiro sem credencial.

Quando escolher o concorrente. Se a operação tem uma única fonte, sem multi-tenant e sem necessidade de trilha, uma integração direta pode bastar.


06

Modelo de cobrança e ROI

negócio

Unidade de cobrança. Conexão ativa por mês (proposta; ainda não medida pelo Billing).

O que dispara custo. Conexões ativas e volume de atestados lidos.

ROI. Uma glosa evitada numa antecipação paga meses de conexão. Sem número defensável ainda.


07

Arquitetura

Decisões não óbvias.

  • O motor é separável. src/service-attestations/sync/ não conhece atestado: recebe itens { kind, externalId, windowDate, payload, raw } e fala com o domínio por interfaces (SyncStore, SyncHooks, SyncRunStore). Há teste que falha se aparecer termo do domínio ou import de fora do diretório. É o corte para o bloco irmão de rastreio da garantia viva (Traccar, resumo por lote): quando ele existir, sync/ sobe para src/shared sem mudar o contrato (regra de dois — não se extrai antes do segundo consumidor).
  • Cancelamento só com leitura completa. O driver diz quais tipos leu por inteiro (completeKinds). Um dia que falhou deixa o tipo incompleto e nada dele é cancelado.
  • Token em memória. O token do Pega dura ~10 min e cabe numa leitura; não se persiste.
  • Sem @Service() resolvido pelo container. Tudo é montado à mão em src/shared/container/modules/service-attestations.ts (defeito do typedi sem design:paramtypes).

Monolito vs. standalone. As rotas existem nos dois. A leitura agendada só liga no processo standalone (main.ts).


08

Conceitos e modelo de dados

Glossário

TermoSignifica
ConexãoAttestationProviderConfig: a ligação de uma organização com uma fonte
AtestadoAttestation: um item afirmado pela fonte, kind = payout (repasse) ou shift (plantão)
JanelawindowDays (padrão 45) dias até hoje, no fuso da conexão
windowDateO dia pelo qual a fonte devolve o item (geração do repasse, início do plantão)
LeituraAttestationSyncRun: uma passada, manual ou agendada

Modelo de dados

Modelo PrismaTabelaPropósitoCampos-chave
AttestationProviderConfigattestation_provider_configsConexão por organizaçãoproviderType, credentials (cifrada), settings, lastSync*
AttestationattestationsAtestado canônicoúnico (organizationId, configId, kind, externalId), contentHash, version, status
AttestationRawattestation_rawsBruto da fonte, à parteattestationId, raw
AttestationSyncRunattestation_sync_runsCada leiturastatus, read/created/changed/cancelled/failed, error
AttestationSimulatedProviderGrantattestation_simulated_provider_grantsConcessão do simulado em produçãouma ativa por organização

Canônico (Attestation): subjectDocument (CPF), subjectName, subjectBusinessDocument (CNPJ), subjectBusinessName, payerDocument, payerName, location, sector, serviceStart, serviceEnd (ISO), competence (YYYY-MM), amountCents, payoutDate (YYYY-MM-DD), payoutGroup, status, contentHash, version, firstSeenAt, lastSeenAt.

Máquina de estados


09

Referência da API

Prefixo /service-attestations/api/v1. Respostas { data } (listas com meta: { total, page, pageSize }). Erros { error, message, details }.

MétodoRotaDescriçãoPermissão
POST/provider-configsCria conexão (credencial no corpo)SERVICE_ATTESTATIONS_ADMIN
GET/provider-configsListaSERVICE_ATTESTATIONS_READ
GET/provider-configs/:idDetalheSERVICE_ATTESTATIONS_READ
PATCH/provider-configs/:idname, settings (merge raso), isActive, isDefault, credentialsSERVICE_ATTESTATIONS_ADMIN
DELETE/provider-configs/:idRemoção lógica (204)SERVICE_ATTESTATIONS_ADMIN
POST/provider-configs/:id/test{ ok, message }SERVICE_ATTESTATIONS_ADMIN
POST/provider-configs/:id/syncLê agora → SyncRun (409 se já houver leitura)SERVICE_ATTESTATIONS_ADMIN
GET/sync-runs?configId=&page&pageSizeLeiturasSERVICE_ATTESTATIONS_READ
GET/attestations?kind&status&subjectDocument&subjectBusinessDocument&payerDocument&competence&from&to&configId&page&pageSizeAtestadosSERVICE_ATTESTATIONS_READ
GET/attestations/:id[?includeRaw=true]Detalhe (bruto só sob pedido)SERVICE_ATTESTATIONS_READ
GET/PUT/DELETE/admin/platform/organizations/:organizationId/simulated-providerConcessão do simuladooperador da plataforma

POST /provider-configs

json
{
  "name": "Pega Plantão — OS Bem Viver",
  "providerType": "PEGA_PLANTAO",
  "credentials": { "clientId": "…", "clientSecret": "…", "username": "…", "password": "…" },
  "settings": {
    "baseUrl": "https://api.pegaplantao.com.br",
    "payerDocument": "11222333000181",
    "payerName": "OS Bem Viver",
    "windowDays": 45,
    "syncIntervalMinutes": 15,
    "timezone": "America/Sao_Paulo"
  },
  "isActive": true,
  "isDefault": true
}
{
  "name": "Pega Plantão — OS Bem Viver",
  "providerType": "PEGA_PLANTAO",
  "credentials": { "clientId": "…", "clientSecret": "…", "username": "…", "password": "…" },
  "settings": {
    "baseUrl": "https://api.pegaplantao.com.br",
    "payerDocument": "11222333000181",
    "payerName": "OS Bem Viver",
    "windowDays": 45,
    "syncIntervalMinutes": 15,
    "timezone": "America/Sao_Paulo"
  },
  "isActive": true,
  "isDefault": true
}
Campo de settingsTipoPadrãoDescrição
baseUrlURL—Obrigatório no PEGA_PLANTAO; https em produção
tokenUrlURL<baseUrl>/tokenEndpoint OAuth2
payerDocument / payerNameCNPJ / texto—Quem paga (a fonte não informa)
windowDays1–12045Janela de leitura
syncIntervalMinutes5–144015Intervalo da leitura agendada
locationCodes / sectorCodestexto[]—Filtro por CodigoLocal / CodigoSetor
timezoneIANAAmerica/Sao_PauloFuso dos dias
scenariotextodefaultSó MOCK: default, empty, unavailable, partial
mockOverrides[{ externalId, action: 'cancel' | 'amount', amountCents? }]—Só MOCK: provoca cancelled/changed

Resposta 201: a conexão com hasCredentials — nunca credentials.

Erros

StatusCódigoQuando
400VALIDATIONCorpo inválido; Pega sem baseUrl ou credencial completa; CNPJ inválido
403FORBIDDEN (SIMULATED_PROVIDER_NOT_GRANTED)MOCK em produção sem concessão
404NOT_FOUNDFora da organização
409CONFLICTNome repetido; leitura já em andamento

10

Início rápido

1. Conectar o simulado (staging)

bash
curl -s -X POST "$BB/service-attestations/api/v1/provider-configs" -H "x-api-key: $KEY" \
  -H 'content-type: application/json' \
  -d '{"name":"Simulado","providerType":"MOCK","settings":{"payerDocument":"11222333000181","payerName":"OS Teste"}}'
curl -s -X POST "$BB/service-attestations/api/v1/provider-configs" -H "x-api-key: $KEY" \
  -H 'content-type: application/json' \
  -d '{"name":"Simulado","providerType":"MOCK","settings":{"payerDocument":"11222333000181","payerName":"OS Teste"}}'

2. Ler agora

bash
curl -s -X POST "$BB/service-attestations/api/v1/provider-configs/$ID/sync" -H "x-api-key: $KEY"
curl -s -X POST "$BB/service-attestations/api/v1/provider-configs/$ID/sync" -H "x-api-key: $KEY"

3. Consultar os repasses

bash
curl -s "$BB/service-attestations/api/v1/attestations?kind=payout&status=confirmed" -H "x-api-key: $KEY"
curl -s "$BB/service-attestations/api/v1/attestations?kind=payout&status=confirmed" -H "x-api-key: $KEY"

4. Provocar mudança e cancelamento (smoke)

bash
curl -s -X PATCH "$BB/service-attestations/api/v1/provider-configs/$ID" -H "x-api-key: $KEY" \
  -H 'content-type: application/json' \
  -d '{"settings":{"mockOverrides":[{"externalId":"MOCK-REP-202609-P01","action":"cancel"},{"externalId":"MOCK-REP-202609-P02","action":"amount","amountCents":500000}]}}'
curl -s -X POST "$BB/service-attestations/api/v1/provider-configs/$ID/sync" -H "x-api-key: $KEY"
# → changed: 1, cancelled: 1; eventos service-attestations.attestation.changed|cancelled
curl -s -X PATCH "$BB/service-attestations/api/v1/provider-configs/$ID" -H "x-api-key: $KEY" \
  -H 'content-type: application/json' \
  -d '{"settings":{"mockOverrides":[{"externalId":"MOCK-REP-202609-P01","action":"cancel"},{"externalId":"MOCK-REP-202609-P02","action":"amount","amountCents":500000}]}}'
curl -s -X POST "$BB/service-attestations/api/v1/provider-configs/$ID/sync" -H "x-api-key: $KEY"
# → changed: 1, cancelled: 1; eventos service-attestations.attestation.changed|cancelled

11

Receitas

Trocar MOCK → PEGA_PLANTAO. Crie uma conexão PEGA_PLANTAO com a credencial real e baseUrl; teste (POST …/test); desative a MOCK (PATCH { "isActive": false }). Nada muda no consumidor: mesmo canônico, mesmos eventos.

Reagir a eventos. Assine service-attestations.attestation.* no webhooks-engine e busque o detalhe em GET /attestations/:id — o evento não leva dado pessoal.


12

Integração com outros building blocks

  • webhooks-engine: entrega os eventos do iam-events às assinaturas.
  • audit-trail: mapeia service-attestations.attestation|sync|provider_config.
  • Operador da plataforma: service-attestations.simulated-provider.* vai ao stream do operador (@shared/lib/operator-events), nunca a webhook.

Eventos (payload sem PII):

EventoPayload
service-attestations.attestation.confirmedattestationId, configId, provider, kind, externalId, status, amountCents, payoutDate, competence, version
service-attestations.attestation.changedo mesmo + previousAmountCents, changedFields
service-attestations.attestation.cancelledo mesmo
service-attestations.sync.failedconfigId, provider, syncRunId, reason
service-attestations.raw-retention.purgedorganizationId, deleted, retentionDays, cutoff — por organização afetada a cada passada da retenção; só a contagem

13

Configuração e operação

VariávelPadrãoDescrição
SERVICE_ATTESTATIONS_CREDENTIAL_MASTER_KEY—64 hex; cifra a credencial da fonte. Obrigatória
SERVICE_ATTESTATIONS_SIMULATED_PROVIDER_ORGANIZATION_IDSvaziaReserva da trava do simulado em produção (* só em staging)
SERVICE_ATTESTATIONS_SYNC_ENABLEDtrueLeitura agendada
SERVICE_ATTESTATIONS_SYNC_TICK_SECONDS60Tique do agendador
SERVICE_ATTESTATIONS_SYNC_MAX_PER_TICK20Conexões lidas por tique
SERVICE_ATTESTATIONS_RAW_RETENTION_DAYS90Retenção do bruto da fonte (attestation_raws), pela última regravação. 0 desliga. O atestado canônico nunca é apagado
BANKING_PLATFORM_ORGANIZATION_IDSvaziaQuem é operador (concessão do simulado)

Migrations: prisma/migrations/20261016180000_service_attestations_core e 20261016200000_service_attestations_raw_retention_index (índice em updated_at; ambas idempotentes). No Launch, pela fase 0 (scripts/launch/), com MIGRATIONS="20261016180000_service_attestations_core 20261016200000_service_attestations_raw_retention_index".

Retenção do bruto: agendador próprio (jobs/raw-retention.scheduler.ts, LockedPass service-attestations:raw-retention:sweep:lock) a cada 6 h, primeira passada 60 s após subir. Apaga em lotes de 500 (FOR UPDATE SKIP LOCKED, seguro com N réplicas), no máximo 200 lotes ou 8 min por passada; o resto fica para a próxima. Uma linha de log por passada (service-attestations: retenção do bruto concluída, com deleted) e o evento service-attestations.raw-retention.purged por organização afetada. Depois de apagado, o detalhe com includeRaw=true devolve raw: null; a próxima leitura que mudar o atestado regrava.

Provedor PEGA_PLANTAO: leituras GET /api/v1/demonstrativos/repasse?dataDeGeracao= (dia a dia), GET /api/v1/plantoes?filtro.dataInicio&filtro.dataFim (janelas de 7 dias; 204 = vazio), GET /api/v1/fornecedores (paginado, paginaAtual/itensPorPagina), POST /api/v1/profissionais/situacao (nomes, melhor esforço). Token POST /token (Basic do cliente, grant_type=password); renova com < 60 s; 401 → renova uma vez; refresh tenta POST, cai para GET com query e, por fim, refaz o login. Nenhuma escrita: lista de leitura conferida antes do envio. Intervalo mínimo de 250 ms entre chamadas.

Padrões nossos (declarados). Competência do repasse do Pega = mês anterior à DataRepasse, com serviço do primeiro ao último dia dela (a fonte não informa). Duas linhas de repasse com a mesma chave no mesmo dia viram um repasse com a soma. externalId sem código da fonte = <CodigoGrupoGeracao>-<hash16> (nunca CPF).

Simulado (MOCK). 12 profissionais (CPF e CNPJ válidos), dois hospitais, setores UTI Adulto, Pronto-socorro, Centro cirúrgico, Enfermaria clínica e UTI Neonatal; 4 a 8 plantões de 12 h por mês; repasse mensal por profissional = soma dos plantões, gerado no dia 5 e pago no dia 20 (útil) do mês seguinte.

Regra da prévia (para sempre haver repasse a receber): a competência em curso já sai como repasse a partir do dia 1 dela — para que sempre exista um repasse a receber com prazo para adiantar (o anterior vence no dia 20) — com o mesmo externalId, o mesmo valor (a escala do mês inteiro) e a mesma payoutDate (dia 20 útil do mês seguinte); só o windowDate é o dia 1. No dia 5 do mês seguinte ela fecha e o windowDate passa a ser o dia 5 (o windowDate não entra no hash, então não há changed). A data de referência é o until da janela. Resultado: em qualquer dia, todo profissional tem ao menos um repasse com payoutDate no futuro — dias 5–19, o fechado do mês anterior; dias 20–4, a prévia do mês em curso. Meses já fechados não mudam.


14

Segurança e compliance

  • Credencial cifrada (aes-256-gcm), decifrada só ao montar o driver; nunca em resposta, log, evento ou SyncRun. Mensagens de erro da fonte não levam corpo nem segredo.
  • Escopo por organização em toda consulta; fora dela é 404.
  • Eventos sem CPF, nome ou CNPJ. O externalId nunca carrega CPF.
  • Fonte real só por https em produção; driver só leitura.
  • Simulado em produção só com concessão do operador (ou lista de reserva).

15

Limitações conhecidas

  • Sem subconta (X-Subaccount-Id e chave de subconta são recusados pelo middleware).
  • Sem retenção automática de attestation_raws.
  • O exemplo da doc do Pega corta os campos do plantão: a identificação do profissional é procurada por nomes prováveis (CodigoProfissional, CPF…) e confirmada na primeira leitura real.
  • Feriados não entram no "dia útil" do simulado.

16

Perguntas frequentes

Como trocar do simulado para o Pega? Criar uma conexão PEGA_PLANTAO com a credencial real. Nada mais muda.

Por que um atestado sumiu como cancelled? Uma leitura completa da janela não o devolveu. Leituras parciais nunca cancelam.

O painel guarda a senha do Pega? Não. A credencial vai direto ao bloco e não sai dele.