# Monetarie Partner API — Design validado (2026-06-22)

## 1. Objetivo

Permitir que o **cliente (fintech parceira)** plugue **o app, o internet banking e o CRM dele** diretamente no **Core** da Monetarie via **chaves de API server-to-server**, **sem** usar a nossa API de clientes, nem os fronts **Merchant** e **IB**. O CRM do cliente faz o **KYC** por fora e nos entrega o cliente já validado; o app/IB do cliente movimenta contas, PIX, TED e recebe eventos por webhook.

Entregáveis da sessão: (a) habilitar a **API de Parceiro** real; (b) provisionar um **usuário do sistema + chave de API** no homolog; (c) **documentação** com a marca nova (`#101820`/`#ffc847`); (d) **Postman Collection**; (e) **testes/comprovação** ponta-a-ponta.

## 2. Aterramento empírico (provado em código — não suposto)

Backend Core = Elixir/Phoenix, namespace `Monetarie.*`, app `:monetarie`, router `core/backend/lib/monetarie_web/router.ex` (3229 linhas, 182 scopes, ~1739 rotas).

**Verdade 1 — identidade é integer e "self-scoped".** Cliente final = linha em `users` (PK integer = subject do JWT = `api_keys.merchant_id`) + `accounts` (PK bigint, `user_id`) + perfil KYC opcional em `cooperative_members` (UUID). **Não há** coluna ligando "cliente → parceiro". Barreira literal: `to_string(current_id) == to_string(merchant_id)` duplicada em `transfer/pix/dict/irpf/investment/notification/boleto/in_transit` controllers (v2).

**Verdade 2 — auth por API key existe, está DORMENTE, e parte já é compatível.**
- `api_keys` (merchant): `client_id` `cli_<hex24>` + `client_secret` `sk_<hex64>` (SHA-256 no banco), header `Authorization: ApiKey <id>:<secret>` ou `Basic`. Plug `ApiKeyAuth` (assigns `:api_key`, `:api_key_merchant_id`; IP allowlist + CIDR), `PermissionEnforcer` (regex `^/api/v2/merchants/\d+/<recurso>` → permissão), `RateLimiterPerKey` (Redis, 1000/min).
- Pipelines `:api_key_authenticated` (router.ex:71-75), `:api_key_or_jwt_authenticated` (77-79), `:tenant_scoped` (81-84) **definidas mas com ZERO `pipe_through`** → uma chave emitida hoje autentica nada.
- **`PixController` e `TedController` JÁ resolvem identidade pela API key** (`conn.assigns[:api_key_merchant_id]` antes do Guardian) — não mudam.
- Emissão JWT-only: `V2.ApiKeyController` (`POST /api/v2/merchants/:merchant_id/api-keys`, retorna o secret 1×), `V2.WebhookController`.
- Catálogo de scopes = `@valid_permissions` em `schemas/api_keys/api_key.ex` (pix:read/write, account:read/write, transfer:read/write, statement:read, payment:read/write).
- Webhooks de saída de produção: `Webhook.valid_events/0` (23 eventos: pix.charge.*, pix.payout.*, pix.refund.*, boleto.*, account.created/updated/blocked, transfer.completed/failed, sta.file.*); entrega Oban com HMAC `X-Monetarie-Signature: sha256=<hmac sobre "timestamp.body">`, replay, rotate-secret.
- OpenAPI/Swagger vivos e públicos: `GET /api/openapi`, `GET /api/swaggerui` (`api_spec/api_spec.ex`; securitySchemes `bearer_auth` + `api_key`=X-Api-Key/tenant — falta um 3º para `Authorization: ApiKey`).

