# Design: exposição pública (api/docs/ib/merchant .monetarie.com) + API externa de cliente final + onboarding PF/PJ Nextcode

Data: 2026-07-21. Validado com o dono nesta sessão. Base: levantamento empírico contra a AWS viva (conta 990933657879), o repo Monetarie e o coreproviders (AVIV), sem inferências.

## 1. Fatos verificados (lastro do desenho)

### 1.1 DNS e padrão vivo de exposição
- `monetarie.com` está no Cloudflare (NS `trace.ns.cloudflare.com` / `dolly.ns.cloudflare.com`). `monetarie.com.br` está na KingHost e fica fora deste trabalho.
- Padrão vivo: `qrcode.monetarie.com` é CNAME para o ALB público `monetarie-qrcode-prod` (VPC 10.50) e `qrcode-h.monetarie.com` para `monetarie-qrcode-homolog` (VPC 10.45). HTTPS 443 apenas, cert ACM por hostname, SG 443 aberto, listener default fixed-response 404 JSON, regra por host + whitelist de paths (`/qr/v2/*`, `/qrc/jwks`) para TG dedicado (`mon-qrc-pub-h/p`, pix-api:4003, healthy nos 2 ambientes).
- Não existe nenhuma WebACL WAF na região hoje (nem no qrcode). Os ALBs públicos do qrcode foram criados fora do Terraform (drift permanente aceito pela regra do projeto: nunca apply).
- Route53 da conta só tem zonas privadas (`monetarie.internal`, `priv.rtmcloud.net.br`). A zona privada é compartilhada entre as VPCs de HML e PRD.

### 1.2 Defeito provado: NGINX do IB/Merchant em PRD
- As imagens de PRD de `core-banking-ui` e `core-merchant-ui` (commit `0f768655`) têm upstream hardcoded `core-api-h.monetarie.internal` (HML) no nginx.
- Da VPC de PRD não há rota para 10.45 (peering só roteia `10.45.1.30/32`, o host da VPN) e o SG do ALB interno de HML só aceita `192.168.0.0/16` e `10.45.0.0/16`.
- Conclusão: todo `/api` do IB e do Merchant em PRD morre em timeout hoje. O admin já usa o padrão correto (`CORE_API_UPSTREAM`/`CORE_API_HOST` via envsubst no Dockerfile). A correção replica esse padrão em banking e merchant.

### 1.3 Padrão de segurança do coreproviders (AVIV), extraído com evidência
- Borda: um ALB público por ambiente, regras por host (api/ib/merchant/docs/core), certs por host via SNI, DNS gerenciado pelo cliente via CNAME. Sem CloudFront.
- WAF (ruleset completo documentado no Cloud Armor, `infrastructure/security/cloud-armor-deny-runbook.md`): regras gerenciadas XSS/SQLi/LFI/RFI/RCE com exclusão de `/api/*` (evita falso positivo em payload assinado), rate-based ban por IP, admin restrito a IP de VPN. O WebACL AWS deles não está no repo (gerenciado fora do IaC); o IP set de clientes de API é sincronizado do banco pelo worker `AwsWafSyncWorker` + `AwsWaf.Client` (WAFv2 REGIONAL, UpdateIPSet).
- API externa (`/api/external/*`): pipeline `[:api, :api_key_authenticated, :audit_context, :hmac_validated, :rate_limited_api, :idempotent]`.
  - Auth: `Authorization: ApiKey client_id:client_secret` ou `Basic`.
  - Whitelist de IP OBRIGATÓRIA por chave: chave sem whitelist recusa com 403 (fail-closed). Resolução de IP confia em XFF somente vindo de proxy confiável, right-most não-proxy.
  - HMAC-SHA512: header `hmac` (128 hex minúsculo), corpo JSON re-serializado com chaves em ordem alfabética, compacto, chave = client_secret cru, comparação constant-time. POSTs apenas.
  - Webhook outbound: HMAC-SHA256 `X-*-Signature: sha256=<hex>` com secret próprio do webhook.
- Docs: VitePress estático público (sem auth) atrás do ALB, nginx endurecido, 5 idiomas, conteúdo completo (visão geral, ciclo de vida PIX, ambientes, Postman, autenticação, página HMAC dedicada, cash-in/out, conta, chaves, devolução, MED e infrações, validação CPF, controles, webhooks com payloads).

