# Autenticação

A API externa da Monetarie utiliza um modelo de segurança em três camadas principais de autenticação -- API Key + Secret, assinatura HMAC-SHA512 por requisição (apenas em `POST`) e whitelist de IP obrigatória -- complementadas por validação de conteúdo, rate limiting e idempotência.

## Visão Geral das Validações

A requisição HTTP passa pelas validações abaixo, **nesta ordem**. Qualquer reprovação retorna erro antes da lógica de negócio:

```
POST /api/external/...
  │
  ├─ 1. Content-Type ──────── Não é application/json nem multipart/form-data? → 415
  ├─ 2. X-Key-Case (KeyCase) ─ Converte params/response snake_case ↔ camelCase (opcional)
  ├─ 3. API Key + Secret ───── Credenciais ausentes/inválidas? → 401 | API Key inativa? → 401 | API Key expirada? → 401
  ├─ 4. IP Whitelist ───────── Whitelist vazia? → 403 "ip whitelist required" | IP fora? → 403 "ip not allowed" | Conta inativa? → 403
  ├─ 5. HMAC-SHA512 (POST) ─── Header `hmac` ausente? → 401 | Assinatura inválida? → 401 | Body inválido? → 400 | API Key sem secret? → 403
  ├─ 6. Rate Limiting ─── Mais de 90.000 req/min por IP? → 429 Retry-After: 60
  ├─ 7. Idempotency (POST) ─── Chave > 256 chars? → 400 | Replay em 24h? → retorna body cacheado + X-Idempotent-Replay: true
  └─ 8. Permissão da API Key ───── API Key sem a permissão exigida pela rota? → 403
       │
       └─ Requisição aceita → Lógica de negócio
```

::: info Validações por método HTTP
- **`GET /balance`** usa apenas `Content-Type → KeyCase → API Key + Secret → IP Whitelist → Permissão da API Key` -- não há rate limiter, não há HMAC, não há idempotência (é polling de alta frequência autorizado).
- **Outros `GET` / `DELETE`** usam `Content-Type → KeyCase → API Key + Secret → IP Whitelist → Rate Limiter → Permissão da API Key` -- sem HMAC e sem idempotência.
- **`POST`** usa o fluxo completo acima (todas as validações).
:::

## Camada 1 -- API Key + Secret

Todas as requisições devem incluir o header `Authorization`. A API aceita **dois formatos equivalentes** -- o `ApiKey` scheme nativo ou o HTTP Basic Authentication. Ambos são validados pela mesma camada de autenticação e têm o mesmo comportamento. Escolha o que for mais conveniente para o seu cliente HTTP.

### Formato recomendado -- `ApiKey` scheme

```
Authorization: ApiKey {client_id}:{client_secret}
```

### Formato alternativo -- HTTP Basic

```
Authorization: Basic {base64(client_id:client_secret)}
```

Exemplo em Bash:

```bash
CLIENT_ID="cli_a1b2c3d4e5f6"
CLIENT_SECRET="sk_0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef01"

# Equivalente a Authorization: ApiKey cli_a1b2...:sk_0123...
BASIC=$(printf '%s:%s' "$CLIENT_ID" "$CLIENT_SECRET" | base64)
curl -X GET https://api.monetarie.com/api/external/balance \
  -H "Authorization: Basic $BASIC"
```

::: tip Quando usar Basic vs ApiKey
Use `Basic` se o seu cliente HTTP (biblioteca, gateway, proxy) já monta credenciais via base64 automaticamente (quase todos fazem). Use `ApiKey` se prefere enviar o secret em texto puro dentro do header -- mesma validação da API.
:::

### Campos da credencial

| Componente | Descrição | Prefixo |
|------------|-----------|---------|
| `client_id` | Identificador público da API Key | `cli_` |
| `client_secret` | Chave secreta (armazenamos apenas o hash) | `sk_` |

O secret nunca é armazenado em texto puro. Quando uma requisição chega, o secret enviado é comparado com o hash armazenado. Se não corresponder, a requisição é rejeitada antes de chegar à lógica de negócio.

