# FINAUTON B2B — Guia de Integração REST (v1)

> Para o time de desenvolvimento do parceiro. Comece pelo [Quickstart](#quickstart) (15 minutos), incluído neste guia. Use o [OpenAPI](openapi.yaml) como contrato máquina-legível e o [catálogo de erros](ERROR_CATALOG.md) para diagnóstico.

Execução REAL, Builder Fee REAL e pagamentos REAIS exigem evidência e autorização
específicas. O [registro de homologação](https://github.com/BielSilver/Finauton/blob/ae59e8445493f1fa44dea59fc270a340222e6d44/docs/backend/quality-control.md) descreve
como o sistema registra esses resultados; exemplos de integração não autorizam operação financeira.

**Arquitetura: REST_API_FIRST / embedded.** O cliente final fica no app/site do **parceiro**. O backend do parceiro fala com o FINAUTON por esta API. O cliente B2B **não** tem login, senha, e-mail nem tela FINAUTON — a única página FINAUTON que ele vê é a **conexão hospedada** do provider (§8), para que segredo de corretora e assinatura de carteira nunca passem pelo parceiro.

```
APP DO PARCEIRO → BACKEND DO PARCEIRO → REST FINAUTON /v1
  → externalUserId → grants → setup/risco → provider → Engine → execução → performance → cobrança
```

## Índice
1. Autenticação · 2. Base URL e versionamento · 3. Identidade e autoridade · 4. Clientes · 5. Contrato · 6. Estratégias · 7. Risco e setup · 8. Providers e conexão hospedada (Binance, Hyperliquid) · 9. Automação · 10. Ordens e execução · 11. Posições e proteção · 12. Situação comercial · 13. Performance · 14. Cobrança e pagamento · 15. Eventos: feed e webhooks · 16. Saúde · 17. Erros · 18. Idempotência · 19. Limites · 20. Segurança · 21. Suspensões e manutenção de posição · 22. Exemplos completos · 23. Legado e o que não existe

---

## 1. Autenticação

O FINAUTON emite para o parceiro uma credencial de integração: `clientId` + `clientSecret` (o segredo aparece **uma vez**; guardamos só o hash). Rotação com janela de convivência e revogação imediata são feitas pelo FINAUTON.

```
Authorization: Basic base64(clientId:clientSecret)
```
(alternativa: `X-Client-Id` + `X-Client-Secret`). **Somente servidor → servidor.** Toda resposta traz `X-Request-Id` (você pode enviar o seu).

## 2. Base URL e versionamento

`https://<api-finauton>/v1/...`, JSON UTF-8. Campos novos podem aparecer em respostas (ignore o que não conhece); mudança incompatível vira `/v2`. Valores em USD (até 6 casas; cobranças em 2 casas); datas ISO-8601 UTC.

## 3. Identidade e autoridade

| Conceito | Quem define | Observação |
|---|---|---|
| Partner (`principalId`) | FINAUTON | Vem da credencial. Nunca envie no corpo. |
| Tenant (`tenantId`) | FINAUTON | Contrato único do parceiro (grants, cobrança, builder, origens de retorno). Criado junto com o parceiro, com o **mesmo id do parceiro**; você não precisa informá-lo. |
| Cliente (`externalUserId`) | Parceiro | O id que **você** já usa (1–120 chars). Chave = parceiro + externalUserId: o mesmo `1001` em outro parceiro é **outro cliente**. Não use e-mail. O FINAUTON nunca devolve id interno. |

| Autoridade | Campos |
|---|---|
| **PLATFORM_CONTROLLED** (só FINAUTON) | performance fee, mensalidade, período e carry, beneficiário e taxa de builder, recebedor de pagamentos, grants máximos de provider/estratégia/capability, envelope de risco do parceiro, suspensão comercial/contrato, confirmação de pagamento |
| **PARTNER_CONFIGURABLE_WITHIN_GRANT** | restringir o tenant (providers/estratégias ⊆ grant; envelope ≤ recebido) — `PUT /v1/tenants/:tenantId/restrictions`; suspender/reativar cliente |
| **CLIENT_CONFIGURABLE_WITHIN_ENVELOPE** | setup/risco ≤ envelope efetivo; estratégias ⊆ grant; automação; conexão do provider |

Tudo é conferido no servidor e falha fechado. Campos de escopo/receita no corpo → `403 SCOPE_FIELD_REJECTED`.

## 4. Clientes

| Método | Rota | Uso |
|---|---|---|
| POST | `/v1/users` `{externalUserId}` | Cria/upsert (idempotente pelo par). `tenantId` é opcional: sem ele, o cliente entra no contrato do parceiro. Mesmo par → `created:false`; outro tenant → `409 TENANT_MISMATCH`. **Idempotency-Key**. |
| GET | `/v1/users?tenantId=&status=&limit=` | Lista seus clientes |
| GET | `/v1/users/:externalUserId` | Vínculo e automações |
| GET | `/v1/users/:externalUserId/status` | Conexões, setup, situação comercial e automação por provider; posições abertas |
| POST | `/v1/users/:externalUserId/suspend` `{reason}` | Bloqueia novas entradas e escritas; leitura e manutenção continuam |
| POST | `/v1/users/:externalUserId/reactivate` `{reason}` | Reativa (não é "pode operar": todos os portões continuam valendo) |

## 5. Contrato

Cada parceiro tem **um contrato**, definido pelo FINAUTON no cadastro: providers, estratégias, teto de risco, cobrança (mensalidade por cliente, % de performance e/ou builder fee na Hyperliquid, em qualquer combinação) e origens de retorno. Na API ele aparece como o tenant de id igual ao do parceiro; você só lê.

| Método | Rota | Uso |
|---|---|---|
| GET | `/v1/tenants` | Seu contrato: grants, restrições, contrato vigente (percentual, período, carry, mensalidade), origens de retorno |
| GET | `/v1/tenants/:tenantId` | O mesmo, pelo id (igual ao do parceiro) |
| PUT | `/v1/tenants/:tenantId/restrictions` `{providers?, strategies?, envelope?, reason}` | Restringe dentro do grant (`null` remove). Nunca amplia (`GRANT_ESCALATION` / `RISK_ENVELOPE_EXCEEDED`). Envelope reduzido abaixo do setup de um cliente deixa esse cliente **OUTSIDE_ENVELOPE** (sem novas entradas) até ele reduzir o setup; nada é alterado automaticamente. |

## 6. Estratégias

- `GET /v1/strategies` — catálogo publicado concedido.
- `GET /v1/users/:id/strategies` → `{granted, selected, selectionMode}`.
- `PUT /v1/users/:id/strategies` `{strategyIds: [...] | null}` — ⊆ grant (`GRANT_ESCALATION`), só publicadas (`STRATEGY_NOT_AVAILABLE`); `null` = todas as concedidas; `[]` = nenhuma (sem entradas).

Efetivas = publicadas ∩ grant do tenant ∩ restrição do parceiro ∩ seleção do cliente.

## 7. Risco e setup

| Método | Rota |
|---|---|
| GET | `/v1/users/:id/setup?providerId=BINANCE` |
| POST | `/v1/users/:id/setup/preview` (não grava; devolve `safety.fingerprint` + alertas) |
| PUT | `/v1/users/:id/setup` (declaração completa; **Idempotency-Key**) |
| PUT | `/v1/users/:id/risk` (**Idempotency-Key**) |

Campos do setup: `providerId, mode ("REAL"), enabled, leverage, orderSizeUsd, maxConcurrentTrades, maxMarginPerTradeUsd, maxDailyLossUsd, capitalBase, maxLossPerTrade, entryTimeoutMinutes, allowedSymbols?, autoStopLoss?, autoTakeProfit?, autoBreakEven?, maxOpenNotionalUsd?, maxSameDirection?, maxAdverseEntryPercent?, riskAcknowledgement?{fingerprint, codes}`.

Regras: envelope efetivo (`RISK_ENVELOPE_EXCEEDED`); setup completo para ligar (`SETUP_INCOMPLETE`, ver `setupStatus.issues`); aceite dos alertas da combinação exata (`RISK_ACK_REQUIRED` → use o `fingerprint` da prévia); trava diária (`DAILY_LOSS_HALT`); **PAPER não é oferecido** (`PAPER_NOT_GRANTED`); conta precisa estar conectada para `REAL` (`API_NOT_CONFIGURED`).

## 8. Providers e conexão hospedada

MVP: **BINANCE** (USDⓈ-M Futures) e **HYPERLIQUID** (perps).

- `GET /v1/users/:id/providers` → por provider: `connectionScheme (API_KEY|AGENT_WALLET)`, `connectionFlow (HOSTED)`, `connection` (`NOT_CONNECTED`, `CONNECT_PENDING`, `CONNECTED`, `ACCESS_LOST`, `RECOVERING`, `REVOKED`), `permissions`, `eligibleForNewEntries`, `commercial`, `builder` (Hyperliquid: `required`, `feeTenthsBps`).
- `POST /v1/users/:id/connect-sessions` `{providerId, returnUrl?, purpose?}` → `201 {sessionId, connectUrl, expiresAt, status, scheme, purpose}`.
- `GET /v1/users/:id/connect-sessions/:sessionId` → `status`: `PENDING | CONNECTED | FAILED | EXPIRED | CANCELLED`, `opened`, `failureCode`.
- `POST /v1/users/:id/accounts/:provider/test` — testa a conexão guardada (leitura).
- `DELETE /v1/users/:id/accounts/:provider` — revoga a conexão no FINAUTON (bloqueia novas entradas; histórico e trades preservados).

**Sessão:** `connectUrl = https://<app-finauton>/connect#t=<token>`. Token aleatório (32 bytes), só o hash no FINAUTON, **uso único**, **15 min**, até 5 tentativas, amarrado a parceiro + tenant + cliente + provider. Fica no **fragmento** (não vai a servidores/logs/Referer) e a página o apaga da barra. Abra no app (redirect/aba/webview); **não registre em log**. Nova sessão cancela a pendente do mesmo cliente+provider. Eventos: `provider.connected`, `provider.connect_failed`.

**`returnUrl`:** aceita só origens **https** registradas pelo FINAUTON no seu contrato (comparação exata de origem). `javascript:`, `data:`, domínio estrangeiro, subdomínio parecido, credenciais na URL → `RETURN_URL_NOT_ALLOWED`. É exibido como botão "Voltar ao app do parceiro" (sem redirect automático).

### 8.1 Binance (API_KEY)
A página orienta o cliente: chave **HMAC** exclusiva; **Enable Futures** ligado; **Enable Withdrawals desligado**; **restrição de IP** com o IP de saída do FINAUTON (sem isso a Binance só concede leitura); Futuros em One-way, Single-Asset, margem Isolada. Chave/segredo vão do navegador do cliente ao FINAUTON, que testa em leitura, cifra (AES-256-GCM) e nunca devolve. Se a chave permitir saque: aviso na página e `permissions.canWithdraw=true`.

### 8.2 Hyperliquid (AGENT_WALLET + builder fee)
1. Cliente conecta **a própria carteira** na página.
2. O FINAUTON gera a carteira-agente; o cliente assina **ApproveAgent** (EIP-712). A agente **opera, não saca nem transfere**. O FINAUTON guarda só a chave da agente, cifrada; chave privada/seed do cliente nunca saem da carteira dele.
3. Para taxa positiva, o cliente assina **ApproveBuilderFee** para o beneficiário FIN único, com o teto efetivo definido pela plataforma/contrato. Aprovação insuficiente recusa novas entradas (`BUILDER_APPROVAL_REQUIRED`). Configuração com taxa zero válida não exige aprovação de taxa positiva; não equivale a configuração ausente.
4. Se a configuração efetiva exigir nova aprovação, use `POST connect-sessions {providerId:"HYPERLIQUID", purpose:"BUILDER_APPROVAL"}` (a conta precisa estar conectada; a mesma carteira assina).

Decisão implementada em 08/10: PLATFORM define o beneficiário FIN único; CONTRACT
pode variar a taxa B2B, mas não usar outra carteira. Configuração válida é obrigatória
para novas entradas B2C/B2B; `BUILDER_CONFIG_REQUIRED`/`BUILDER_CONFIG_INVALID` não
autorizam fallback gratuito. Manutenção com `reduceOnly` preserva suas exceções.
Referência: [Builder único](B2B_INTEGRATION_GUIDE.md).

Revogar: o cliente pode revogar a agente na própria Hyperliquid (ApproveAgent de outra agente/expiração) e o parceiro pode `DELETE …/accounts/HYPERLIQUID`.

## 9. Automação

- `PUT /v1/users/:id/automation` `{providerId, strategyId, enabled, risk{leverage, maxMarginPerTradeUsd, maxConcurrentTrades, maxDailyLossUsd}, conditions?{symbols, tradingWindowUtc, minConfidence}}` (**Idempotency-Key**). Corpo sempre completo: `risk` é obrigatório também com `enabled:false` (desligar). Evento `automation.updated`.
- `GET /v1/users/:id/automation` → por provider: `setupEnabled, mode, haltedToday, automation, newEntries (ALLOWED|BLOCKED), blockReason, positionMaintenance: "CONTINUES"`.

Um sinal da Engine só vira ordem se **todos** os portões permitirem: parceiro/tenant/cliente ativos; provider e estratégia no grant efetivo; estratégia selecionada; ativo no envelope/automação; risco dentro do envelope; setup válido e sem trava diária; P4 (contrato ativo, sem obrigação vencida); P5 (provider homologado, acesso ok); builder válido e, para taxa positiva, aprovado (Hyperliquid). Nenhum sinal abre posição só porque o `externalUserId` existe.

## 10. Ordens e execução

- `POST /v1/orders/prepare` · `POST /v1/orders/execute` `{externalUserId, ...intent}` (**Idempotency-Key**, cota `ordersPerMinute`).
- `GET /v1/orders/:intentId?externalUserId=&providerId=` · `POST /v1/orders/:intentId/reconcile`.
- `GET /v1/users/:id/intents?status=OPEN|ALL`.

Abertura passa por P3/P4/P5/builder. Fechar, reduzir (`reduceOnly`) e proteger continuam permitidos com o cliente suspenso ou com cobrança vencida.

## 11. Posições e proteção

- `GET /v1/users/:id/positions?providerId=` — posições no provider.
- `GET /v1/users/:id/execution-status` — por posição: `protection.status` (`PROTECTED`, `PROTECTION_DEGRADED`, `PROVIDER_ACCESS_LOST`, `UNKNOWN`), stop/alvo registrados e motivo; e `providers[].access`.
- `GET /v1/users/:id/account?providerId=` — saldo/conta.

## 12. Situação comercial

`GET /v1/users/:id/commercial` → por provider: `allowed, state (ACTIVE|GRACE|SUSPENDED|REVOKED|BLACKLISTED|NO_ENTITLEMENT|ACCOUNT_INACTIVE), reasonCode, graceUntil, source`. Ver `ERROR_CATALOG.md`.

## 13. Performance

**Base financeira: `providerNetRealizedPnlUsd` canônico.** Para cada parcela de fechamento REAL, o FINAUTON normaliza UMA vez o resultado econômico realizado a partir dos fatos do provider (sem dedução dupla — semântica por provider em `PROVIDER_PNL_SEMANTICS.md`):

| Provider | Fato do provider | Canônico |
|---|---|---|
| BINANCE | `realizedPnl` (sem comissão e sem funding), `commission`, `FUNDING_FEE` | `realizedPnl − comissões (entrada+saída) + funding` |
| HYPERLIQUID | `closedPnl` (sem taxa e sem funding), `fee` (inclui `builderFee`), `userFunding` | `closedPnl − (fee − builderFee) + funding` |

- **Builder fee é receita FINAUTON separada**: NÃO reduz o PnL do cliente, o líquido, o carry, o cobrável nem a performance fee.
- Sem fatos suficientes do provider (ex.: comissão em BNB, funding indisponível), a parcela fica **pendente** e **não é faturada** até a reconciliação confirmar.
- **Depósito e saque não são performance.**

**Fórmula:** `periodPerformance = Σ providerNetRealizedPnlUsd confirmado no período`.
- **NO_CARRY:** `cobrável = max(0, periodPerformance)`.
- **CARRY:** `total = periodPerformance + carryIn (≤0)`; `cobrável = max(0, total)`; `carryOut = min(0, total)`.
- `performanceFee = cobrável × percentual`, arredondada meio-para-cima ao centavo. Demais valores com 6 casas (micro-USD).

Ex. 20%: CARRY −100/+60/+100 → cobra 0/0/60 (fee 0/0/12), carry −100/−40/0; NO_CARRY → 0/60/100 (fee 0/12/20).

**Períodos (UTC, [início, fim)):** WEEKLY segunda 00:00 → segunda seguinte 00:00; MONTHLY dia 1 00:00 → dia 1 seguinte. O instante da virada pertence ao período novo.

- `GET /v1/users/:id/performance` → `current` (`status: "LIVE_ESTIMATE"`: período aberto, estimativa — não é cobrança) + `closedStatements`.
- `GET /v1/users/:id/statements` → extratos fechados (`status: "CLOSED"`, `kind: ORIGINAL|ADJUSTMENT`, `contractVersion`, `policy`, `period`, percentual, carry, fee, `creditUsd`). Campos de valor:
  - `basis: "CANONICAL_NET_REALIZED_PNL"`, `providerNetRealizedPnlUsd` / `netPerformanceUsd` — **CANONICAL NET REALIZED = base financeira**;
  - `auditComponents { role: "AUDIT_EVIDENCE", providerRealizedPnlUsd, venueFeesUsd, fundingUsd }` — **RAW COMPONENTS = evidência/auditoria** (não some ao líquido de novo);
  - `builderFees { role: "FINAUTON_REVENUE_SEPARATE", builderFeesUsd }` — **BUILDER FEE = receita separada FINAUTON**.
  - Não recalcule o canônico no seu app: exiba o `providerNetRealizedPnlUsd` que o FINAUTON devolve.

O extrato congela a versão do contrato que valia no **início** do período. Mudança de contrato vale a partir do **próximo período**. **Troca WEEKLY↔MONTHLY** pode ser pedida a qualquer momento e fica **agendada**: o período vigente fecha inteiro na periodicidade antiga; a nova começa no próximo boundary (o 1º período novo vai do boundary até o limite natural seguinte — ex.: WEEKLY→MONTHLY pedido numa quarta: a semana fecha na segunda; o 1º período mensal vai dessa segunda ao dia 1). Sem segmentação, sem retroatividade; o carry atravessa a troca. Fato que chega depois do fechamento gera **ADJUSTMENT** automático (e, em CARRY, recalcula os períodos seguintes já fechados) — sem cobrar duas vezes: diferença positiva vira nova obrigação; negativa reduz a obrigação aberta ou vira crédito abatido na próxima performance fee.

## 14. Cobrança e pagamento

| Provider | Monetização |
|---|---|
| BINANCE | mensalidade (clientes com Binance conectada) + performance fee |
| HYPERLIQUID | builder fee (na ordem, aprovada pelo cliente) + performance fee (sem mensalidade, salvo contrato) |

- `GET /v1/users/:id/billing` → `status (ACTIVE|DUE|GRACE|SUSPENDED)`, `policy` (versão, mensalidade, providers da mensalidade, moeda, percentual, período, carry, vencimento, grace 24 h), `builder`, `payment` (redes/tokens), `openObligations`, `newEntriesBlockedBy`.
- `GET /v1/users/:id/obligations`.
- Ciclo: vencimento → **GRACE 24 h** (entradas seguem) → **SUSPENDED** (novas entradas negadas; manutenção continua) → pagamento confirmado → reativação automática (`commercial.reactivated`).
Dois meios de pagamento convergem para a **mesma** obrigação (não há dois billings):

**A) `EVM_TRANSFER` (USDT/USDC, rede EVM aceita pelo recebedor FINAUTON)**
  1. `POST /v1/users/:id/obligations/:obligationId/payment-instructions` `{"method":"EVM_TRANSFER", networkId, token, payerAddress}` (`method` opcional; padrão) (**Idempotency-Key**) → valor **exato** em unidades mínimas (inclui etiqueta única), destinatário FINAUTON (versão do recebedor), validade.
  2. O pagador envia exatamente esse valor, da carteira declarada.
  3. `POST /v1/users/:id/payments/:paymentId/confirm` `{txHash}` (**Idempotency-Key**) → o FINAUTON verifica on-chain (chainId, contrato do token, destinatário, pagador, valor, após a instrução, confirmações). `200` confirmado; `202` com `code` (aguardando confirmações, valor abaixo etc.). Uma transação credita **uma** cobrança (`TX_ALREADY_USED`).
  4. `GET /v1/users/:id/payments/:paymentId`.

**B) `HYPERLIQUID_USD_SEND` (USDC na Hyperliquid; obrigação em USDC)**
  1. `POST …/payment-instructions` `{"method":"HYPERLIQUID_USD_SEND", "returnUrl"?}` (**Idempotency-Key**) → `payment` + `paymentUrl` (página FIN hospedada; token no fragmento, 15 min, uso único, `returnUrl` só das origens registradas).
  2. Abra `paymentUrl` para o cliente. Ele conecta a **carteira PRINCIPAL** da conta Hyperliquid conectada (outra carteira → `WALLET_MISMATCH`) e assina um `usdSend` com valor exato para a carteira FINAUTON.
  3. O FINAUTON repassa a assinatura do cliente e **verifica no ledger da Hyperliquid**: só credita um delta `send` (USDC) com o **mesmo nonce** assinado nesta sessão, do pagador para a carteira FINAUTON, valor exato → `obligation.paid` → reativação P4 (`commercial.reactivated`) se não houver outro bloqueio. Transferência compatível sem nonce (`internalTransfer`) não é creditada automaticamente (`PAYMENT_LEDGER_BINDING_AMBIGUOUS`). Não há `txHash` a informar (`PAYMENT_METHOD_MISMATCH`).
  - **Status: LOCAL_READY / REAL_VALIDATION_REQUIRED** — em QA o método existe, mas só será oferecido em produção depois da validação REAL (fila M7).
  4. **Não é débito automático:** cada cobrança exige uma nova assinatura humana.