**Verdade 3 — não há borda pública nem ingestão de KYC externo.**
- Só o ALB **interno** (`monetarie-internal-homolog-45`, HTTP:80). **Zero** ALB internet-facing, **zero** zona pública `monetarie.com.br`, **zero** WAFv2, **zero** CloudFront/API-GW, **zero** cert ACM p/ o domínio. SG `alb_public` (0.0.0.0/0:80/443) existe órfão (0 ENIs).
- Criar cliente+conta hoje só via Admin `MerchantsController.create/2` (User + Account kind=1 + wallet TigerBeetle em 1 chamada, **não transacional**, IDs aleatórios). `Onboarding.Promotion.promote/1` cria User+Member **sem** conta/wallet (`MemberHooks` morto).
- KYC = 100% Nextcode interno (`onboarding_applications`/`nextcode_proposals` + `DecisionEngine.evaluate/1`). **Não existe** endpoint para veredito de KYC externo.
- **Substrato de compliance já existe** (reuso): `Compliance.screen_sanctions/2` (OFAC/UN/EU/BCB/COAF → `sanctions_checks`), `create_pep_record/1`, `create_compliance_case/3` + `add_compliance_case_evidence/4`, `EddAssessment`, `AuditLog`. `Member` já tem colunas KYC (kyc_risk_level, kyc_last_reviewed_at, is_pep, doc_*, source_of_funds, account_purpose, metadata).

## 3. Decisões do dono (2026-06-22)

1. **Borda:** privado/VPN agora; borda pública (`api-h.monetarie.com.br` + WAF) = **fase 2**.
2. **Tipo de conta:** **definido pelo cliente** → `account_type` é parâmetro do endpoint (default conta de pagamento), validado contra os `kind` do `Account`.
3. **Escopo v1:** **catálogo completo**.

## 4. Arquitetura

### 4.1 Modelo de dados (migrações aditivas/nuláveis — sem backfill para chaves existentes)
- Nova tabela **`partners`** (uuid PK): `name`, `document` (cnpj), `entity_id` (uuid → entities, a SCD), `status`, `metadata`, timestamps. (Espelha o padrão `sub_merchants`.)
- **`api_keys` += `partner_id uuid NULL` → partners** (chave é "de parceiro" sse `partner_id` setado; mantém `merchant_id` p/ retrocompat).
- **`users` += `partner_id uuid NULL` → partners** (+ índice), carimbado na criação via API de parceiro. (Cardinalidade 1 cliente→1 parceiro criador.)
- Autorização: trocar a igualdade self-scope por **checagem de pertencimento** — chave de parceiro autoriza quando `target_user.partner_id == api_key.partner_id`. Helper compartilhado + `ApiKeyAuth` expõe `partner_id`.

### 4.2 Auth & rotas
- Novo escopo **`/api/partner/v1/*`** com `pipe_through :api_key_authenticated` (ApiKeyAuth → PermissionEnforcer → RateLimiterPerKey) + `:idempotent`/`:audited`.
- Header: `Authorization: ApiKey <client_id>:<client_secret>` (ou `Basic`). IP allowlist por chave. Rate-limit por chave.
- Scopes novos onde necessário: append em `@valid_permissions` + par `{método, regex}` no `PermissionEnforcer` (ex.: `customer:create`, `ted:write`).

### 4.3 Criação de cliente + abertura de conta
- Novo use-case **`Monetarie.UseCases.Accounts.OpenAccount.open_account/1`**: extrai e **conserta** o miolo do `MerchantsController.create` → **transacional**, usa `nextval('users_id_seq')`/`nextval('accounts_id_seq')`, `account_type`/`kind` **parametrizável** (cliente escolhe; default conta de pagamento), cria wallet via `Wallet.create/2-3`, carimba `users.partner_id`. `MerchantsController` passa a chamar o mesmo use-case.

### 4.4 Ingestão de KYC (parceiro-atestado)
- Novo use-case **`Monetarie.UseCases.Onboarding.PartnerKyc.attest/1`**: grava veredito do parceiro no `Member` (kyc_risk_level, kyc_last_reviewed_at=now, is_pep, doc_*, source_of_funds, account_purpose, `metadata` com payload bruto + responsável/CRM id + `status="approved_by_partner"`); abre `compliance_case` (source `partner_kyc`) + `add_compliance_case_evidence/4` por documento.
- **Dever regulatório não delegável (SCD):** o endpoint **roda sanctions + PEP do nosso lado** (Circular BACEN 3.978/2020 + Lei 9.613/98) **antes** de ativar; hit ⇒ conta **bloqueada/EDD** em vez de ativa.
- (Opcional, recomendado) tabela `partner_kyc_attestations` (partner_id, responsible_party, verdict_payload, doc hashes) p/ veredito atestado de primeira classe.