::: warning API Key pode expirar
Apesar de na prática a maioria das API Keys serem criadas sem data de expiração, **o campo `expires_at` pode existir na credencial**. Se uma chave for configurada com `expires_at` no passado, a autenticação falha com `401 API key has expired`. Também é possível revogar (marcar como inativa): retorna `401 API key is inactive`. Isso **substitui** a informação anterior desta documentação que afirmava que API Keys eram permanentes.
:::

## Camada 2 -- HMAC-SHA512

Requisições transacionais (POST, PUT, PATCH) exigem assinatura HMAC-SHA512 do body no header `hmac`. A validação usa **comparação em tempo constante** (constant-time comparison) para impedir ataques de timing.

Veja [HMAC-SHA512](/hmac) para exemplos de implementação em 6 linguagens.

## Camada 3 -- IP Whitelist

**Toda API Key deve ter pelo menos um IP na whitelist** -- mesmo uma API Key recém-criada com credenciais válidas é rejeitada enquanto a whitelist estiver vazia:

```json
{
  "error": {
    "status": 403,
    "message": "IP whitelist required. Configure at least one allowed IP to use this API key."
  }
}
```

Depois que a whitelist tem pelo menos uma entrada, requisições vindas de IPs fora da lista recebem:

```json
{
  "error": {
    "status": 403,
    "message": "Request IP not in API key whitelist"
  }
}
```

### Formatos aceitos

| Formato | Exemplo | Comentário |
|---------|---------|------------|
| IPv4 individual | `203.0.113.45` | Apenas um endpoint público |
| Notação CIDR (IPv4) | `203.0.113.0/24` | Sub-rede /24 inteira (256 IPs). Use `/32` para um único host |
| CIDR agregado | `172.20.16.0/20` | Range privado (exemplo) -- aceito literalmente |

::: tip IPv4 vs IPv6
A API normaliza endereços `::ffff:A.B.C.D` (IPv4 mapeado em IPv6, usado por load balancers confiáveis) para o endereço IPv4 correspondente antes de comparar com a whitelist. Você **não precisa** incluir a forma IPv6-mapeada; basta cadastrar o IPv4 literal. Para clientes que saem exclusivamente via IPv6, cadastre o endereço IPv6 completo em notação padrão (ex: `2001:db8::1`).
:::

::: warning Formato da string
A whitelist espera strings **exatas**. Um espaço antes/depois, uma máscara errada (`/28` quando o range tem 256 IPs), ou um IP em notação com zeros à esquerda (`203.000.113.045`) rejeita requisições silenciosamente sem nenhum aviso no response além do 403 padrão. Sempre valide no Merchant Portal usando um IP de teste antes de colocar em produção.
:::

Configure a whitelist no Merchant Portal ao criar ou editar a API Key.

## Headers

### Headers obrigatórios

| Header | Valor | Obrigatório |
|--------|-------|-------------|
| `Authorization` | `ApiKey {client_id}:{client_secret}` ou `Basic {base64(client_id:client_secret)}` | Sim -- todas as requisições |
| `Content-Type` | `application/json` (ou `multipart/form-data` em uploads) | Sim -- `POST`, `PUT`, `PATCH` com body. Enviar `application/x-www-form-urlencoded` (default do `curl -d` sem `-H`) retorna `415 Unsupported Media Type` |
| `hmac` | Assinatura HMAC-SHA512 do body em hexadecimal lowercase | Sim -- apenas `POST` em `/api/external/*` |

### Headers opcionais

| Header | Valor | Efeito |
|--------|-------|--------|
| `Idempotency-Key` | Chave única ≤ 256 chars (UUID v4 recomendado) | Deduplica replays em 24h. **Só funciona em `POST`** -- em `GET`/`DELETE` o header é silenciosamente ignorado (não retorna erro) |
| `X-Key-Case` | `camelCase` | Converte `camelCase` dos request params para `snake_case` (entrada) e `snake_case` para `camelCase` das chaves de toda a response JSON (saída). Útil para clientes em JavaScript, TypeScript ou Kotlin |
| `X-Forwarded-For` | IP(s) separados por vírgula | Respeitado apenas quando a conexão TCP direta vem de proxy confiável. Ignorado em conexões diretas do cliente |