### 1.4 Estado do Core relevante
- `api_keys` já existe no shape AVIV (`client_id`, `client_secret_hash`, `client_secret_prefix`, `permissions`, `ip_whitelist`, escopo merchant/partner). Plug `ApiKeyAuth` existe e checa whitelist, mas hoje whitelist vazia passa (na AVIV recusa). Pipeline `api_key_authenticated` definido e NÃO usado por nenhuma rota. Não existe HMAC de request.
- Partner API (`/api/partner/v1`, OAuth2 client_credentials) fica intocada e interna. `api.monetarie.com` é OUTRA coisa: a API principal de clientes finais.
- docs-portal vivo (HML:14, PRD:9, imagens `*-9fb3392b-portal-20260719`) já serve o portal parceiro limpo (zero `/raw/`). O diretório `docs-portal/` do repo com Dockerfile antigo está superado; a fonte real é `docs/partner-portal/`.
- Onboarding: duas pilhas. (A) fluxo próprio PF (`onboarding_applications`, telas públicas `/onboarding/*` no IB) e PJ (`business_onboardings`, `/register` + 9 passos, SMS real). (B) fluxo Nextcode proposals (`Proposals.build_template_from_settings`, `ProposalsClient` com `ApiKey`, webhook `POST /api/webhooks/nextcode/:external_id/finish` fail-closed, `nextcode_proposals`/`kyc_provider_configs`, tela admin `NextcodeProviderView`).
- Gaps provados do fluxo (B): nenhuma rota chama `create_proposal`; tela Nextcode do IB órfã (composable stub que lança erro); review de proposals sem view no admin (`useOnboarding.ts` órfão); `KycController.approve` cria só `users` (sem member/conta); proposals/registrations são CNPJ-only.
- Os 2 JSONs de jornada da Nextcode (Desktop) são configs de jornada: PF = `ocr`+`liveness`, postProcessing `faceMatch`; PJ = `receita-federal-cnpj-qsa`+`survey`+`ocr`+`liveness` com `surveyId` provisionado (`6a5e237f912cbb919313b84c`); ambos com entrega por e-mail, `redirectTo` monetarie.com.br, expiração 259200000 ms, layout dourado `#CE8F32`, logo base64. O builder atual do Core manda `backgroundCheckLegalEntity` no PJ, divergente do JSON oficial.
- ApiKey da Nextcode foi recebida do dono nesta sessão. Regra absoluta 10: NÃO fica em arquivo nenhum. Destino: Secrets Manager `monetarie/{env}/kyc/nextcode/api_key`.

## 2. Decisões do dono (registradas)

1. Crescer o ALB público existente do qrcode em cada ambiente (fiel à AVIV, um ALB público por ambiente). Nome "qrcode" fica como cosmético.
2. `api.monetarie.com` = API de clientes finais, modelo AVIV (ApiKey + whitelist obrigatória + HMAC-SHA512). Partner API continua interna e intocada. Garantia exigida: zero brecha entre as superfícies.
3. Onboarding: PF+PJ ponta a ponta (IB + registro manual no coreadmin), jornadas Nextcode pelos 2 JSONs.
4. Certs: ACM gerenciado (custo zero) para os 8 hostnames novos. Certs do qrcode intocados (exigência BACEN).
5. Rate limit de borda dimensionado para o piso de projeto de 120.000 req/60s. Nunca throttlear tráfego legítimo.
6. Criar também as variantes `-h.monetarie.com` (HML): `api-h`, `docs-h`, `ib-h`, `merchant-h`.
7. NGINX de cada UI aponta para a API do MESMO ambiente, sem margem de erro (IB HML -> api HML; IB PRD -> api PRD; idem merchant).

## 3. Desenho

