Skip to content

Authentification

L'API externe Minha Konta utilise un modele de securite avec trois couches principales d'authentification -- API Key + Secret, signature HMAC-SHA512 par requete (uniquement en POST) et whitelist IP obligatoire -- completees par la validation du contenu, le rate limiting et l'idempotence.

Vue generale des validations

La requete HTTP passe par les validations ci-dessous, dans cet ordre. Tout rejet renvoie une erreur avant l'execution de la logique metier :

POST /api/external/...

  ├─ 1. Content-Type ──────── Pas application/json ni multipart/form-data ? → 415
  ├─ 2. X-Key-Case (KeyCase) ─ Convertit params/response snake_case ↔ camelCase (optionnel)
  ├─ 3. API Key + Secret ───── Credentials absents/invalides ? → 401 | API Key inactive ? → 401 | API Key expirée ? → 401
  ├─ 4. IP Whitelist ───────── Whitelist vide ? → 403 "ip whitelist required" | IP hors liste ? → 403 "ip not allowed" | Compte inactif ? → 403
  ├─ 5. HMAC-SHA512 (POST) ─── Header `hmac` absent ? → 401 | Signature invalide ? → 401 | Body invalide ? → 400 | API Key sans secret ? → 403
  ├─ 6. Rate Limiting ─── Plus de 90 000 req/min par IP ? → 429 Retry-After: 60
  ├─ 7. Idempotency (POST) ─── Clé > 256 chars ? → 400 | Replay dans 24h ? → renvoie body en cache + X-Idempotent-Replay: true
  └─ 8. Permission API Key ───── API Key sans la permission exigée par la route ? → 403

       └─ Requête acceptée → Logique metier

Validations par methode HTTP

  • GET /balance utilise uniquement Content-Type → KeyCase → API Key + Secret → IP Whitelist → Permission API Key -- pas de rate limiter, pas de HMAC, pas d'idempotence (polling haute fréquence autorisé).
  • Les autres GET / DELETE utilisent Content-Type → KeyCase → API Key + Secret → IP Whitelist → Rate Limiter → Permission API Key -- sans HMAC ni idempotence.
  • POST utilise le flux complet de validations ci-dessus (toutes les validations).

Couche 1 -- API Key + Secret

Toutes les requêtes doivent inclure l'en-tête Authorization. L'API accepte deux formats équivalents -- le schéma natif ApiKey ou HTTP Basic Authentication. Les deux sont validés par la même couche d'authentification et ont le même comportement. Choisissez celui qui convient le mieux à votre client HTTP.

Format recommandé -- schéma ApiKey

Authorization: ApiKey {client_id}:{client_secret}

Format alternatif -- HTTP Basic

Authorization: Basic {base64(client_id:client_secret)}

Exemple en Bash :

bash
CLIENT_ID="cli_a1b2c3d4e5f6"
CLIENT_SECRET="sk_0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef01"

# Équivalent à 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"

Quand utiliser Basic vs ApiKey

Utilisez Basic si votre client HTTP (bibliothèque, gateway, proxy) construit automatiquement les credentials via base64 (la quasi-totalité le fait). Utilisez ApiKey si vous préférez envoyer le secret en clair dans l'en-tête -- les deux passent par le même parser côté API.

Champs du credential

ComposantDescriptionPréfixe
client_idIdentifiant public de l'API Keycli_
client_secretClé secrète (nous stockons uniquement le hash)sk_

Le secret n'est jamais stocké en clair. Lorsqu'une requête arrive, le secret envoyé est comparé au hash stocké. En cas de non-correspondance, la requête est rejetée avant d'atteindre la logique métier.

L'API Key peut expirer

Bien qu'en pratique la plupart des API Keys soient créées sans date d'expiration, le champ expires_at existe dans le schéma. Si une clé est configurée avec expires_at dans le passé, l'authentification échoue avec 401 API key has expired. Il est également possible de la révoquer (marquer comme inactive) : retourne 401 API key is inactive. Cela remplace l'information précédente de cette documentation qui affirmait que les API Keys étaient permanentes.

Couche 2 -- HMAC-SHA512

Les requêtes transactionnelles (POST, PUT, PATCH) exigent une signature HMAC-SHA512 du body dans l'en-tête hmac. La validation utilise une comparaison en temps constant (constant-time comparison) pour empêcher les attaques par timing.

Consultez HMAC-SHA512 pour des exemples d'implémentation dans 6 langages.

Couche 3 -- IP Whitelist

Toute API Key doit avoir au moins un IP dans la whitelist -- même une API Key fraîchement créée avec des credentials valides est rejetée tant que la whitelist est vide :

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

Une fois la whitelist dotée d'au moins une entrée, les requêtes provenant d'IPs hors liste reçoivent :

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