::: tip `Idempotency-Key` -- resposta do servidor
Quando enviar o header `Idempotency-Key`, o servidor ecoa o mesmo valor de volta em `Idempotency-Key` no response e, caso a requisição seja um replay de uma já processada nas últimas 24 horas (mesma chave + mesmo método HTTP + mesmo path), adiciona também o header `X-Idempotent-Replay: true` e retorna o body cacheado **tal como foi retornado na primeira execução** (mesmo status HTTP, mesmo body byte-a-byte). O cache é escopado por `(método, path, chave)` -- usar a mesma chave em endpoints diferentes não causa colisão. Chaves com mais de 256 caracteres são rejeitadas com `400 Idempotency-Key must be at most 256 characters`. Apenas respostas `2xx` são cacheadas -- respostas de erro (`4xx`/`5xx`) permitem retry com a mesma chave.
:::

::: tip `X-Key-Case` -- conversão para camelCase
Se o seu stack trabalha em `camelCase` (JS/TS, Kotlin, Swift), envie `X-Key-Case: camelCase` e a API aceita request body em `camelCase` (ex: `externalId`, `pixKey`) e devolve a response com chaves em `camelCase`. Sem esse header, a API permanece em `snake_case` (ex: `external_id`, `pix_key`). O header pode ser enviado em qualquer endpoint `/api/external/*` -- não precisa ser sempre o mesmo valor por API Key.
:::

::: warning HMAC assina o body **antes** da conversão interna
Se você envia `X-Key-Case: camelCase` junto com um `POST` que exige HMAC, assine o body **exatamente como vai trafegar na rede** (i.e., em `camelCase` se for essa a serialização que você está usando). O HMAC é calculado no servidor sobre o body recebido, não sobre a forma `snake_case` interna.
:::

## Exemplo Completo

```bash
CLIENT_ID="cli_a1b2c3d4e5f6"
CLIENT_SECRET="sk_0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef01"

# Consulta de saldo (GET -- sem HMAC)
curl -X GET https://api.monetarie.com/api/external/balance \
  -H "Authorization: ApiKey $CLIENT_ID:$CLIENT_SECRET"

# PIX Cash-Out (POST -- com HMAC + Idempotency-Key)
# IMPORTANTE: chaves em ordem alfabética (amount < description < pix_key < pix_key_type).
# O servidor reordena alfabeticamente antes de calcular o HMAC esperado - ver /hmac.
BODY='{"amount":3000,"description":"Pagamento","pix_key":"12345678901","pix_key_type":"cpf"}'
HMAC=$(echo -n "$BODY" | openssl dgst -sha512 -hmac "$CLIENT_SECRET" | awk '{print $2}')

curl -X POST https://api.monetarie.com/api/external/pix/cash-out \
  -H "Authorization: ApiKey $CLIENT_ID:$CLIENT_SECRET" \
  -H "Content-Type: application/json" \
  -H "hmac: $HMAC" \
  -H "Idempotency-Key: cashout-order-9876" \
  -d "$BODY"
```

## Proteções Adicionais

| Proteção | Descrição |
|----------|-----------|
| **Rate Limiting (API)** | 90.000 req/min (1.500 req/s) **por IP** em todo `/api/external/*`, exceto `GET /balance` que não possui rate limit -- permitido polling de alta frequência. Chave do limiter: `(IP, janela de 60s)`. Quando o limite é excedido, o servidor responde `429 Too Many Requests` com header `Retry-After: 60`. Em todas as respostas `2xx` que passam pelo limiter, o header `x-ratelimit-remaining` é anexado com o número de requests restantes na janela |
| **Rate Limiting (WAF de borda)** | Regras por API Key/IP no WAF de borda quando configuradas. Opera **antes** do API, portanto um 429 desta camada não chega a incrementar o contador do API |
| **Rate Limiting (auth)** | 5 req/min em endpoints de autenticação de admin/merchant (não se aplica ao `/api/external/*`) |
| **Proteção DICT no Cash-Out** | Específica do `POST /pix/cash-out` quando resolve PIX key via DICT. Quando aplicada, retorna `HTTP 202` com `status: "queued"` e dispara webhook `pix.payout.queued` enquanto a transação aguarda reprocessamento automático. Ver [PIX Cash-Out (por chave)](/pix-cashout-key) |
| **AWS WAF** | Firewall de aplicação protegendo a API com regras OWASP (XSS, SQLi, LFI, RFI, RCE) |
| **HTTPS + TLS 1.2+** | Criptografia obrigatória em todas as conexões |
| **HSTS** | Navegadores forçados a usar HTTPS |