### 3.1 DNS e certificados
- 1 cert ACM por ambiente com 4 SANs. HML: `api-h/docs-h/ib-h/merchant-h.monetarie.com`. PRD: `api/docs/ib/merchant.monetarie.com`. Validação DNS: CNAMEs de validação criados no Cloudflare (ação do dono ou sessão com acesso ao Cloudflare).
- CNAMEs de tráfego no Cloudflare em modo DNS-only (nuvem cinza; proxy laranja quebraria whitelist de IP e duplicaria WAF): HML -> `monetarie-qrcode-homolog-830783211.sa-east-1.elb.amazonaws.com`; PRD -> `monetarie-qrcode-prod-1233236150.sa-east-1.elb.amazonaws.com`.

### 3.2 ALB (crescer o do qrcode, por ambiente)
- Anexar o cert novo via SNI ao listener 443.
- Criar listener :80 com default redirect 301 para 443 (SG ganha ingress 80).
- Regras novas no listener 443 (default 404 continua):
  - prio 20: host `api(-h).monetarie.com` + paths `/api/external/*`, `/api/webhooks/nextcode/*` -> TG `mon-extapi-pub-{h,p}` (core-api:4000, health `/health`).
  - prio 30: host `ib(-h).monetarie.com` -> TG `mon-ib-pub-{h,p}` (banking-ui:8080).
  - prio 40: host `merchant(-h).monetarie.com` -> TG `mon-mer-pub-{h,p}` (merchant-ui:8080).
  - prio 50: host `docs(-h).monetarie.com` -> TG `mon-docs-pub-{h,p}` (docs-portal:8080).
- ECS: adicionar o segundo TG nos services `core-api`, `core-banking-ui`, `core-merchant-ui`, `docs-portal` (update-service com múltiplos load balancers, sem recriar service).
- SG das tasks: ingress 4000/8080 a partir do SG do ALB público de cada ambiente (replica o que o qrcode -> pix-api:4003 já tem).
- Garantia de isolamento: pela regra de paths, por `api.monetarie.com` nada além de `/api/external/*` e o webhook Nextcode é alcançável. Admin, v1, v2 e Partner API continuam exclusivos do ALB interno.

### 3.3 NGINX das UIs (pré-requisito de PRD)
- `core/apps/banking/nginx.conf` e `core/apps/merchant/nginx.conf` migram para o padrão do admin: template com `CORE_API_UPSTREAM` e `CORE_API_HOST` resolvidos por envsubst no boot; task-def de HML aponta `core-api-h.monetarie.internal`, a de PRD `core-api.monetarie.internal`.
- Rebuild + redeploy das 2 UIs nos 2 ambientes com validação viva do `/api` pós-swap em cada um.

### 3.4 WAF (WebACL REGIONAL por ambiente, associado ao ALB público)
- Managed rules: AWSManagedRulesCommonRuleSet, KnownBadInputs, SQLiRuleSet, AmazonIpReputationList, AnonymousIpList. Scope-down: excluir `/api/external/*` das regras de inspeção de payload (padrão AVIV de evitar falso positivo em payload assinado); qrcode host monitorado antes de qualquer enforcement.
- Rate-based rules: dimensionadas para nunca ficar abaixo do piso de 120.000 req/60s de projeto. IPs presentes no IP set de clientes de API ficam isentos de throttle; portais de navegador (ib/merchant/docs) recebem limite por IP alto o suficiente para uso legítimo.
- Rollout em duas fases obrigatórias: tudo entra em COUNT mode, observa métricas, e só depois vira BLOCK com OK do dono.
- IP set `monetarie-extapi-clients` sincronizado do banco: port do `AwsWafSyncWorker` + `AwsWaf.Client` da AVIV (união dos `ip_whitelist` das `api_keys` ativas, normalizado /32 ou /128).

### 3.5 API externa de cliente final (`/api/external`)
- Novo scope no router do core-api, pipeline espelho AVIV: `[:api, :api_key_authenticated, :audited, :hmac_validated, :rate_limited, :idempotent]`.
- `ApiKeyAuth` vira fail-closed: chave sem `ip_whitelist` configurada recusa com 403 (mudança de contrato somente para o pipeline externo; comportamento atual preservado onde o plug não é usado).
- Novo plug `HmacValidation` (port do AVIV): header `hmac`, HMAC-SHA512, corpo JSON compacto com chaves em ordem alfabética, chave = client_secret cru, comparação constant-time, POSTs.
- Superfície (paridade AVIV, reusando use cases existentes do v2/partner, sem reimplementar money-path): PIX cash-out por chave e por EMV, cash-in (QR), consultas por id/e2e/external-id, saldo, extrato, chaves PIX, devolução, MED e infrações com defesa, validação CPF, webhooks CRUD, ping.
- Gestão de chaves: cliente pelo IB (v2 `ApiKeyController`, já existe com update de ip-whitelist) e operador pelo coreadmin (parity controller, já existe).
- Faseamento: primeiro auth+HMAC+whitelist+saldo+extrato+PIX cash-out/cash-in+webhooks; o restante na sequência imediata com a doc acompanhando.