**Sobrepagamento** voluntário (EVM): a obrigação é quitada e o excedente vai para **revisão manual** (`PAYMENT_OVERPAID_MANUAL_REVIEW`) — **sem crédito automático**. Isso é diferente do **crédito de ajuste**: quando um ADJUSTMENT reduz uma performance fee já paga, a diferença vira `creditUsd`, abatido na próxima **performance fee**.

### 14.1 Propriedade de segurança: autoridade de trading ≠ autoridade de transferência

- **Carteira-agente** (aprovada pelo cliente; chave guardada cifrada pelo FIN): **só trading**. Não saca, não transfere.
- **Carteira principal** (só o cliente): assina `usdSend` **quando o cliente decide pagar**, na página FIN.
- **Nenhuma credencial permanente do FINAUTON pode executar `usdSend`.** O FIN nunca guarda chave privada principal nem seed; só monta o que o cliente vai assinar, repassa a assinatura e verifica o ledger.
- Binance: a API key do cliente é exigida **sem saque** (`Enable Withdrawals` desligado); o FIN não move fundos da conta do cliente.

O parceiro **não** cria extrato, não marca pago e não altera percentual, mensalidade, builder ou recebedor.

## 15. Eventos: feed e webhooks

Tipos: `client.updated`, `provider.connected`, `provider.connect_failed`, `provider.disconnected`, `provider.access_lost`, `automation.updated`, `position.opened`, `position.updated`, `position.closed`, `protection.degraded`, `statement.created`, `statement.adjusted`, `obligation.created`, `obligation.paid`, `commercial.grace`, `commercial.suspended`, `commercial.reactivated`.