### Headers de rate limiting na resposta

Sempre que um request passa pelo rate limiter, a resposta inclui:

| Header | Aparece em | Valor |
|--------|-----------|-------|
| `x-ratelimit-remaining` | Respostas 2xx (após passar pelo limiter) | Número inteiro: requests restantes na janela atual de 60s, escopado por IP |
| `Retry-After` | Apenas em `429 Too Many Requests` | `60` (sempre, em segundos) -- aguarde antes de tentar novamente |

::: tip Como o limiter conta "janelas"
O rate limiting usa **janelas fixas de 60 segundos**, não sliding window. Na prática, o limite estável de 1.500 req/s por IP é o comportamento que o cliente deve considerar.
:::

## Por que HMAC-SHA512 e não mTLS?

O mTLS (mutual TLS) autentica a **conexão**, não o **conteúdo**. Se a conexão está autenticada, todas as requisições passam sem validação individual.

O HMAC valida **cada requisição separadamente**. Mesmo dentro de uma conexão válida, qualquer alteração no payload faz a requisição ser rejeitada.

| Aspecto | mTLS | HMAC-SHA512 |
|---------|------|-------------|
| Valida | Canal TLS | Payload da requisição |
| Gestão | Certificados X.509 (emissão, rotação, revogação, CRL/OCSP) | Gera par, atualiza, invalida |
| Risco operacional | Certificados expirados -- causa frequente de incidentes | Chave é string simples |
| Integridade do conteúdo | Não | Sim |

O TLS já garante criptografia do transporte. O HMAC adiciona integridade e autenticidade do payload -- algo que o mTLS por si só não cobre.

## Respostas de Erro

::: warning A API tem 4 formatos distintos de erro
Dependendo da camada que rejeita a requisição, o shape do body JSON é diferente. Sempre inspecione o shape antes de fazer o parsing do erro no cliente:

1. **`{"error": {status, message}}`** -- `Content-Type` (415), `API Key + IP Whitelist` (401/403), `Idempotency` (400), rate limiting (429).
2. **`{"error": "forbidden", "message": "..."}`** -- apenas `Permissão da API Key` (403).
3. **`{"worked": false, "detail": "..."}`** -- apenas `HMAC validation` (400, 401, 403).
4. **`{"errors": {atom: "msg"}}`** -- erros de negócio (qualquer erro de negócio 4xx/5xx).
:::

### Camada 0 -- Content-Type

Antes de qualquer autenticação, `POST`/`PUT`/`PATCH` sem um Content-Type aceito (`application/json` ou `multipart/form-data`) é bloqueado.

#### 415 -- Content-Type não suportado

```json
{
  "error": {
    "status": 415,
    "message": "Unsupported Media Type. Expected Content-Type: application/json",
    "hint": "Add header: -H 'Content-Type: application/json'"
  }
}
```

::: tip Armadilha comum com `curl -d`
`curl -d '{"...":""}' URL` (sem `-H`) envia `Content-Type: application/x-www-form-urlencoded` por default. A API pode interpretar isso como form-urlencoded e não como JSON. Inclua `-H 'Content-Type: application/json'` para receber a validação correta.
:::

### Camada 1 -- API Key + IP Whitelist

Erros de credenciais ausentes, inválidas, API Key inativa/expirada ou IP fora da whitelist vêm no formato `{"error": {status, message}}`:

#### 401 -- Credenciais Ausentes

```json
{
  "error": {
    "status": 401,
    "message": "Missing API key credentials. Use Authorization: ApiKey <client_id>:<client_secret>"
  }
}
```

