Skip to content

Autenticacion

La API externa de Minha Konta utiliza un modelo de seguridad con tres capas principales de autenticacion -- API Key + Secret, firma HMAC-SHA512 por request (solo en POST) y whitelist de IP obligatoria -- complementadas por validacion de contenido, rate limiting e idempotencia.

Vision general de validaciones

La solicitud HTTP pasa por las validaciones abajo, en este orden. Cualquier rechazo retorna error antes de ejecutar la logica de negocio:

POST /api/external/...

  ├─ 1. Content-Type ──────── No es application/json ni multipart/form-data? → 415
  ├─ 2. X-Key-Case (KeyCase) ─ Convierte params/response snake_case ↔ camelCase (opcional)
  ├─ 3. API Key + Secret ───── Credenciales ausentes/invalidas? → 401 | API Key inactiva? → 401 | API Key expirada? → 401
  ├─ 4. IP Whitelist ───────── Whitelist vacia? → 403 "ip whitelist required" | IP fuera? → 403 "ip not allowed" | Cuenta inactiva? → 403
  ├─ 5. HMAC-SHA512 (POST) ─── Header `hmac` ausente? → 401 | Firma invalida? → 401 | Body invalido? → 400 | API Key sin secret? → 403
  ├─ 6. Rate Limiting ─── Mas de 90.000 req/min por IP? → 429 Retry-After: 60
  ├─ 7. Idempotency (POST) ─── Clave > 256 chars? → 400 | Replay en 24h? → retorna body cacheado + X-Idempotent-Replay: true
  └─ 8. Permiso de API Key ───── API Key sin el permiso exigido por la ruta? → 403

       └─ Solicitud aceptada → Logica de negocio

Validaciones por metodo HTTP

  • GET /balance usa solo Content-Type → KeyCase → API Key + Secret → IP Whitelist → Permiso de API Key -- no hay rate limiter, no hay HMAC, no hay idempotencia (es polling de alta frecuencia autorizado).
  • Otros GET / DELETE usan Content-Type → KeyCase → API Key + Secret → IP Whitelist → Rate Limiter → Permiso de API Key -- sin HMAC y sin idempotencia.
  • POST usa el flujo completo de validaciones arriba (todas las validaciones).

Capa 1 -- API Key + Secret

Todas las solicitudes deben incluir el header Authorization. La API acepta dos formatos equivalentes -- el ApiKey scheme nativo o el HTTP Basic Authentication. Ambos son validados por la misma capa de autenticacion y tienen el mismo comportamiento. Elija lo que sea mas conveniente para su cliente HTTP.

Formato recomendado -- ApiKey scheme

Authorization: ApiKey {client_id}:{client_secret}

Formato alternativo -- HTTP Basic

Authorization: Basic {base64(client_id:client_secret)}

Ejemplo en 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"

Cuando usar Basic vs ApiKey

Use Basic si su cliente HTTP (biblioteca, gateway, proxy) ya monta credenciales via base64 automaticamente (casi todos lo hacen). Use ApiKey si prefiere enviar el secret en texto puro dentro del header -- misma validacion de la API.

Campos de la credencial

ComponenteDescripcionPrefijo
client_idIdentificador publico de la API Keycli_
client_secretClave secreta (almacenamos solo el hash)sk_

El secret nunca se almacena en texto puro. Cuando una solicitud llega, el secret enviado es comparado con el hash almacenado. Si no coincide, la solicitud se rechaza antes de llegar a la logica de negocio.

API Key puede expirar

Aunque en la practica la mayoria de las API Keys se crean sin fecha de expiracion, el campo expires_at puede existir en la credencial. Si una clave se configura con expires_at en el pasado, la autenticacion falla con 401 API key has expired. Tambien es posible revocar (marcar como inactiva): retorna 401 API key is inactive. Esto sustituye la informacion anterior de esta documentacion que afirmaba que API Keys eran permanentes.

Capa 2 -- HMAC-SHA512

Solicitudes transaccionales (POST, PUT, PATCH) exigen firma HMAC-SHA512 del body en el header hmac. La validacion usa comparacion en tiempo constante (constant-time comparison) para impedir ataques de timing.

Vea HMAC-SHA512 para ejemplos de implementacion en 6 lenguajes.

Capa 3 -- IP Whitelist

Toda API Key debe tener al menos un IP en la whitelist -- incluso una API Key recien creada con credenciales validas es rechazada mientras la whitelist este vacia:

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

Despues que la whitelist tiene al menos una entrada, solicitudes provenientes de IPs fuera de la lista reciben:

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