Formato (feed e webhook):
```json
{ "eventId": "6ac5…", "eventType": "obligation.created", "occurredAt": "2026-10-07T03:33:30.751Z",
  "principalId": "…", "tenantId": "PARCEIRO_A", "externalUserId": "1001", "data": { … } }
```
`eventId` é imutável (deduplique por ele); um fato = um evento.

**Feed (polling):** `GET /v1/events?after=<eventId>&limit=&type=` → `{events, next}`. Guarde `next`; recupera tudo após indisponibilidade.

**Webhooks (push):** o FINAUTON cadastra seu endpoint **HTTPS** e entrega o segredo **uma vez**. Cada POST traz:
- `X-Finauton-Event-Id`, `X-Finauton-Event-Type`, `X-Finauton-Timestamp`
- `X-Finauton-Signature: t=<unix>,v1=<hex HMAC-SHA256(segredo, "<t>.<corpo>")>`

Valide: recalcule o HMAC sobre `t + "." + corpo cru`, compare em tempo constante e recuse `|agora − t| > 300 s`. Responda **2xx** rápido (processamento assíncrono). Falha (não-2xx/timeout 10 s) → retry exponencial (1 min, 2, 4… até 6 h; 12 tentativas) com o **mesmo** `eventId`; depois `DEAD` (o FINAUTON pode reenviar). Seu endpoint fora do ar nunca afeta execução, posições ou cobrança.