### 3.6 docs.monetarie.com
- Mesmo service docs-portal (portal limpo já no ar). Conteúdo passa a documentar a API externa de cliente final no formato AVIV completo (pt/en/es): visão geral, ciclo de vida PIX, ambientes (HML `-h` e PRD), Postman collection, autenticação ApiKey, página HMAC dedicada com exemplos multi-linguagem, cash-in/out, conta, chaves, devolução, MED, validação CPF, webhooks com payloads.
- A documentação da Partner API sai do host público e permanece interna (`docs-h.monetarie.internal`).

### 3.7 Onboarding PF+PJ ponta a ponta (Nextcode)
- Config: ApiKey no Secrets Manager `monetarie/{env}/kyc/nextcode/api_key`, injetada como `NEXTCODE_API_KEY` na task-def do core-api; registro por ambiente na tela `NextcodeProviderView` (grava `kyc_provider_configs`). `NEXTCODE_ENABLED` sobe OFF e flipa por ambiente com OK do dono. Confirmar com a Nextcode qual base_url a key atende (prod `onboarding-api.nxcd.app` vs `staging.nxcd.app`) antes do flip.
- Templates: os 2 JSONs viram os templates canônicos das jornadas. `build_template_from_settings` alinhado ao oficial: PJ = `receita-federal-cnpj-qsa`+`survey`+`ocr`+`liveness` com `surveyId` do JSON (hoje manda `backgroundCheckLegalEntity`, divergente); PF = `ocr`+`liveness` sem survey; ambos com `faceMatch` em postProcessing, `acceptedDocuments` do JSON, entrega por e-mail, `redirectTo`, expiração 3 dias, layout dourado e logo.
- Gatilhos (o gap real): IB PF cria jornada após OTP do fluxo `/onboarding`; IB PJ cria jornada no step `kyc` do business-onboarding; coreadmin manual ganha escolha PF ou PJ e dispara jornada para o e-mail do cliente (dossiê PJ atual continua como alternativa).
- Fecho do ciclo: webhook finish (existe, fail-closed) -> review de proposals exposta no admin (view nova sobre o composable órfão) -> approve corrigido para promoção completa (user + cooperative_member + conta via Promotion/OpenAccount, PF e PJ). Migration aditiva para proposals/registrations aceitarem CPF.

## 4. Sequência de execução (hoje)
1. Infra: certs ACM + CNAMEs Cloudflare (validação e tráfego) + listener/regras/TGs/SG + ECS second TG.
2. Fix NGINX banking/merchant + rebuild/redeploy nos 2 ambientes com validação viva.
3. Portais ib/merchant/docs públicos validados em HML (e PRD na sequência com OK).
4. WAF em COUNT nos 2 ambientes.
5. Frente 2 (backend + telas) com TDD, deploy HML, validação viva com jornada real, PRD com OK.
6. API externa `/api/external` faseada + docs de cliente final no formato AVIV.

## 5. Riscos e salvaguardas
- Deploy de UI/core-api não toca pix/spb; ainda assim vale a regra: nunca deployar durante operação de money-path ao vivo do dono.
- WAF nunca nasce em BLOCK. Primeiro COUNT, análise, depois enforcement com OK.
- Whitelist obrigatória e HMAC valem para a superfície externa nova; nada muda para Partner API ou fluxos internos.
- Cloudflare fica DNS-only; qualquer mudança para proxy exigiria redesenho de IP allowlisting.
- Segredos (Nextcode ApiKey, client_secrets) jamais em repo/markdown/env versionado; somente Secrets Manager.