Formatos aceptados

FormatoEjemploComentario
IPv4 individual203.0.113.45Solo un endpoint publico
Notacion CIDR (IPv4)203.0.113.0/24Sub-red /24 entera (256 IPs). Use /32 para un solo host
CIDR agregado172.20.16.0/20Range privado (ejemplo) -- aceptado literalmente

IPv4 vs IPv6

La API normaliza direcciones ::ffff:A.B.C.D (IPv4 mapeado en IPv6, usado por load balancers confiables) a la direccion IPv4 correspondiente antes de comparar con la whitelist. Usted no necesita incluir la forma IPv6-mapeada; basta registrar el IPv4 literal. Para clientes que salen exclusivamente via IPv6, registre la direccion IPv6 completa en notacion estandar (ej: 2001:db8::1).

Formato del string

La whitelist espera strings exactos. Un espacio antes/despues, una mascara incorrecta (/28 cuando el range tiene 256 IPs), o un IP en notacion con ceros a la izquierda (203.000.113.045) rechaza solicitudes silenciosamente sin ningun aviso en el response mas alla del 403 estandar. Siempre valide en el Merchant Portal usando un IP de prueba antes de colocar en produccion.

Configure la whitelist en el Merchant Portal al crear o editar la API Key.

Headers

Headers obligatorios