Formats acceptés

FormatExempleCommentaire
IPv4 individuel203.0.113.45Un seul endpoint public
Notation CIDR (IPv4)203.0.113.0/24Sous-réseau /24 entier (256 IPs). Utilisez /32 pour un hôte unique
CIDR agrégé172.20.16.0/20Plage privée (exemple) -- accepté littéralement

IPv4 vs IPv6

L'API normalise les adresses ::ffff:A.B.C.D (IPv4 mappé en IPv6, utilisé par des load balancers de confiance) vers l'adresse IPv4 correspondante avant de comparer avec la whitelist. Vous n'avez pas besoin d'inclure la forme IPv6-mappée ; il suffit d'enregistrer l'IPv4 littéral. Pour des clients qui sortent exclusivement via IPv6, enregistrez l'adresse IPv6 complète en notation standard (ex. : 2001:db8::1).

Format de la chaîne

La whitelist attend des chaînes exactes. Un espace avant/après, un masque erroné (/28 quand la plage a 256 IPs), ou un IP en notation avec zéros en tête (203.000.113.045) rejette silencieusement les requêtes sans autre avertissement que le 403 standard. Validez toujours dans le Merchant Portal avec un IP de test avant la mise en production.

Configurez la whitelist dans le Merchant Portal à la création ou à l'édition de l'API Key.

Headers

Headers obligatoires

