Skip to content

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ócio

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.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

ComponenteDescriçãoPrefixo
client_idIdentificador público da API Keycli_
client_secretChave 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:

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

FormatoExemploComentário
IPv4 individual203.0.113.45Apenas um endpoint público
Notação CIDR (IPv4)203.0.113.0/24Sub-rede /24 inteira (256 IPs). Use /32 para um único host
CIDR agregado172.20.16.0/20Range 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

HeaderValorObrigatório
AuthorizationApiKey {client_id}:{client_secret} ou Basic {base64(client_id:client_secret)}Sim -- todas as requisições
Content-Typeapplication/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
hmacAssinatura HMAC-SHA512 do body em hexadecimal lowercaseSim -- apenas POST em /api/external/*

Headers opcionais

HeaderValorEfeito
Idempotency-KeyChave ú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-CasecamelCaseConverte 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-ForIP(s) separados por vírgulaRespeitado 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

bash
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çãoDescriçã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-OutEspecí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 WAFFirewall 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
HSTSNavegadores forçados a usar HTTPS

Headers de rate limiting na resposta

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

HeaderAparece emValor
x-ratelimit-remainingRespostas 2xx (após passar pelo limiter)Número inteiro: requests restantes na janela atual de 60s, escopado por IP
Retry-AfterApenas em 429 Too Many Requests60 (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.

AspectomTLSHMAC-SHA512
ValidaCanal TLSPayload da requisição
GestãoCertificados X.509 (emissão, rotação, revogação, CRL/OCSP)Gera par, atualiza, invalida
Risco operacionalCertificados expirados -- causa frequente de incidentesChave é string simples
Integridade do conteúdoNãoSim

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:

  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'"
  }
}

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

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:

HeaderValor
Retry-After60 (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: 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.minhakonta.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.

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ãoDescrição
pix:writeGerar QR Code (Cash-In)
pix:readListar chaves PIX
transfer:writeEnviar PIX (Cash-Out)
transfer:readConsultar transações (por ID, E2E, Tag, External ID), comprovante, listar transações
payment:writeSolicitar devolução (refund) e submeter defesa MED
payment:readListar e consultar MED
account:writeCriar e remover webhooks
account:readConsultar saldo, listar webhooks, validar CPF
statement:readConsultar extrato

Permissões por Endpoint

EndpointMétodoPermissão
/pix/cash-inPOSTpix:write
/pix/cash-outPOSTtransfer:write
/pix/refundPOSTpayment:write
/med/:id/defensePOSTpayment:write
/cpf/validatePOSTaccount:read
/webhooksPOSTaccount:write
/webhooksGETaccount:read
/webhooks/:idDELETEaccount:write
/balanceGETaccount:read
/transactionsGETtransfer:read
/transactions/:idGETtransfer:read
/transactions/e2e/:e2e_idGETtransfer:read
/transactions/tag/:tagGETtransfer:read
/transactions/ref/:external_idGETtransfer:read
/transactions/:id/receiptGETtransfer:read
/pix/keysGETpix:read
/medGETpayment:read
/med/:idGETpayment:read
/statementGETstatement: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_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

Minha Konta Instituição de Pagamento - ISPB 39929224