```js
const [t, v1] = sig.match(/^t=(\d+),v1=([a-f0-9]{64})$/).slice(1);
const ok = Math.abs(Date.now()/1000 - Number(t)) <= 300 &&
  crypto.timingSafeEqual(crypto.createHmac('sha256', SECRET).update(`${t}.${rawBody}`).digest(), Buffer.from(v1, 'hex'));
```

## 16. Saúde

`GET /v1/health` → `{api: "UP", providers: [{providerId, status, realExecution: "ENABLED"|"NOT_VALIDATED"}]}`. Sem detalhes de infraestrutura. `NOT_VALIDATED` = provider ainda sem homologação REAL.

## 17. Erros

Ver `ERROR_CATALOG.md` (HTTP, código, significado, se repetir, ação).

## 18. Idempotência

Mutações exigem `Idempotency-Key` (8–128 caracteres `A-Z a-z 0-9 . _ : -`): criar cliente, setup, risco, automação, ordens, instrução e confirmação de pagamento. Mesma chave + mesmo corpo → mesma resposta (persistida, sobrevive a reinício); mesma chave + corpo diferente → `409 IDEMPOTENCY_CONFLICT`; em andamento → `409 IDEMPOTENCY_IN_PROGRESS`. A confirmação de pagamento é reconsultável: reenviar o mesmo `txHash` nunca credita duas vezes.

