Autenticação
A API externa da Minha Konta 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ócioValidações por método HTTP
GET /balanceusa apenasContent-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/DELETEusamContent-Type → KeyCase → API Key + Secret → IP Whitelist → Rate Limiter → Permissão da API Key-- sem HMAC e sem idempotência. POSTusa 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:
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.minhakonta.com/api/external/balance \
-H "Authorization: Basic $BASIC"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.
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 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:
{
"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:
{
"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 |
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).
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 |
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.
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.
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
CLIENT_ID="cli_a1b2c3d4e5f6"
CLIENT_SECRET="sk_0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef01"
# Consulta de saldo (GET -- sem HMAC)
curl -X GET https://api.minhakonta.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.minhakonta.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) |
| 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 |
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
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:
{"error": {status, message}}--Content-Type(415),API Key + IP Whitelist(401/403),Idempotency(400), rate limiting (429).{"error": "forbidden", "message": "..."}-- apenasPermissão da API Key(403).{"worked": false, "detail": "..."}-- apenasHMAC validation(400, 401, 403).{"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
{
"error": {
"status": 415,
"message": "Unsupported Media Type. Expected Content-Type: application/json",
"hint": "Add header: -H 'Content-Type: application/json'"
}
}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
{
"error": {
"status": 401,
"message": "Missing API key credentials. Use Authorization: ApiKey <client_id>:<client_secret>"
}
}401 -- Credenciais Inválidas
{
"error": {
"status": 401,
"message": "Invalid API key credentials"
}
}401 -- API Key Inativa
{
"error": {
"status": 401,
"message": "API key is inactive"
}
}401 -- API Key Expirada
{
"error": {
"status": 401,
"message": "API key has expired"
}
}403 -- IP Whitelist Vazia
{
"error": {
"status": 403,
"message": "IP whitelist required. Configure at least one allowed IP to use this API key."
}
}403 -- IP não Autorizado
{
"error": {
"status": 403,
"message": "Request IP not in API key whitelist"
}
}403 -- Conta Inativa
{
"error": {
"status": 403,
"message": "Account is not active"
}
}403 -- Permissão Insuficiente
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": "..."}.
{
"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
{
"worked": false,
"detail": "Invalid HMAC signature"
}{
"worked": false,
"detail": "Missing HMAC header"
}400 -- Body ausente ou JSON inválido
{
"worked": false,
"detail": "Request body is required for HMAC validation"
}{
"worked": false,
"detail": "Request body must be valid JSON for HMAC validation"
}403 -- API Key sem HMAC secret configurado
{
"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
{
"errors": {
"bad_request": {
"amount": ["is required"]
}
}
}404 -- Recurso Não Encontrado
{
"errors": {
"not_found": "Transaction not found"
}
}401 -- Não Autorizado (regra de negócio)
{
"errors": {
"unauthorized": "invalid credentials"
}
}422 -- Entidade Não Processável
{
"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:
{
"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) |
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: 60continua sendo o padrão para qualquer camada.
Camada 5 -- Idempotency
400 -- Idempotency-Key muito longa
{
"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
- Use a base de produção api.minhakonta.com
- Solicite a emissão das credenciais para a conta desejada
- Informe os IPs que devem entrar na whitelist
- Informe as permissões necessárias (veja tabela abaixo)
- Após a emissão, armazene o
client_ide oclient_secretcom segurança
Para editar permissões de uma API Key existente, solicite a atualização das permissões da credencial.
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 |
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 Minha Konta.
Resposta de Erro -- 403 (Permissão Insuficiente)
Veja o formato em Camada 1 -- 403 Permissão Insuficiente.
As permissões são configuradas na criação da API Key pelo Merchant Portal ou pela API de administração.
Segurança
- Nunca exponha o
client_secretem 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_atestiver 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