HeaderValeurObligatoire
AuthorizationApiKey {client_id}:{client_secret} ou Basic {base64(client_id:client_secret)}Oui -- toutes les requêtes
Content-Typeapplication/json (ou multipart/form-data pour les uploads)Oui -- POST, PUT, PATCH avec body. Envoyer application/x-www-form-urlencoded (default de curl -d sans -H) retourne 415 Unsupported Media Type
hmacSignature HMAC-SHA512 du body en hexadécimal lowercaseOui -- uniquement POST sur /api/external/*

Headers optionnels

HeaderValeurEffet
Idempotency-KeyClé unique ≤ 256 caractères (UUID v4 recommandé)Déduplique les replays pendant 24h. Ne fonctionne qu'en POST -- en GET/DELETE l'en-tête est silencieusement ignoré (pas d'erreur)
X-Key-CasecamelCaseConvertit le camelCase des request params en snake_case (entrée) et le snake_case en camelCase pour les clés de toute la response JSON (sortie). Utile pour les clients en JavaScript, TypeScript ou Kotlin
X-Forwarded-ForIP(s) séparés par virguleRespecté uniquement lorsque la connexion TCP directe vient d'un proxy de confiance. Ignoré dans les connexions directes du client

Idempotency-Key -- réponse du serveur

Quand vous envoyez l'en-tête Idempotency-Key, le serveur renvoie la même valeur dans Idempotency-Key dans la response et, si la requête est un replay d'une déjà traitée dans les dernières 24 heures (même clé + même méthode HTTP + même path), il ajoute aussi l'en-tête X-Idempotent-Replay: true et retourne le body en cache tel qu'il a été renvoyé à la première exécution (même code HTTP, même body byte-à-byte). Le cache est limité à (méthode, path, clé) -- utiliser la même clé sur des endpoints différents ne provoque pas de collision. Les clés de plus de 256 caractères sont rejetées avec 400 Idempotency-Key must be at most 256 characters. Seules les réponses 2xx sont mises en cache -- les réponses d'erreur (4xx/5xx) permettent un retry avec la même clé.

X-Key-Case -- conversion en camelCase

Si votre stack travaille en camelCase (JS/TS, Kotlin, Swift), envoyez X-Key-Case: camelCase et l'API acceptera le request body en camelCase (ex. : externalId, pixKey) et renverra la response avec des clés en camelCase. Sans cet en-tête, l'API reste en snake_case (ex. : external_id, pix_key). L'en-tête peut être envoyé sur n'importe quel endpoint /api/external/* -- pas besoin d'être toujours la même valeur par API Key.

HMAC signe le body avant la conversion interne

Si vous envoyez X-Key-Case: camelCase avec un POST qui exige HMAC, signez le body exactement tel qu'il va transiter sur le réseau (i.e., en camelCase si c'est cette sérialisation que vous utilisez). Le HMAC est calculé côté serveur sur le body reçu, pas sur la forme snake_case interne.

Exemple complet

bash
CLIENT_ID="cli_a1b2c3d4e5f6"
CLIENT_SECRET="sk_0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef01"

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

# PIX Cash-Out (POST -- avec HMAC + Idempotency-Key)
# IMPORTANT : clés en ordre alphabétique (amount < description < pix_key < pix_key_type).
# Le serveur réordonne alphabétiquement avant de calculer le HMAC attendu - voir /fr/hmac.
BODY='{"amount":3000,"description":"Paiement","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"

Protections additionnelles

ProtectionDescription
Rate Limiting (API)90 000 req/min (1 500 req/s) par IP sur tout /api/external/*, sauf GET /balance qui n'a pas de rate limit -- polling haute fréquence autorisé. Clé du limiter : (IP, fenêtre de 60s). Quand la limite est dépassée, le serveur répond 429 Too Many Requests avec l'en-tête Retry-After: 60. Dans toutes les réponses 2xx qui traversent le limiter, l'en-tête x-ratelimit-remaining est ajouté avec le nombre de requêtes restantes dans la fenêtre
Rate Limiting (WAF de bord)Regles par API Key/IP dans le WAF de bord lorsqu'elles sont configurees. Opere avant le API, donc un 429 de cette couche n'incremente pas le compteur du API
Rate Limiting (auth)5 req/min sur les endpoints d'authentification admin/merchant (ne s'applique pas à /api/external/*)
Protection DICT sur Cash-OutSpécifique au POST /pix/cash-out quand il résout une clé PIX via DICT. Quand elle s'applique, retourne HTTP 202 avec status: "queued" et déclenche le webhook pix.payout.queued pendant que la transaction attend un retraitement automatique. Voir PIX Cash Out (par clé)
AWS WAFFirewall d'application protegeant l'API avec des regles OWASP (XSS, SQLi, LFI, RFI, RCE)
HTTPS + TLS 1.2+Chiffrement obligatoire sur toutes les connexions
HSTSNavigateurs forcés à utiliser HTTPS

Headers de rate limiting dans la response

Chaque fois qu'une requête passe par le rate limiter, la réponse inclut :

HeaderApparaît dansValeur
x-ratelimit-remainingRéponses 2xx (après passage par le limiter)Entier : requêtes restantes dans la fenêtre actuelle de 60s, limité par IP
Retry-AfterUniquement dans 429 Too Many Requests60 (toujours, en secondes) -- attendez avant de réessayer

Comment le limiter compte les « fenêtres »

Le rate limiting utilise des fenetres fixes de 60 secondes, pas de sliding window. En pratique, la limite stable de 1 500 req/s par IP est le comportement que le client doit prendre en compte.

Pourquoi HMAC-SHA512 et pas mTLS ?

Le mTLS (mutual TLS) authentifie la connexion, pas le contenu. Si la connexion est authentifiée, toutes les requêtes passent sans validation individuelle.

Le HMAC valide chaque requête séparément. Même à l'intérieur d'une connexion valide, toute altération du payload fait rejeter la requête.

AspectmTLSHMAC-SHA512
ValideCanal TLSPayload de la requête
GestionCertificats X.509 (émission, rotation, révocation, CRL/OCSP)Génère la paire, met à jour, invalide
Risque opérationnelCertificats expirés -- cause fréquente d'incidentsLa clé est une simple chaîne
Intégrité du contenuNonOui

Le TLS garantit déjà le chiffrement du transport. Le HMAC ajoute l'intégrité et l'authenticité du payload -- ce que le mTLS seul ne couvre pas.

Réponses d'erreur

L'API a 4 formats distincts d'erreur

Selon la couche de validation qui rejette la requete, le format du body JSON est different. Inspectez toujours le format avant de parser l erreur cote client :

  1. {"error": {status, message}} -- Content-Type (415), API Key + IP Whitelist (401/403), Idempotency (400), rate limiting (429).
  2. {"error": "forbidden", "message": "..."} -- uniquement Permission API Key (403).
  3. {"worked": false, "detail": "..."} -- uniquement HMAC validation (400, 401, 403).
  4. {"errors": {atom: "msg"}} -- erreurs métier (toute erreur métier 4xx/5xx).

Couche 0 -- Content-Type

Avant toute authentification, POST/PUT/PATCH sans un Content-Type accepté (application/json ou multipart/form-data) est bloqué.

415 -- Content-Type non supporté

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

Piège courant avec curl -d

curl -d '{"...":""}' URL (sans -H) envoie Content-Type: application/x-www-form-urlencoded par defaut. L API peut l interpreter comme form-urlencoded et non comme JSON. Ajoutez -H 'Content-Type: application/json' pour obtenir la validation attendue.

Couche 1 -- API Key + IP Whitelist

Les erreurs de credentials absents, invalides, API Key inactive/expirée ou IP hors whitelist viennent au format {"error": {status, message}} :

401 -- Credentials absents

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

401 -- Credentials invalides

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

401 -- API Key inactive

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

401 -- API Key expirée

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

403 -- Whitelist d'IP vide

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

403 -- IP non autorisé

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

403 -- Compte inactif

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

403 -- Permission insuffisante

Ce 403 vient de la validation des permissions

Cette erreur apparait quand l API Key est valide, l IP est autorisee, mais la credential ne possede pas la permission exigee par l endpoint. C est pourquoi le JSON differe des erreurs de credential/IP : il utilise {"error": "forbidden", "message": "..."}.

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

Couche 2 -- Validation HMAC-SHA512

Les erreurs emises par la validation HMAC utilisent toujours le format {"worked": false, "detail": "..."} avec des codes HTTP differents selon la cause :

401 -- Signature invalide ou header absent

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

400 -- Body absent ou JSON invalide

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 sans HMAC secret configuré

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

Couche 3 -- Erreurs métier

Après l'authentification, les erreurs de validation, paramètres manquants, ressources introuvables et règles métier viennent au format {"errors": {atom: "msg"}} :

400 -- Requête invalide

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

404 -- Ressource introuvable

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

401 -- Non autorisé (règle métier)

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

422 -- Entité non traitable

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

Couche 4 -- Rate Limiting (API ou WAF de bord)

429 -- Rate Limit dépassé

Body du 429 emis par la couche de rate limiting :

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

Headers inclus dans la response 429 :

HeaderValeur
Retry-After60 (secondes à attendre avant retry)

Comment réagir au 429

  • Backoff exponentiel : commencez à 60s (valeur du Retry-After), doublez à chaque retry suivant jusqu'à un plafond raisonnable (ex. : 5 min).
  • N'ignorez jamais l'en-tête : même si votre client a sa propre stratégie de retry, le Retry-After est la source de vérité canonique pour cet endpoint.
  • Si le 429 vient du WAF de bord (couche au-dessus du API), le body peut avoir un shape différent -- le status 429 avec Retry-After: 60 reste le pattern pour toute couche.

Couche 5 -- Idempotency

400 -- Idempotency-Key trop longue

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

Permissions

Chaque API Key possède une liste de permissions qui déterminent quels endpoints sont accessibles. Si l'API Key n'a pas la permission nécessaire, la requête est rejetée avec 403 Forbidden.

Comment obtenir l'API Key

  1. Utilisez la base de production api.minhakonta.com
  2. Demandez l'emission des identifiants pour le compte cible
  3. Indiquez les IPs qui doivent etre ajoutees a la whitelist
  4. Indiquez les permissions necessaires (voir tableau ci-dessous)
  5. Apres emission, stockez le client_id et le client_secret en securite

Pour editer les permissions d'une API Key existante, demandez la mise a jour des permissions de l'identifiant.

Obligatoire pour envoyer PIX

Pour réaliser des opérations de PIX Cash-Out (envoi de PIX), l'API Key doit avoir la permission transfer:write. Sans cette permission, toutes les tentatives d'envoi retournent 403 Forbidden avec le message API key lacks permission: transfer:write.

Permissions minimales recommandées pour une opération complète :

  • Cash-In (recevoir) : pix:write + transfer:read
  • Cash-Out (envoyer) : transfer:write + transfer:read
  • Consultations : transfer:read + account:read + statement:read
  • Webhooks : account:write + account:read

Permissions disponibles

PermissionDescription
pix:writeGénérer QR Code (Cash-In)
pix:readLister les clés PIX
transfer:writeEnvoyer PIX (Cash-Out)
transfer:readConsulter transactions (par ID, E2E, Tag, External ID), reçu, lister transactions
payment:writeDemander remboursement (refund) et envoyer une defense MED
payment:readLister et consulter MED
account:writeCréer et supprimer webhooks
account:readConsulter solde, lister webhooks, valider CPF
statement:readConsulter relevé

Permissions par endpoint

EndpointMéthodePermission
/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

Réponse d'erreur -- 403 (Permission insuffisante)

Voir le format dans Couche 1 -- 403 Permission insuffisante.

Utilisez payment:read pour lister et consulter les MEDs. Utilisez payment:write pour envoyer une defense via POST /api/external/med/:id/defense. L'endpoint de defense recoit du JSON signe par HMAC; les uploads binaires restent disponibles dans les portails Minha Konta.

Les permissions sont configurées à la création de l'API Key par le Merchant Portal ou par l'API d'administration.

Sécurité

  • N'exposez jamais le client_secret dans du code frontend ou des dépôts publics
  • Utilisez des variables d'environnement sur votre serveur
  • L'API Key peut expirer si le champ expires_at est configuré ; sinon, elle reste valide jusqu'à révocation manuelle dans le Merchant Portal
  • Configurez les IPs autorisés dans la whitelist -- une whitelist vide bloque la clé avec 403 IP whitelist required

Minha Konta Instituição de Pagamento - ISPB 39929224