## 19. Limites

Por parceiro, contados no banco: `requestsPerMinute`, `ordersPerMinute` → `429 RATE_LIMITED` (só o seu parceiro é afetado). Use backoff exponencial. Providers têm limites próprios; o FINAUTON distribui contas reais por IP de saída.

## 20. Segurança

- Credencial só no seu servidor; rotacione; peça revogação em incidente.
- Nunca colete segredo de corretora, seed ou chave privada no seu app: use a conexão hospedada.
- Não registre `connectUrl` nem o segredo do webhook em logs.
- Valide a assinatura de todo webhook.
- Fora do seu escopo → 404.

## 21. Suspensões e manutenção de posição

| Situação | Nova entrada | Manutenção (stop/TP/parcial/fechar/emergência/reconciliação) | Leitura REST | Cobrança |
|---|---|---|---|---|
| Cliente suspenso | não | sim | sim | segue apurando |
| Tenant suspenso / contrato suspenso | não | sim | sim | segue |
| Comercial suspenso (obrigação vencida) | não | sim | sim | segue |
| Revogado / blacklist | não | sim | sim | segue |
| Acesso ao provider perdido | não | sim (reconexão/reconciliação) | sim | segue |
| Trava de risco diária | não (até o dia seguinte UTC) | sim | sim | segue |
| Automação desligada | não (por sinal) | sim | sim | segue |
| Setup fora do envelope | não | sim | sim | segue |