### 4.5 Catálogo v1 (todos sob `/api/partner/v1`, auth por API key, escopo do parceiro)
- **Clientes/KYC:** criar cliente + abrir conta (com `account_type` + veredito KYC do parceiro); consultar status; anexar documentos.
- **Contas:** consultar conta(s) do parceiro, status.
- **Saldo & extrato:** saldo, histórico/extrato, comprovante.
- **PIX:** chaves DICT (criar/listar/excluir), DICT lookup, enviar, QR/cobrança, devolução, status.
- **TED/transferência:** TED, transferência interna, favoritos.
- **Webhooks:** registrar/listar/rotacionar/testar/replay; catálogo de eventos.

### 4.6 OpenAPI → Postman
- Adicionar 3º securityScheme (`Authorization: ApiKey`) e operation specs nos endpoints `/api/partner/v1`. **Postman Collection** gerada a partir do `/api/openapi`, com exemplos + variáveis de ambiente + testes (scripts) por request.

## 5. Provisionamento (homolog)
- Criar **partner** + **usuário do sistema** + emitir **chave de API** (client_id/secret) para o cliente. Secret entregue 1× e guardado em **Secrets Manager `monetarie/homolog/partner/<cliente>/api_key`** (regra #9/#10 — nada de segredo em Markdown/PDF).

## 6. Testes & comprovação
- ExUnit (ConnCase + ExMachina) por endpoint: emitir `api_key` via `ApiKeys.create_api_key/2`, `put_req_header("authorization", "ApiKey id:secret")`, asserir `json_response`. Template: `test/monetarie_web/controllers/v2/api_key_controller_test.exs`.
- Migrações do zero em PG limpo (garantir boot) + suíte verde.
- Ponta-a-ponta no homolog (via VPN, `core-api-h.monetarie.internal`): criar cliente → abrir conta → saldo → PIX → webhook recebido. Evidência capturada.

## 7. Deploy
- `mix test` verde → build `docker buildx arm64` → ECR `…/monetarie/core-api:<tag>` → `ecs register-task-definition` (nova revisão, pois muda imagem/migrações) → `ecs update-service` → rodar migrações na Aurora. Conferir por **digest**.

## 8. Fora de escopo / fase 2
- Borda pública `api-h.monetarie.com.br` + WAFv2 + ACM + zona pública (decisão: privado por enquanto).
- Flutter nativo (launcher/splash) — pendência de branding pré-existente.
- Re-captura estrita de screenshots pós-rebrand (regra #11).

## 9. Itens verificados antes do build (validados, sem suposição)
- **`api_keys` existe e é montada por migração. RESOLVIDO.** O schema é criado pela migração squash `20260101000000_create_schema.exs`, que executa `psql -f priv/repo/sql/mon_core_schema_clean.sql` (dump de produção de 2026-03-07, `@disable_ddl_transaction`, `ON_ERROR_STOP=1`). Conferido no banco `mn_core` montado só por migração (254 migrações, 550 tabelas): `api_keys` presente, com colunas `id uuid` PK, `merchant_id integer NOT NULL` FK para `users(id)`, `client_id`/`client_secret_hash` unique, `permissions varchar[]`, `status`, `ip_whitelist[]`, `expires_at`, `last_used_at`, timestamps; `webhooks` referencia `api_keys`. As migrações novas rodam após a squash; no deploy via `Release.migrate()` a squash já está aplicada, então não dependem de `psql` no container.
- **Tipos de conta:** `Account.kind` é integer livre. A lista exposta no parâmetro `account_type` será definida lendo o mapeamento real de `kind` durante o build.
- **`entity_id`/`ledger_id` do `Wallet.create`:** confirmar o ledger por entidade no build (TODO existente em `MerchantsController:194`).

## 10. Guard-rails
- Conta AWS `990933657879`/sa-east-1, profile `vulcimonetarie`. Não tocar bastion/Lerian/TGW/DX/SG/rotas. Homolog `-h` only. Segredos só em Secrets Manager. Core não assina mensagens BACEN. Relatório cliente sem detalhes internos/segredos.