HeaderValorObligatorio
AuthorizationApiKey {client_id}:{client_secret} o Basic {base64(client_id:client_secret)}Si -- todas las solicitudes
Content-Typeapplication/json (o multipart/form-data en uploads)Si -- POST, PUT, PATCH con body. Enviar application/x-www-form-urlencoded (default de curl -d sin -H) retorna 415 Unsupported Media Type
hmacFirma HMAC-SHA512 del body en hexadecimal lowercaseSi -- solo POST en /api/external/*

Headers opcionales

HeaderValorEfecto
Idempotency-KeyClave unica ≤ 256 chars (UUID v4 recomendado)Deduplica replays en 24h. Solo funciona en POST -- en GET/DELETE el header es silenciosamente ignorado (no retorna error)
X-Key-CasecamelCaseConvierte camelCase de los request params a snake_case (entrada) y snake_case a camelCase de las claves de toda la response JSON (salida). Util para clientes en JavaScript, TypeScript o Kotlin
X-Forwarded-ForIP(s) separados por comaRespetado solo cuando la conexion TCP directa viene de proxy confiable. Ignorado en conexiones directas del cliente

Idempotency-Key -- respuesta del servidor

Cuando envie el header Idempotency-Key, el servidor reproduce el mismo valor en Idempotency-Key en el response y, en caso que la solicitud sea un replay de una ya procesada en las ultimas 24 horas (misma clave + mismo metodo HTTP + mismo path), agrega tambien el header X-Idempotent-Replay: true y retorna el body cacheado tal como fue retornado en la primera ejecucion (mismo status HTTP, mismo body byte-a-byte). El cache es scope por (metodo, path, clave) -- usar la misma clave en endpoints diferentes no causa colision. Claves con mas de 256 caracteres son rechazadas con 400 Idempotency-Key must be at most 256 characters. Solo respuestas 2xx son cacheadas -- respuestas de error (4xx/5xx) permiten retry con la misma clave.

X-Key-Case -- conversion a camelCase

Si su stack trabaja en camelCase (JS/TS, Kotlin, Swift), envie X-Key-Case: camelCase y la API acepta request body en camelCase (ej: externalId, pixKey) y devuelve la response con claves en camelCase. Sin ese header, la API permanece en snake_case (ej: external_id, pix_key). El header puede ser enviado en cualquier endpoint /api/external/* -- no necesita ser siempre el mismo valor por API Key.

HMAC firma el body antes de la conversion interna

Si usted envia X-Key-Case: camelCase junto con un POST que exige HMAC, firme el body exactamente como va a transitar en la red (es decir, en camelCase si esa es la serializacion que usted esta usando). El HMAC es calculado en el servidor sobre el body recibido, no sobre la forma snake_case interna.

Ejemplo Completo

bash
CLIENT_ID="cli_a1b2c3d4e5f6"
CLIENT_SECRET="sk_0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef01"

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

# PIX Cash-Out (POST -- con HMAC + Idempotency-Key)
# IMPORTANTE: claves en orden alfabetico (amount < description < pix_key < pix_key_type).
# El servidor reordena alfabeticamente antes de calcular el HMAC esperado - ver /es/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"

Protecciones Adicionales

ProteccionDescripcion
Rate Limiting (API)90.000 req/min (1.500 req/s) por IP en todo /api/external/*, excepto GET /balance que no tiene rate limit -- permitido polling de alta frecuencia. Clave del limiter: (IP, ventana de 60s). Cuando el limite es excedido, el servidor responde 429 Too Many Requests con header Retry-After: 60. En todas las respuestas 2xx que pasan por el limiter, el header x-ratelimit-remaining es agregado con el numero de requests restantes en la ventana
Rate Limiting (WAF de borde)Reglas por API Key/IP en el WAF de borde cuando estan configuradas. Opera antes del API, por lo tanto un 429 de esta capa no llega a incrementar el contador del API
Rate Limiting (auth)5 req/min en endpoints de autenticacion de admin/merchant (no aplica a /api/external/*)
Proteccion DICT en Cash-OutEspecifica del POST /pix/cash-out cuando resuelve PIX key via DICT. Cuando se aplica, retorna HTTP 202 con status: "queued" y dispara webhook pix.payout.queued mientras la transaccion aguarda reprocesamiento automatico. Ver PIX Cash-Out (por clave)
AWS WAFFirewall de aplicacion protegiendo la API con reglas OWASP (XSS, SQLi, LFI, RFI, RCE)
HTTPS + TLS 1.2+Cifrado obligatorio en todas las conexiones
HSTSNavegadores forzados a usar HTTPS

Headers de rate limiting en la respuesta

Siempre que un request pasa por el rate limiter, la respuesta incluye:

HeaderAparece enValor
x-ratelimit-remainingRespuestas 2xx (despues de pasar por el limiter)Numero entero: requests restantes en la ventana actual de 60s, scope por IP
Retry-AfterSolo en 429 Too Many Requests60 (siempre, en segundos) -- espere antes de intentar de nuevo

Como el limiter cuenta "ventanas"

El rate limiting usa ventanas fijas de 60 segundos, no sliding window. En la practica, el limite estable de 1.500 req/s por IP es el comportamiento que el cliente debe considerar.

Por que HMAC-SHA512 y no mTLS?

El mTLS (mutual TLS) autentica la conexion, no el contenido. Si la conexion esta autenticada, todas las solicitudes pasan sin validacion individual.

El HMAC valida cada solicitud por separado. Incluso dentro de una conexion valida, cualquier alteracion en el payload hace que la solicitud sea rechazada.

AspectomTLSHMAC-SHA512
ValidaCanal TLSPayload de la solicitud
GestionCertificados X.509 (emision, rotacion, revocacion, CRL/OCSP)Genera par, actualiza, invalida
Riesgo operacionalCertificados expirados -- causa frecuente de incidentesClave es string simple
Integridad del contenidoNoSi

El TLS ya garantiza cifrado del transporte. El HMAC agrega integridad y autenticidad del payload -- algo que el mTLS por si solo no cubre.

Respuestas de Error

La API tiene 4 formatos distintos de error

Dependiendo de que capa de validacion rechaza la solicitud, el shape del body JSON es diferente. Siempre inspeccione el shape antes de parsear el error en el cliente:

  1. {"error": {status, message}} -- Content-Type (415), API Key + IP Whitelist (401/403), Idempotency (400), rate limiting (429).
  2. {"error": "forbidden", "message": "..."} -- solo Permiso de API Key (403).
  3. {"worked": false, "detail": "..."} -- solo HMAC validation (400, 401, 403).
  4. {"errors": {atom: "msg"}} -- errores de negocio (cualquier error de negocio 4xx/5xx).

Capa 0 -- Content-Type

Antes de cualquier autenticacion, POST/PUT/PATCH sin un Content-Type aceptado (application/json o multipart/form-data) es bloqueado.

415 -- Content-Type no soportado

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

Trampa comun con curl -d

curl -d '{"...":""}' URL (sin -H) envia Content-Type: application/x-www-form-urlencoded por default. La API puede interpretarlo como form-urlencoded y no como JSON. Incluya -H 'Content-Type: application/json' para obtener la validacion esperada.

Capa 1 -- API Key + IP Whitelist

Errores de credenciales ausentes, invalidas, API Key inactiva/expirada o IP fuera de la whitelist vienen en el formato {"error": {status, message}}:

401 -- Credenciales Ausentes

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

401 -- Credenciales Invalidas

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

401 -- API Key Inactiva

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 Vacia

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

403 -- IP no Autorizado

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

403 -- Cuenta Inactiva

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

403 -- Permiso Insuficiente

Este 403 viene de la validacion de permisos

Este error aparece cuando la API Key es valida, el IP esta autorizado, pero la credencial no tiene el permiso exigido por el endpoint. Por eso el JSON difiere de los errores de credencial/IP: usa {"error": "forbidden", "message": "..."}.

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

Capa 2 -- Validacion HMAC-SHA512

Los errores emitidos por la validacion HMAC siempre vienen en el formato {"worked": false, "detail": "..."} con diferentes codigos HTTP segun la causa:

401 -- Firma invalida o header ausente

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

400 -- Body ausente o JSON invalido

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 sin HMAC secret configurado

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

Capa 3 -- Errores de Negocio

Despues de la autenticacion, errores de validacion, parametros faltantes, recursos no encontrados y reglas de negocio vienen en el formato {"errors": {atom: "msg"}}:

400 -- Solicitud Invalida

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

404 -- Recurso No Encontrado

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

401 -- No Autorizado (regla de negocio)

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

422 -- Entidad No Procesable

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

Capa 4 -- Rate Limiting (API o WAF de borde)

429 -- Rate Limit Excedido

Body del 429 emitido por la capa de rate limiting:

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

Headers incluidos en la respuesta 429:

HeaderValor
Retry-After60 (segundos a esperar antes de retry)

Como reaccionar al 429

  • Backoff exponencial: comience con 60s (valor del Retry-After), doble a cada retry siguiente hasta un techo razonable (ej: 5 min).
  • Nunca ignore el header: incluso si su cliente tiene su propia estrategia de retry, el Retry-After es la fuente de verdad canonica para este endpoint.
  • Si 429 es del WAF de borde (capa arriba del API), el body puede tener shape diferente -- el status 429 con Retry-After: 60 continua siendo el estandar para cualquier capa.

Capa 5 -- Idempotency

400 -- Idempotency-Key muy larga

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

Permisos (Permissions)

Cada API Key posee una lista de permisos que determinan cuales endpoints pueden ser accesados. Si la API Key no posee el permiso necesario, la solicitud es rechazada con 403 Forbidden.

Como obtener la API Key

  1. Use la base de produccion api.minhakonta.com
  2. Solicite la emision de credenciales para la cuenta deseada
  3. Informe los IPs que deben agregarse a la whitelist
  4. Informe los permisos necesarios (vea tabla abajo)
  5. Luego de la emision, almacene el client_id y el client_secret con seguridad

Para editar permisos de una API Key existente, solicite la actualizacion de los permisos de la credencial.

Obligatorio para enviar PIX

Para realizar operaciones de PIX Cash-Out (envio de PIX), la API Key debe tener el permiso transfer:write. Sin ese permiso, todas las tentativas de envio retornan 403 Forbidden con el mensaje API key lacks permission: transfer:write.

Permisos minimos recomendados para operacion completa:

  • Cash-In (recibir): pix:write + transfer:read
  • Cash-Out (enviar): transfer:write + transfer:read
  • Consultas: transfer:read + account:read + statement:read
  • Webhooks: account:write + account:read

Permisos Disponibles

PermisoDescripcion
pix:writeGenerar QR Code (Cash-In)
pix:readListar claves PIX
transfer:writeEnviar PIX (Cash-Out)
transfer:readConsultar transacciones (por ID, E2E, Tag, External ID), comprobante, listar transacciones
payment:writeSolicitar devolucion (refund) y enviar defensa MED
payment:readListar y consultar MED
account:writeCrear y remover webhooks
account:readConsultar saldo, listar webhooks, validar CPF
statement:readConsultar extracto

Permisos por Endpoint

EndpointMetodoPermiso
/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

Respuesta de Error -- 403 (Permiso Insuficiente)

Vea el formato en Capa 1 -- 403 Permiso insuficiente.

Use payment:read para listar y consultar MEDs. Use payment:write para enviar defensa en POST /api/external/med/:id/defense. El endpoint de defensa recibe JSON firmado por HMAC; los uploads binarios permanecen disponibles en los portales Minha Konta.

Los permisos son configurados en la creacion de la API Key por el Merchant Portal o por la API de administracion.

Seguridad

  • Nunca exponga el client_secret en codigo frontend o repositorios publicos
  • Utilice variables de ambiente en su servidor
  • La API Key puede expirar si el campo expires_at esta configurado; de lo contrario, permanece valida hasta ser revocada manualmente en el Merchant Portal
  • Configure IPs permitidos en la whitelist -- una whitelist vacia bloquea la clave con 403 IP whitelist required

Minha Konta Instituição de Pagamento - ISPB 39929224