Regra soberana: bloquear entrada **nunca** abandona a manutenção de uma posição existente.

## 22. Exemplos completos

```bash
API=https://api.finauton.example
AUTH="Authorization: Basic $(printf '%s:%s' "$FIN_CLIENT_ID" "$FIN_CLIENT_SECRET" | base64)"
H='content-type: application/json'

# Contrato + cliente
curl -sX POST "$API/v1/users" -H "$AUTH" -H "$H" -H "Idempotency-Key: user-1001-0001" -d '{"externalUserId":"1001"}'
curl -s "$API/v1/tenants" -H "$AUTH"

# Estratégias
curl -s "$API/v1/users/1001/strategies" -H "$AUTH"
curl -sX PUT "$API/v1/users/1001/strategies" -H "$AUTH" -H "$H" -d '{"strategyIds":["FUTURES_MAIN_V1"]}'

# Conectar Binance (abra connectUrl no app do cliente; depois consulte)
curl -sX POST "$API/v1/users/1001/connect-sessions" -H "$AUTH" -H "$H" -d '{"providerId":"BINANCE","returnUrl":"https://app.parceiro.example/conta"}'
curl -s "$API/v1/users/1001/connect-sessions/$SESSION_ID" -H "$AUTH"

# Conectar Hyperliquid (carteira + ApproveAgent + ApproveBuilderFee na página FINAUTON)
curl -sX POST "$API/v1/users/1001/connect-sessions" -H "$AUTH" -H "$H" -d '{"providerId":"HYPERLIQUID"}'

# Risco: prévia → setup com aceite
curl -sX POST "$API/v1/users/1001/setup/preview" -H "$AUTH" -H "$H" -d '{"providerId":"BINANCE","mode":"REAL","enabled":true,"leverage":3,"orderSizeUsd":10,"maxConcurrentTrades":1,"maxMarginPerTradeUsd":10,"maxDailyLossUsd":10,"capitalBase":100,"maxLossPerTrade":2,"entryTimeoutMinutes":30}'
curl -sX PUT "$API/v1/users/1001/setup" -H "$AUTH" -H "$H" -H "Idempotency-Key: setup-1001-0001" -d '{…mesmo corpo…,"riskAcknowledgement":{"fingerprint":"<da prévia>","codes":["<códigos da prévia>"]}}'

# Automação
curl -sX PUT "$API/v1/users/1001/automation" -H "$AUTH" -H "$H" -H "Idempotency-Key: auto-1001-0001" -d '{"providerId":"BINANCE","strategyId":"FUTURES_MAIN_V1","enabled":true,"risk":{"leverage":3,"maxMarginPerTradeUsd":10,"maxConcurrentTrades":1,"maxDailyLossUsd":10}}'

# Monitorar
curl -s "$API/v1/users/1001/status" -H "$AUTH"
curl -s "$API/v1/users/1001/execution-status" -H "$AUTH"
curl -s "$API/v1/users/1001/positions?providerId=BINANCE" -H "$AUTH"
curl -s "$API/v1/users/1001/performance" -H "$AUTH"
curl -s "$API/v1/users/1001/billing" -H "$AUTH"

# Pagar uma obrigação
curl -sX POST "$API/v1/users/1001/obligations/$OBLIGATION_ID/payment-instructions" -H "$AUTH" -H "$H" -H "Idempotency-Key: pay-1001-0001" -d '{"networkId":"arbitrum","token":"USDT","payerAddress":"0x…"}'
curl -sX POST "$API/v1/users/1001/payments/$PAYMENT_ID/confirm" -H "$AUTH" -H "$H" -H "Idempotency-Key: confirm-1001-0001" -d '{"txHash":"0x…"}'

# Eventos
curl -s "$API/v1/events?after=$CURSOR&limit=100" -H "$AUTH"
```