#### 401 -- Credenciais Inválidas

```json
{
  "error": {
    "status": 401,
    "message": "Invalid API key credentials"
  }
}
```

#### 401 -- API Key Inativa

```json
{
  "error": {
    "status": 401,
    "message": "API key is inactive"
  }
}
```

#### 401 -- API Key Expirada

```json
{
  "error": {
    "status": 401,
    "message": "API key has expired"
  }
}
```

#### 403 -- IP Whitelist Vazia

```json
{
  "error": {
    "status": 403,
    "message": "IP whitelist required. Configure at least one allowed IP to use this API key."
  }
}
```

#### 403 -- IP não Autorizado

```json
{
  "error": {
    "status": 403,
    "message": "Request IP not in API key whitelist"
  }
}
```

#### 403 -- Conta Inativa

```json
{
  "error": {
    "status": 403,
    "message": "Account is not active"
  }
}
```

#### 403 -- Permissão Insuficiente

::: tip Este 403 vem da validação de permissão
Este erro aparece quando a API Key é válida, o IP está autorizado, mas a credencial não possui a permissão exigida pelo endpoint. Por isso o JSON é diferente dos erros de credencial/IP: usa `{"error": "forbidden", "message": "..."}`.
:::

```json
{
  "error": "forbidden",
  "message": "API key lacks permission: transfer:write"
}
```

### Camada 2 -- Validação HMAC-SHA512

Erros emitidos pela validação HMAC sempre vêm no formato `{"worked": false, "detail": "..."}` com diferentes códigos HTTP conforme a causa:

#### 401 -- Assinatura inválida ou header ausente

```json
{
  "worked": false,
  "detail": "Invalid HMAC signature"
}
```

```json
{
  "worked": false,
  "detail": "Missing HMAC header"
}
```

#### 400 -- Body ausente ou JSON inválido

```json
{
  "worked": false,
  "detail": "Request body is required for HMAC validation"
}
```

```json
{
  "worked": false,
  "detail": "Request body must be valid JSON for HMAC validation"
}
```

#### 403 -- API Key sem HMAC secret configurado

```json
{
  "worked": false,
  "detail": "HMAC secret not configured for this API key"
}
```

### Camada 3 -- Erros de Negócio

Depois da autenticação, erros de validação, parâmetros faltantes, recursos não encontrados e regras de negócio vêm no formato `{"errors": {atom: "msg"}}`:

#### 400 -- Requisição Inválida

```json
{
  "errors": {
    "bad_request": {
      "amount": ["is required"]
    }
  }
}
```

#### 404 -- Recurso Não Encontrado

```json
{
  "errors": {
    "not_found": "Transaction not found"
  }
}
```

#### 401 -- Não Autorizado (regra de negócio)

```json
{
  "errors": {
    "unauthorized": "invalid credentials"
  }
}
```

#### 422 -- Entidade Não Processável

```json
{
  "errors": {
    "unprocessable_entity": "Invalid PIX key format"
  }
}
```

### Camada 4 -- Rate Limiting (rate limiting ou WAF de borda)

#### 429 -- Rate Limit Excedido

Body do 429 vindo da camada de rate limiting:

```json
{
  "error": {
    "status": 429,
    "message": "Too many requests. Please try again later."
  }
}
```

Headers incluídos na resposta 429:

| Header | Valor |
|--------|-------|
| `Retry-After` | `60` (segundos a aguardar antes de retry) |

::: tip Como reagir ao 429
- **Backoff exponencial**: comece com 60s (valor do `Retry-After`), dobre a cada retry subsequente até um teto razoável (ex: 5 min).
- **Nunca ignore o header**: mesmo que o seu cliente tenha sua própria estratégia de retry, o `Retry-After` é a fonte de verdade canônica para este endpoint.
- **Se 429 for do WAF de borda** (camada acima do API), o body pode ter shape diferente -- o status 429 com `Retry-After: 60` continua sendo o padrão para qualquer camada.
:::

### Camada 5 -- Idempotency

#### 400 -- Idempotency-Key muito longa

```json
{
  "error": {
    "status": 400,
    "message": "Idempotency-Key must be at most 256 characters"
  }
}
```