Smoke completo: `tools/b2b-partner-harness/harness.mjs` (usa só esta API).

## 23. Legado e o que não existe

- `PUT /v1/users/:id/accounts/:provider` com segredo no corpo: **legado/deprecated**, só para clientes antigos sem tenant. Cliente de tenant → `403 HOSTED_CONNECT_REQUIRED`.
- Não existem no MVP: login/cadastro FINAUTON para cliente B2B; portal FINAUTON do parceiro; PAPER no B2B; Aster/MEXC em REAL; débito automático via `sendUsd` (exige assinatura da carteira principal a cada transferência — `SENDUSD = NOT_SUITABLE_FOR_AUTOMATIC_PERFORMANCE_COLLECTION`; o pagamento `usdSend` **iniciado e assinado pelo cliente** é suportado — §14 B); detecção automática de pagamento EVM sem `txHash` (backlog).

---


<a id="quickstart"></a>

<!-- INICIO FONTE: quickstart -->

<a id="quickstart--finauton-b2b--quickstart-15-minutos"></a>

## FINAUTON B2B — Quickstart (15 minutos)

> **PRÉ-PRODUÇÃO (QA).** O contrato REST está estável para integração. Execução REAL em Binance/Hyperliquid, Builder Fee REAL e pagamentos REAIS estão **em validação** (`REAL VALIDATION IN PROGRESS`) — não apresente como produção.

Você vai integrar o **seu backend** à REST `/v1`. Seus clientes continuam no **seu app**; o FINAUTON nunca pede login a eles. Referência completa: [autenticação e contrato REST](#1-autenticação) · [OpenAPI](openapi.yaml) · [erros](ERROR_CATALOG.md).

<a id="quickstart--1-credenciais"></a>

### 1. Credenciais

O FINAUTON envia (canal seguro): `clientId`, `clientSecret` e a URL da API. Não há `tenantId` para configurar: o contrato é um só por parceiro. Guarde o segredo no cofre do **servidor**.

```bash
export API=https://api.finauton.example      # QA: o endereço que o FINAUTON informar
export AUTH="Authorization: Basic $(printf '%s:%s' "$FIN_CLIENT_ID" "$FIN_CLIENT_SECRET" | base64)"
export H='content-type: application/json'
```

<a id="quickstart--2-autenticar-teste"></a>

### 2. Autenticar (teste)

```bash
curl -s "$API/v1/health" -H "$AUTH"     # {"api":"UP","providers":[…]}
curl -s "$API/v1/tenants" -H "$AUTH"    # seu contrato: grants, cobrança e origens de retorno
```
`401` = credencial errada/revogada.

<a id="quickstart--3-criar-o-cliente-upsert"></a>

### 3. Criar o cliente (upsert)

Use o **seu** id do usuário como `externalUserId` (nunca e-mail).

```bash
curl -sX POST "$API/v1/users" -H "$AUTH" -H "$H" -H "Idempotency-Key: user-1001-0001" \
  -d '{"externalUserId":"1001"}'
# 201 {"externalUserId":"1001","tenantId":"<SEU_PARCEIRO>","status":"ACTIVE","created":true}
```
Repetir o mesmo par devolve `created:false`.

<a id="quickstart--4-configurar-o-risco"></a>

### 4. Configurar o risco

```bash
SETUP='{"providerId":"BINANCE","mode":"REAL","enabled":false,"leverage":3,"orderSizeUsd":10,"maxConcurrentTrades":1,"maxMarginPerTradeUsd":10,"maxDailyLossUsd":10,"capitalBase":100,"maxLossPerTrade":2,"entryTimeoutMinutes":30}'
curl -sX POST "$API/v1/users/1001/setup/preview" -H "$AUTH" -H "$H" -d "$SETUP"
# guarde safety.fingerprint e safety.flags[].code
```
Valores acima do envelope → `RISK_ENVELOPE_EXCEEDED`. Gravar o setup em `REAL` exige a conta conectada (passo 6); depois:
```bash
curl -sX PUT "$API/v1/users/1001/setup" -H "$AUTH" -H "$H" -H "Idempotency-Key: setup-1001-0001" \
  -d '{…SETUP com "enabled":true…,"riskAcknowledgement":{"fingerprint":"<fingerprint>","codes":["<code>",…]}}'
```

<a id="quickstart--5-escolher-a-estratégia"></a>

### 5. Escolher a estratégia

```bash
curl -s "$API/v1/users/1001/strategies" -H "$AUTH"     # granted / selected
curl -sX PUT "$API/v1/users/1001/strategies" -H "$AUTH" -H "$H" -d '{"strategyIds":["FUTURES_MAIN_V1"]}'
```

<a id="quickstart--6-conectar-o-provider-página-hospedada-finauton"></a>

### 6. Conectar o provider (página hospedada FINAUTON)

```bash
curl -sX POST "$API/v1/users/1001/connect-sessions" -H "$AUTH" -H "$H" \
  -d '{"providerId":"BINANCE","returnUrl":"https://app.seudominio.example/conta"}'
# 201 {"sessionId":"…","connectUrl":"https://app.finauton.example/connect#t=…","status":"PENDING","expiresAt":"…"}
```
- Entregue o `connectUrl` ao app do cliente e abra-o (redirect/aba/webview). **Não grave em log.** Vale 15 min, uma vez.
- O cliente informa a chave/segredo da Binance (ou, na Hyperliquid, conecta a carteira e assina) **na página FINAUTON**. Você nunca recebe segredo.
- `returnUrl` precisa ser de uma origem https registrada pelo FINAUTON no seu contrato.

Acompanhe:
```bash
curl -s "$API/v1/users/1001/connect-sessions/$SESSION_ID" -H "$AUTH"   # PENDING → CONNECTED
curl -s "$API/v1/users/1001/providers" -H "$AUTH"                       # connection: CONNECTED
```
(ou receba o evento `provider.connected`).

<a id="quickstart--7-ligar-a-automação"></a>

### 7. Ligar a automação

```bash
curl -sX PUT "$API/v1/users/1001/automation" -H "$AUTH" -H "$H" -H "Idempotency-Key: auto-1001-0001" \
  -d '{"providerId":"BINANCE","strategyId":"FUTURES_MAIN_V1","enabled":true,"risk":{"leverage":3,"maxMarginPerTradeUsd":10,"maxConcurrentTrades":1,"maxDailyLossUsd":10}}'
curl -s "$API/v1/users/1001/automation" -H "$AUTH"   # providers[].newEntries: ALLOWED | BLOCKED (+ providers[].blockReason)
```
Desligar (o corpo é sempre completo — `risk` é obrigatório também para desligar; reenvie o atual):
```bash
curl -sX PUT "$API/v1/users/1001/automation" -H "$AUTH" -H "$H" -H "Idempotency-Key: auto-1001-0002" \
  -d '{"providerId":"BINANCE","strategyId":"FUTURES_MAIN_V1","enabled":false,"risk":{"leverage":3,"maxMarginPerTradeUsd":10,"maxConcurrentTrades":1,"maxDailyLossUsd":10}}'
```
`providers[].newEntries` fica `BLOCKED` com o motivo até tudo estar pronto: `SETUP_DISABLED` (grave o setup `REAL` com `"enabled":true` e o aceite do passo 4 — exige a conexão do passo 6 concluída), `AUTOMATION_OFF`, motivos comerciais (`CONTRACT_REQUIRED`, `OBLIGATION_OVERDUE`…) ou `CLIENT_SUSPENDED`. Na Hyperliquid, configuração de builder válida é obrigatória; para taxa positiva, o cliente também precisa aprová-la na página de conexão.

<a id="quickstart--8-monitorar"></a>

### 8. Monitorar

```bash
curl -s "$API/v1/users/1001/status" -H "$AUTH"             # conexões, comercial, automação
curl -s "$API/v1/users/1001/execution-status" -H "$AUTH"   # posições + proteção
curl -s "$API/v1/users/1001/positions?providerId=BINANCE" -H "$AUTH"   # posições no provider (422 CREDENTIALS_MISSING sem conexão)
curl -s "$API/v1/users/1001/performance" -H "$AUTH"        # período aberto (estimativa) + extratos
```

<a id="quickstart--9-cobrança-e-eventos"></a>

### 9. Cobrança e eventos

```bash
curl -s "$API/v1/users/1001/billing" -H "$AUTH"            # política, status, obrigações
curl -s "$API/v1/users/1001/obligations" -H "$AUTH"
curl -s "$API/v1/events?limit=100" -H "$AUTH"              # guarde "next" e use ?after=
```
Webhooks: peça ao FINAUTON para registrar sua URL https; valide `X-Finauton-Signature` (ver guia §15).

Pronto. Para um teste de fumaça completo: `node tools/b2b-partner-harness/harness.mjs smoke 1001` com `FIN_API`, `FIN_CLIENT_ID` e `FIN_CLIENT_SECRET`.

<!-- FIM FONTE: quickstart -->