## Permissões (Permissions)

Cada API Key possui uma lista de permissões que determinam quais endpoints podem ser acessados. Se a API Key não possuir a permissão necessária, a requisição é rejeitada com `403 Forbidden`.

### Como obter a API Key

1. Use a base de produção **api.monetarie.com**
2. Solicite a emissão das credenciais para a conta desejada
3. Informe os IPs que devem entrar na whitelist
4. Informe as permissões necessárias (veja tabela abaixo)
5. Após a emissão, armazene o `client_id` e o `client_secret` com segurança

Para editar permissões de uma API Key existente, solicite a atualização das permissões da credencial.

::: danger Obrigatório para enviar PIX
Para realizar operações de **PIX Cash-Out** (envio de PIX), a API Key **deve** ter a permissão `transfer:write`. Sem essa permissão, todas as tentativas de envio retornam `403 Forbidden` com a mensagem `API key lacks permission: transfer:write`.

Permissões mínimas recomendadas para operação completa:
- **Cash-In (receber):** `pix:write` + `transfer:read`
- **Cash-Out (enviar):** `transfer:write` + `transfer:read`
- **Consultas:** `transfer:read` + `account:read` + `statement:read`
- **Webhooks:** `account:write` + `account:read`
:::

### Permissões Disponíveis

| Permissão | Descrição |
|-----------|-----------|
| `pix:write` | Gerar QR Code (Cash-In) |
| `pix:read` | Listar chaves PIX |
| `transfer:write` | Enviar PIX (Cash-Out) |
| `transfer:read` | Consultar transações (por ID, E2E, Tag, External ID), comprovante, listar transações |
| `payment:write` | Solicitar devolução (refund) e submeter defesa MED |
| `payment:read` | Listar e consultar MED |
| `account:write` | Criar e remover webhooks |
| `account:read` | Consultar saldo, listar webhooks, validar CPF |
| `statement:read` | Consultar extrato |

### Permissões por Endpoint

| Endpoint | Método | Permissão |
|----------|--------|-----------|
| `/pix/cash-in` | POST | `pix:write` |
| `/pix/cash-out` | POST | `transfer:write` |
| `/pix/refund` | POST | `payment:write` |
| `/med/:id/defense` | POST | `payment:write` |
| `/cpf/validate` | POST | `account:read` |
| `/webhooks` | POST | `account:write` |
| `/webhooks` | GET | `account:read` |
| `/webhooks/:id` | DELETE | `account:write` |
| `/balance` | GET | `account:read` |
| `/transactions` | GET | `transfer:read` |
| `/transactions/:id` | GET | `transfer:read` |
| `/transactions/e2e/:e2e_id` | GET | `transfer:read` |
| `/transactions/tag/:tag` | GET | `transfer:read` |
| `/transactions/ref/:external_id` | GET | `transfer:read` |
| `/transactions/:id/receipt` | GET | `transfer:read` |
| `/pix/keys` | GET | `pix:read` |
| `/med` | GET | `payment:read` |
| `/med/:id` | GET | `payment:read` |
| `/statement` | GET | `statement:read` |

::: info MED na External API
Use `payment:read` para listar e consultar MEDs. Use `payment:write` para submeter defesa em `POST /api/external/med/:id/defense`. O endpoint de defesa recebe JSON assinado por HMAC; uploads binários de anexos permanecem disponíveis nos portais Monetarie.
:::

### Resposta de Erro -- 403 (Permissão Insuficiente)

Veja o formato em [Camada 1 -- 403 Permissão Insuficiente](#camada-1-api-key-ip-whitelist).

As permissões são configuradas na criação da API Key pelo Merchant Portal ou pela API de administração.

::: warning Segurança
- Nunca exponha o `client_secret` em código frontend ou repositórios públicos
- Utilize variáveis de ambiente no seu servidor
- A API Key **pode expirar** se o campo `expires_at` estiver configurado; caso contrário, permanece válida até ser revogada manualmente no Merchant Portal
- Configure IPs permitidos na whitelist -- uma whitelist vazia bloqueia a chave com `403 IP whitelist required`
:::
