Démarrage rapide
- Créez une cléTableau de bord → Clés API → Nouvelle clé test. La clé secrète
sk_test_…ne s'affiche qu'une fois : copiez-la tout de suite. - Créez un paiement
POST /v1/paymentsavec le montant, la méthode et votre référence. - Redirigez le clientvers le
checkout_urlrenvoyé. Il y paie avec MonCash ou Natcash. - Confirmez côté serveur
GET /v1/payments/:idjusqu'àsucceeded, puis livrez. Ne livrez jamais sur la seule foi de la page de retour : un client peut la fermer avant la fin.
URL de base : https://api.fredapay.com
/openapi.yaml. Importez-la dans Postman, Swagger UI, ou générez un client dans le langage de votre choix.
Authentification
En-tête Authorization: Bearer sk_test_… (ou sk_live_…) sur chaque requête. Les clés live s'activent après vérification du compte.
Sandbox et Live
Il y a une seule API pour tous les services. Le préfixe de la clé décide de l'environnement :
sk_test_…: Sandbox. Argent fictif, cartes de test. Aucun risque.sk_live_…: Live. Vrais paiements, vraies cartes, vrai argent.
Les données des deux environnements sont complètement séparées : soldes, paiements, retraits, cartes, titulaires. Une carte ou un titulaire créé en Sandbox n'existe pas en Live. Vous pouvez revenir au Sandbox à tout moment depuis le tableau de bord.
Une clé sk_live_… peut être créée dès que la vérification KYC/KYB est approuvée : elle donne accès aux paiements (Gateway). L'émission de cartes en Live (/v1/cards) demande une autorisation séparée, à demander depuis le tableau de bord. Si elle est refusée, la même clé continue de fonctionner pour les paiements, et vous pouvez redemander l'accès plus tard. Le solde Live du Master Wallet est alimenté uniquement par de vrais dépôts.
Authorization: Bearer sk_test_VOTRE_CLE_SECRETE Content-Type: application/json
Tester ma clé API et ma configuration
Avant d'écrire du code, vérifiez que Freda Pay accepte votre clé et que votre compte est bien configuré. L'outil appelle GET /v1/ping et POST /v1/webhooks/test, les mêmes appels que vous ferez depuis votre serveur.
api.fredapay.com : elle n'est ni enregistrée ni transmise ailleurs, et disparaît quand vous fermez la page. Si vous testez une clé sk_live_…, supprimez-la ensuite du presse-papiers.
Que vérifie l'outil ?
- La clé est reconnue (ni mal écrite, ni révoquée) et l'environnement : Sandbox ou Live.
- Le compte est actif (non suspendu). Une clé Live demande aussi le Live activé après KYC/KYB.
- L'émission de cartes est autorisée (en Live, par l'accès Issuing séparé).
- Un endpoint webhook est enregistré. Le bouton « webhook de test » envoie un événement
webhook.testsigné et vous dit si votre serveur a répondu2xx.
Si la vérification échoue
| Code | Sens | Que faire |
|---|---|---|
MISSING_API_KEY | Clé absente ou mal formée | Elle doit ressembler à sk_test_… ou sk_live_…, sans espace. |
INVALID_API_KEY | Clé inconnue ou révoquée | Créez une nouvelle clé dans Tableau de bord → Clés API. |
ACCOUNT_SUSPENDED | Compte suspendu | Écrivez à issuing@fredapay.com. |
LIVE_NOT_ENABLED | Clé Live, Live pas encore activé | Terminez la vérification KYC/KYB. |
NO_WEBHOOK_ENDPOINT | Aucun webhook enregistré | Enregistrez une URL avec POST /v1/webhooks (voir Notifications). |
curl https://api.fredapay.com/v1/ping \ -H "Authorization: Bearer sk_test_VOTRE_CLE_SECRETE" curl -X POST https://api.fredapay.com/v1/webhooks/test \ -H "Authorization: Bearer sk_test_VOTRE_CLE_SECRETE"
Créer un paiement
Crée une demande de paiement et renvoie l'adresse où votre client paiera.
Paramètres
| Champ | Type | Description |
|---|---|---|
amount requis | nombre | Montant. En HTG (MonCash, Natcash) : minimum 20. Par carte, en USD : minimum 0,50. |
method requis | texte | moncash, natcash, moncash_ussd (le client reçoit une invite USSD sur son téléphone, sans page de paiement) ou all (le client choisit), ou carte : paiement par carte bancaire (Visa, Mastercard), toujours en USD. Voir Paiement par carte. |
reference requis | texte | Votre identifiant unique, par exemple le numéro de commande. Si vous renvoyez une référence déjà utilisée, vous recevez le paiement existant au lieu d'en créer un second. |
currency optionnel | texte | HTG par défaut pour MonCash et Natcash ; USD par défaut et obligatoire pour carte. |
customer_name / customer_email optionnel | texte | Pour carte : le nom du client (et son email pour le reçu). |
return_url optionnel | texte | Pour carte : adresse https où la page de paiement ramène le client après le paiement. |
phone optionnel | texte | Requis seulement pour moncash_ussd. Format 509XXXXXXXX. |
Réponse
Code 201 avec le paiement. Gardez son id pour vérifier le statut, et redirigez le client vers checkout_url (il vaut null pour moncash_ussd).
Paiement par carte (USD)
Avec method: "carte" et currency: "USD", la réponse contient un checkout_url : une page de paiement sécurisée Freda Pay où le client entre sa carte (Visa, Mastercard). Redirigez simplement votre client vers cette adresse, comme pour MonCash. Les numéros de carte ne passent jamais par votre serveur.
Ajoutez return_url (adresse https de votre site) : après le paiement, la page ramène le client chez vous. Le retour du navigateur ne prouve pas le paiement : confirmez toujours par le webhook payment.succeeded ou par GET /v1/payments/:id.
Un paiement par carte réussi crédite votre solde Gateway USD, frais déduits (fee, net_amount). Un paiement refusé ou expiré passe à failed et déclenche payment.failed. Montant minimum : 0,50 USD. En Live, le paiement par carte demande une activation : sans elle, l'API répond 403 CARD_NOT_AVAILABLE. Les plugins WooCommerce et Wix proposent déjà la carte.
Un lien de paiement unique accepte MonCash, Natcash et carte : le prix est dans la devise du lien, et le client est débité dans la devise du moyen choisi (USD pour la carte, HTG pour MonCash et Natcash), converti au taux fixe de la plateforme.
curl -X POST https://api.fredapay.com/v1/payments \ -H "Authorization: Bearer sk_test_VOTRE_CLE" \ -H "Content-Type: application/json" \ -d '{ "amount": 1500, "method": "moncash", "reference": "CMD-1001" }'
const res = await fetch("https://api.fredapay.com/v1/payments", { method: "POST", headers: { Authorization: `Bearer ${process.env.FREDA_SECRET_KEY}`, "Content-Type": "application/json", }, body: JSON.stringify({ amount: 1500, method: "moncash", reference: "CMD-1001" }), }); const payment = await res.json(); // Redirigez le client : // res.redirect(payment.checkout_url)
<?php $ch = curl_init("https://api.fredapay.com/v1/payments"); curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => [ "Authorization: Bearer " . getenv("FREDA_SECRET_KEY"), "Content-Type: application/json", ], CURLOPT_POSTFIELDS => json_encode([ "amount" => 1500, "method" => "moncash", "reference" => "CMD-1001", ]), ]); $payment = json_decode(curl_exec($ch), true); header("Location: " . $payment["checkout_url"]);
import os, requests r = requests.post( "https://api.fredapay.com/v1/payments", headers={"Authorization": f"Bearer {os.environ['FREDA_SECRET_KEY']}"}, json={"amount": 1500, "method": "moncash", "reference": "CMD-1001"}, ) payment = r.json() # Redirigez le client vers payment["checkout_url"]
{
"id": "pay_8f2a91c4d0e1b7a3",
"reference": "CMD-1001",
"amount": 1500,
"currency": "HTG",
"method": "moncash",
"status": "pending",
"fee": null,
"net_amount": null,
"checkout_url": "https://…",
"created_at": "2026-09-24T14:02:11Z"
}
Vérifier un paiement
Renvoie l'état actuel du paiement. À chaque appel, Freda Pay interroge le réseau MonCash ou Natcash pour obtenir le statut à jour.
Statuts possibles
| Statut | Signification | Que faire |
|---|---|---|
pending | Le client n'a pas encore payé. | Vérifiez à nouveau dans 3 à 5 secondes. |
succeeded | Paiement confirmé. fee et net_amount sont remplis. | Livrez la commande. |
failed | Le paiement n'a pas abouti. | Proposez au client de réessayer avec une nouvelle référence. |
Arrêtez de vérifier après environ 10 minutes sans confirmation : le client a probablement abandonné.
curl https://api.fredapay.com/v1/payments/pay_8f2a91c4d0e1b7a3 \
-H "Authorization: Bearer sk_test_VOTRE_CLE"
async function waitForPayment(id) { for (let i = 0; i < 120; i++) { const res = await fetch(`https://api.fredapay.com/v1/payments/${id}`, { headers: { Authorization: `Bearer ${process.env.FREDA_SECRET_KEY}` }, }); const p = await res.json(); if (p.status !== "pending") return p; await new Promise(r => setTimeout(r, 5000)); } return null; // abandonné }
<?php $ch = curl_init("https://api.fredapay.com/v1/payments/" . $paymentId); curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => ["Authorization: Bearer " . getenv("FREDA_SECRET_KEY")], ]); $payment = json_decode(curl_exec($ch), true); if ($payment["status"] === "succeeded") { // marquez la commande comme payée }
import os, time, requests def wait_for_payment(payment_id): headers = {"Authorization": f"Bearer {os.environ['FREDA_SECRET_KEY']}"} for _ in range(120): p = requests.get(f"https://api.fredapay.com/v1/payments/{payment_id}", headers=headers).json() if p["status"] != "pending": return p time.sleep(5) return None
Notifications (webhooks)
Plutôt que d'interroger GET /v1/payments/:id en boucle, enregistrez une URL sur votre serveur : Freda Pay lui envoie un POST signé dès qu'un événement se produit.
Enregistrer votre URL
{ "url": "https://votre-site.com/webhooks/freda" }, en https://. La réponse contient un secret (whsec_…) affiché une seule fois : copiez-le tout de suite.
GET | Relit l'URL active (sans le secret). |
POST | Remplace l'URL, génère un nouveau secret. |
DELETE | Désactive l'endpoint. |
POST .../rotate-secret | Nouveau secret, même URL. |
Événements disponibles
| Événement | Déclenché quand… |
|---|---|
payment.succeeded | Un paiement MonCash/Natcash/carte est confirmé. |
payment.failed | Un paiement par carte a été refusé ou a expiré (voir data.failure_reason). |
payout.succeeded | Un retrait est arrivé chez le destinataire. |
payout.failed | Un retrait a échoué (voir data.failure_reason). |
card.created | Une carte demandée est active (remplace le polling de création). |
card.failed | La création d'une carte a échoué. |
card.frozen / card.unfrozen | Une carte a été gelée / dégelée. |
card.terminated | Une carte a été supprimée définitivement. |
card.funded | Une carte a été rechargée. Les données contiennent amount, fee, total_debited (montant + frais prélevés sur le Master Wallet) et le nouveau balance. |
card.withdrawn | Des fonds ont été retirés d'une carte vers le Master Wallet. Les données contiennent amount, fee, net_credited et le nouveau balance. |
webhook.test | Événement de test envoyé à votre demande (Tester ma clé API ou POST /v1/webhooks/test). |
card.transaction | Un mouvement a eu lieu sur une carte (achat, remboursement, refus). Les données contiennent card_id, type, amount, merchant_name, jamais le numéro de carte. |
Vérifier la signature
Chaque livraison inclut X-Freda-Signature: sha256=…, un HMAC-SHA256 du corps brut signé avec votre secret. Recalculez-le et comparez en temps constant.
POST /webhooks/freda HTTP/1.1 X-Freda-Signature: sha256=5f4d...b2a1 X-Freda-Event-Id: evt_8f2a91c4d0e1 X-Freda-Delivery-Attempt: 1 Content-Type: application/json { "id": "evt_8f2a91c4d0e1", "event": "payment.succeeded", "created_at": "2026-09-27T10:15:00Z", "data": { "id": "pay_7c1a...", "reference": "CMD-001", "amount": 1000, "currency": "HTG", "net_amount": 930 } }
const crypto = require("crypto"); function isValid(rawBody, signatureHeader, secret) { const expected = crypto.createHmac("sha256", secret) .update(rawBody) .digest("hex"); const received = signatureHeader.replace("sha256=", ""); return crypto.timingSafeEqual( Buffer.from(expected), Buffer.from(received) ); } // Utilisez rawBody AVANT tout JSON.parse : un corps // re-sérialisé ne redonne pas les mêmes octets.
2xx, ou aucune réponse sous 8s : nouvelle tentative à +5s puis +30s. Abandon après 3 échecs. Répondez 200 vite, traitez l'événement après.
data.id (ou id de l'événement) comme clé d'idempotence de votre côté.
Créer un retrait
Envoie de l'argent de votre solde Gateway vers un numéro MonCash ou Natcash. Utile pour retirer vos gains ou payer un fournisseur, un livreur ou un employé. Frais : 5 % du montant, en plus du montant envoyé. Le virement bancaire ou Zelle (frais fixes de 100 HTG, minimum 1 000 HTG) se demande depuis le tableau de bord, section Retraits, vers le compte que vous choisissez parmi ceux que vous avez enregistrés (la devise dépend du pays : HTG ou USD en Haïti, USD ailleurs ; Zelle est payé en USD) : il est traité à la main par notre équipe et n'est pas disponible par l'API.
Paramètres
| Champ | Type | Description |
|---|---|---|
amount requis | nombre | Montant en HTG. |
method requis | texte | moncash ou natcash. |
recipient requis | texte | Numéro MonCash ou Natcash du bénéficiaire (réseaux d'Haïti), format 509XXXXXXXX sans espaces ni +. |
reference requis | texte | Votre identifiant unique pour ce retrait. Renvoyer la même référence renvoie le retrait existant, ce qui vous protège contre un double envoi. |
Réponse
Code 202 : le retrait est accepté et passe en file d'attente avec le statut pending. Les retraits sont traités un par un, avec un intervalle de sécurité d'environ deux minutes entre chaque envoi. Suivez le résultat avec Vérifier un retrait.
400 INSUFFICIENT_BALANCE et rien n'est envoyé. Les frais de retrait (5 % du montant pour MonCash et Natcash) sont ajoutés au montant débité : il faut donc montant + frais sur le solde. Cette somme est mise de côté dès l'acceptation de la demande ; si le retrait échoue, elle vous est rendue en entier.
curl -X POST https://api.fredapay.com/v1/payouts \ -H "Authorization: Bearer sk_test_VOTRE_CLE" \ -H "Content-Type: application/json" \ -d '{ "amount": 500, "method": "natcash", "recipient": "50937001234", "reference": "RET-2026-001" }'
const res = await fetch("https://api.fredapay.com/v1/payouts", { method: "POST", headers: { Authorization: `Bearer ${process.env.FREDA_SECRET_KEY}`, "Content-Type": "application/json", }, body: JSON.stringify({ amount: 500, method: "natcash", recipient: "50937001234", reference: "RET-2026-001", }), }); const payout = await res.json(); // status: "pending"
<?php $ch = curl_init("https://api.fredapay.com/v1/payouts"); curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => [ "Authorization: Bearer " . getenv("FREDA_SECRET_KEY"), "Content-Type: application/json", ], CURLOPT_POSTFIELDS => json_encode([ "amount" => 500, "method" => "natcash", "recipient" => "50937001234", "reference" => "RET-2026-001", ]), ]); $payout = json_decode(curl_exec($ch), true);
import os, requests r = requests.post( "https://api.fredapay.com/v1/payouts", headers={"Authorization": f"Bearer {os.environ['FREDA_SECRET_KEY']}"}, json={ "amount": 500, "method": "natcash", "recipient": "50937001234", "reference": "RET-2026-001", }, ) payout = r.json() # status: "pending"
Vérifier un retrait
Renvoie l'état du retrait.
| Statut | Signification |
|---|---|
pending | En file d'attente ou en cours d'envoi. |
success | L'argent a été envoyé. fee contient les frais du réseau. |
failed | L'envoi a échoué et votre solde n'a pas été débité. failure_reason explique pourquoi, par exemple un numéro invalide. |
curl https://api.fredapay.com/v1/payouts/po_3c9e71aa04b2d58f \
-H "Authorization: Bearer sk_test_VOTRE_CLE"
{
"id": "po_3c9e71aa04b2d58f",
"reference": "RET-2026-001",
"amount": 500,
"method": "natcash",
"recipient": "50937001234",
"status": "success",
"fee": 12.5,
"failure_reason": null
}
Consulter le solde
Renvoie vos soldes :
- gateway (HTG) : l'argent reçu par MonCash et Natcash, frais déduits. C'est ce solde que vous retirez avec les retraits.
- gateway_usd (USD) : l'argent reçu par carte bancaire, frais déduits. Il se retire par virement bancaire ou Zelle depuis le tableau de bord.
gateway_balancesregroupe les deux :{ "HTG": ..., "USD": ... }. - master_wallet (USD) : les fonds que vous avez déposés pour émettre des cartes et payer votre abonnement.
curl https://api.fredapay.com/v1/balance \ -H "Authorization: Bearer sk_test_VOTRE_CLE" { "gateway": { "available": 12400, "currency": "HTG" }, "gateway_usd": { "available": 182.3, "currency": "USD" }, "master_wallet": { "available": 95.96, "currency": "USD" } }
Émettre des cartes virtuelles
Créez des cartes Visa ou Mastercard virtuelles, préfinancées depuis votre Master Wallet (USD), pour un usage individuel ou une carte d'entreprise.
/dashboard/cards/… (session, pour le tableau de bord) et /v1/cards/… (clé API sk_test_…/sk_live_…, pour votre backend). Même corps de requête, même réponse. Utilisez /v1/cards pour intégrer.
Titulaires de carte
Un titulaire est requis pour chaque carte individuelle (pas pour une carte entreprise). Vous pouvez en créer plusieurs, par exemple un par employé, puis choisir lequel reçoit la carte avec holder_id.
GET /cards/holders | Liste de vos titulaires de l'environnement de la clé (du plus récent au plus ancien). |
GET /cards/holders/:id | Un titulaire (404 NOT_FOUND s'il n'appartient pas à votre compte). |
POST /cards/holders | Crée un titulaire (champs ci-dessous). |
L'ancien POST /cards/holder-profile fonctionne toujours et crée simplement un titulaire de plus.
Identifiant, statuts et règles
- Quel identifiant envoyer ? Le champ
iddu titulaire (formempc_…), tel que renvoyé parPOST /cards/holders,GET /cards/holdersetGET /cards/holders/:id. Envoyez-le dansholder_iddePOST /cards. Toujours explicitement en production. - Statuts possibles :
pending(inscription en cours),enrolled(prêt) etfailed(échec, voirfailure_reason). Seul un titulaireenrolledpeut recevoir une carte. La photo (photo_url) est facultative : elle n'est jamais exigée. - Délai : la création est synchrone. Quand
POST /cards/holdersrépond201, le titulaire est déjàenrolledet utilisable tout de suite : aucune attente ni nouvelle tentative n'est nécessaire. Si la réponse est une erreur, le titulaire est enregistré enfailed: corrigez la cause puis recréez-le. - Doublons : si un titulaire
enrolledexiste déjà avec le même e-mail dans le même environnement,POST /cards/holdersne crée rien de plus : il répond200avec"already_existed": trueet l'idexistant. - Sandbox et Live sont séparés : le mode vient du préfixe de la clé (
sk_test_ousk_live_). Un titulaire créé avec une clé de test n'existe pas en Live, et inversement. Utiliser un titulaire de l'autre mode renvoie409 HOLDER_MODE_MISMATCH. Le titulaire est rattaché à votre compte marchand, pas à une clé précise : deux clés du même mode voient les mêmes titulaires. - Sans
holder_id: l'API prend le titulaireenrolledle plus récent de votre compte, dans l'environnement de la clé. Si plusieurs utilisateurs créent des titulaires en même temps avec la même clé, le « dernier » peut être celui d'un autre utilisateur : ne vous fiez pas à ce défaut, envoyez toujoursholder_idet gardez l'idobtenu à la création.
| Champ | Description |
|---|---|
firstName, lastName | Nom du titulaire. |
email | Adresse email. |
country | Code pays à 2 lettres (ISO 3166), ex. US, FR, NG. |
dob | Date de naissance, YYYY-MM-DD. |
identificationNumber | Numéro de pièce d'identité. |
identity | Pièce d'identité officielle, requise pour émettre une carte : objet avec type (NIN, PASSPORT, VOTERS_CARD ou DRIVERS_LICENSE), number, imageUrl (image en Base64 : data:image/jpeg;base64,..., PNG, JPEG ou WebP, environ 700 Ko au maximum ; un lien https direct vers l'image est aussi accepté, nous la téléchargeons et la convertissons) et country (2 lettres, par défaut le pays du titulaire). |
phoneShortCode, phoneNumber | Indicatif et numéro, ex. +1 et 2025550123. |
address | Objet avec street, city, state, postalCode et country : les 5 sont requis. |
photoUrl optionnel | Photo ou logo pour votre tableau de bord (data URL png/jpeg/webp de moins de 200 Ko, ou URL https). |
curl -X POST https://api.fredapay.com/v1/cards/holders \ -H "Authorization: Bearer sk_test_VOTRE_CLE" \ -H "Content-Type: application/json" \ -d '{ "firstName": "Rose", "lastName": "Lakay", "email": "rose@example.com", "country": "US", "dob": "1990-05-12", "identificationNumber": "ID123456", "identity": { "type": "PASSPORT", "number": "P1234567", "imageUrl": "data:image/jpeg;base64,/9j/4AAQ...", "country": "US" }, "phoneShortCode": "+1", "phoneNumber": "37001234", "address": { "street": "350 Fifth Avenue", "city": "New York", "state": "NY", "postalCode": "HT6110", "country": "US" } }'
{
"id": "mpc_1a2b3c4d5e6f7a8b",
"already_existed": false,
"holder": {
"id": "mpc_1a2b3c4d5e6f7a8b",
"mode": "sandbox",
"status": "enrolled",
"first_name": "Rose",
"last_name": "Lakay",
"email": "rose@example.com",
"country": "US",
"photo_url": null,
"dob": "1990-05-12",
"phone": "+137001234",
"address": { "street": "350 Fifth Avenue", "city": "New York", "state": "NY", "postalCode": "HT6110", "country": "US" },
"identification_number_last4": "3456",
"failure_reason": null,
"created_at": "2026-10-11T10:00:00.000Z"
},
"profile": { /* même objet que holder (ancien nom, conservé) */ }
}
{ "holders": [
{ "id": "mpc_1a2b3c4d5e6f7a8b", "mode": "sandbox", "status": "enrolled",
"first_name": "Rose", "last_name": "Lakay", "email": "rose@example.com",
"country": "US", "photo_url": null, "failure_reason": null,
"created_at": "2026-10-11T10:00:00.000Z" }
] }
// GET /v1/cards/holders/mpc_1a2b… renvoie { "holder": { …même objet… } }
400 MISSING_FIELDS champ ou adresse incomplète (le message liste les champs) 400 INVALID_PHOTO photoUrl invalide ou trop lourde 400 INVALID_JSON corps non JSON 401 MISSING_API_KEY / INVALID_API_KEY 403 LIVE_NOT_ENABLED clé live avant vérification du compte 502 HOLDER_CREATION_FAILED le service de cartes n'a pas créé le titulaire 502 CARD_SERVICE_ERROR / 503 CARD_PROVIDER_AUTH service de cartes en erreur (un titulaire en échec est conservé avec status "failed" + failure_reason)
Créer une carte
La création est asynchrone : réponse 202, statut pending. Quelques instants plus tard, le statut passe à active ou failed. Interrogez Gérer une carte, ou recevez un webhook.
id de la réponse
C'est le seul identifiant dont vous avez besoin. Le même id (ex. card_8f2a91c4d0e1) sert pour tous les autres appels : voir la carte, son historique, l'alimenter, la geler. Enregistrez-le dès la création. Aucun autre identifiant n'est nécessaire.
Paramètres
| Champ | Type | Description |
|---|---|---|
reference requis | texte | Votre identifiant unique. Rejoué, renvoie la carte existante. |
kind optionnel | texte | individual (défaut) ou business. |
brand optionnel | texte | VISA ou MASTERCARD. |
amount requis | nombre | Montant à préfinancer, en dollars (pas en centimes). Débité du Master Wallet immédiatement. |
holder_id optionnel | Carte individuelle : id du titulaire (mpc_…, voir Titulaires). Recommandé : toujours l'envoyer. Sans lui, l'API prend le titulaire enrolled le plus récent de votre compte dans l'environnement de la clé. | |
business_name | texte | Requis si kind = business. Pas de profil titulaire nécessaire dans ce cas. |
400 BELOW_MINIMUM_BALANCE si le Master Wallet ne couvre pas le montant (préfinancement + frais de création). Si la création échoue après le débit, le montant est automatiquement recrédité.
Erreurs de POST /cards
| HTTP | Code | Cause et solution |
|---|---|---|
| 400 | MISSING_FIELDS | reference manquante (ou business_name pour une carte entreprise). |
| 400 | INVALID_BRAND | brand doit être VISA ou MASTERCARD. |
| 400 | HOLDER_PROFILE_REQUIRED | Carte individuelle, holder_id absent, et aucun titulaire n'existe dans l'environnement de la clé. Créez-en un (POST /cards/holders) avec une clé du même mode, puis envoyez son id. |
| 409 | HOLDER_NOT_ENROLLED | Le titulaire (indiqué par holder_id, ou aucun titulaire prêt sans holder_id) n'a pas le statut enrolled. Le message donne le statut et la raison d'échec ; recréez le titulaire. |
| 400 | HOLDER_PROFILE_REQUIRED | Carte individuelle sans holder_id alors qu'aucun titulaire n'existe dans l'environnement de la clé. Voir Titulaires. |
| 409 | HOLDER_NOT_ENROLLED | Le titulaire n'est pas au statut enrolled (pending ou failed). |
| 409 | HOLDER_MODE_MISMATCH | Le titulaire appartient à l'autre environnement (Sandbox/Live). |
| 409 | HOLDER_PROFILE_INCOMPLETE | Ancien titulaire sans lien avec le service de cartes et non récupérable : recréez-le. |
| 404 | NOT_FOUND | holder_id inconnu pour votre compte. |
| 400 | CARD_LIMIT_REACHED | Limite de cartes du plan Issuing atteinte (par environnement). |
| 400 | BELOW_MINIMUM_BALANCE | Master Wallet insuffisant. |
| 403 | ISSUING_LIVE_NOT_ENABLED | Clé live avant l'autorisation Issuing Live. |
| 401 / 403 | MISSING_API_KEY, INVALID_API_KEY, LIVE_NOT_ENABLED | Voir Erreurs. |
| 502 / 503 | CARD_SERVICE_ERROR, CARD_PROVIDER_AUTH, CARD_LIVE_NOT_CONFIGURED | Service de cartes indisponible ; rien n'est débité (ou remboursé aussitôt). Réessayez plus tard. |
Spécification complète et publique : https://api.fredapay.com/openapi.yaml.
curl -X POST https://api.fredapay.com/dashboard/cards \ -H "Authorization: Bearer VOTRE_SESSION_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "reference": "CARD-001", "brand": "VISA", "amount": 20 }'
const res = await fetch("https://api.fredapay.com/dashboard/cards", { method: "POST", headers: { Authorization: `Bearer ${sessionToken}`, "Content-Type": "application/json", }, body: JSON.stringify({ reference: "CARD-001", brand: "VISA", amount: 20 }), }); const card = await res.json(); // status: "pending"
{
"id": "card_8f2a91c4d0e1",
"reference": "CARD-001",
"kind": "individual",
"brand": "VISA",
"status": "pending",
"created_at": "2026-09-25T04:15:04Z"
}
Gérer une carte
Chaque action existe sous /dashboard/cards/… (session) et /v1/cards/… (clé API). Colonne ci-dessous : le chemin après ce préfixe.
| Action | Endpoint | Effet |
|---|---|---|
| Voir une carte | GET /cards/:id | Statut, masked_pan, solde. |
| Lister les cartes | GET /cards | Toutes vos cartes. |
| Historique des transactions | GET /cards/:id/transactions | Liste des achats/crédits + month_spend (dépensé ce mois-ci). |
| Ajouter des fonds | POST /cards/:id/fund | { "amount": 10, "reference": "FUND-001" }. Débite le Master Wallet (montant + frais de recharge). La réponse ajoute fee et total_debited. reference (ou l'en-tête Idempotency-Key) est facultative mais recommandée : rejouer la même clé après un timeout ne débite pas deux fois et renvoie idempotent_replay: true ; la même clé avec un autre montant renvoie 409 IDEMPOTENCY_MISMATCH. |
| Retirer des fonds | POST /cards/:id/withdraw | { "amount": 5, "reference": "WD-001" }. Recrédite le Master Wallet (montant moins les frais de retrait). La réponse ajoute fee et net_credited. Même règle d'idempotence que /fund. Solde insuffisant : 400 INSUFFICIENT_CARD_BALANCE. |
| Geler | POST /cards/:id/freeze | Bloque tout achat et tout ajout/retrait de fonds. |
| Dégeler | POST /cards/:id/unfreeze | Réactive la carte. |
| Fermer définitivement | POST /cards/:id/terminate | Irréversible. La carte est fermée chez notre partenaire, son solde restant est reversé au Master Wallet, puis elle disparaît de vos listes. |
| Simuler une transaction SANDBOX | POST /cards/:id/simulate-transaction | { "amount": 12.5, "type": "DEBIT" } (ou CREDIT). Pour tester l'historique. Refusé en mode Live. |
| Voir le numéro complet | POST /cards/:id/reveal | Réponse : { number, cvv, expiry_month, expiry_year, holder_name }. Dans le tableau de bord, seules les cartes business ont un bouton Détails : les détails d'une carte individuelle ne sont jamais visibles par vos équipes. Par l'API, cette route sert uniquement à afficher la carte à son titulaire ; ne stockez ni ne journalisez jamais le numéro ni le CVV. |
pending (création en cours) → active ou failed. Une carte active peut passer à disabled (gelée) puis revenir à active, ou à terminated (définitif).
409 CARD_FROZEN sur /fund ou /withdraw si la carte est disabled. Dégelez-la d'abord.
Où la carte fonctionne
Les cartes sont des cartes virtuelles en USD. Elles sont acceptées chez les commerçants qui acceptent les cartes USD, surtout des commerçants américains (par exemple Meta, PayPal, Google, Canva). Certaines catégories de commerçants et certains pays sont refusés.
Catégories de commerçants refusées
Un commerçant est identifié par un code de catégorie (MCC). Les cartes sont refusées pour ceux-ci :
| Code MCC | Catégorie |
|---|---|
4829 | Transferts d'argent |
5962 | Services d'organisation de voyages |
5966 | Télémarketing sortant |
6051 | Institutions non financières et cryptomonnaies |
6211 | Courtiers et négociants en valeurs mobilières |
7273 | Services de rencontres et d'escorte |
7297 | Salons de massage |
7800 | Loteries d'État |
7801 | Casinos en ligne agréés par un État |
7802 | Courses de chevaux et de chiens agréées |
7995 | Paris et jeux d'argent en ligne |
Pays où la carte ne peut pas être utilisée
Des sanctions géographiques s'appliquent à ces pays et territoires :
Afghanistan, Algérie, Biélorussie, Burundi, République centrafricaine, Comores, Congo (République démocratique du), Congo (République du), Corée du Nord, Cuba, Gambie, Iran, Irak, Kirghizistan, Liban, Libéria, Libye, Maldives, Myanmar, Nicaragua, Palestine, Russie, Serbie, Somalie, Soudan, Soudan du Sud, Suriname, Svalbard et Jan Mayen, Syrie, Tadjikistan, Togo, Tokelau, Turkménistan, Ukraine, Ouzbékistan, Venezuela, Wallis-et-Futuna, Yémen, Zimbabwe.
Types de transaction
Le champ type de GET /cards/:id/transactions prend l'une de ces valeurs. Le champ mode vaut CREDIT ou DEBIT.
| Type | Signification |
|---|---|
AUTHORIZATION | La carte est utilisée avec succès chez un commerçant. |
SETTLEMENT | Le règlement d'un achat est terminé. |
FUNDING | La carte est alimentée via /fund. |
WITHDRAWAL | Des fonds sont retirés via /withdraw. |
TERMINATION | La carte est fermée. |
DECLINE | Une tentative d'utilisation est refusée (solde insuffisant, catégorie ou pays non autorisé, etc.). |
REVERSAL | Un commerçant débite la carte puis rend aussitôt la somme. |
REFUND | Remboursement d'un montant débité par erreur. |
CROSS-BORDER | Achat chez un commerçant hors des États-Unis, ou dans une devise autre que l'USD. |
Webhooks de carte
La confirmation de création de carte arrive de façon asynchrone. Deux options, au choix :
card.created / card.failed automatiquement.
Sans URL enregistrée, interrogez simplement GET /dashboard/cards/:id jusqu'à ce que status quitte pending :
async function waitForCard(id) { for (let i = 0; i < 40; i++) { const res = await fetch(`https://api.fredapay.com/dashboard/cards/${id}`, { headers: { Authorization: `Bearer ${sessionToken}` }, }); const card = await res.json(); if (card.status !== "pending") return card; await new Promise(r => setTimeout(r, 3000)); } }
Comptez généralement quelques secondes entre la création et la confirmation.
Liens de paiement, sans une ligne de code
Pas de site ou pas de développeur ? Créez un lien depuis le tableau de bord et envoyez-le par WhatsApp, SMS, e-mail ou sur vos réseaux sociaux.
- Ouvrez « Liens de paiement »dans votre tableau de bord.
- Indiquez un montant, ou laissez videSans montant, votre client saisit lui-même la somme : pratique pour les dons ou les factures variables.
- Partagez le lienVotre client choisit MonCash ou Natcash et paie. L'argent arrive sur votre solde Gateway.
Bouton de paiement pour n'importe quel site
Collez deux lignes dans votre site pour afficher un bouton « Payer avec Freda Pay ». Il fonctionne sur Wix, Squarespace, Webflow, Shopify, Blogger ou une simple page HTML.
Le bouton utilise un lien de paiement : il ne contient aucune clé secrète et peut donc être placé sans risque dans une page publique.
- Créez un lien de paiement et copiez son identifiant (
lnk_…). - Ajoutez un bloc « HTML personnalisé » ou « Embed » dans votre éditeur de site.
- Collez le code ci-contre en remplaçant
lnk_VOTRE_LIEN.
<div data-freda-link="lnk_VOTRE_LIEN"></div> <script src="https://pay.fredapay.com/freda-button.js" async></script>
Options : data-label="Faire un don" change le texte du bouton, data-theme="light" affiche une version claire.
Plugins et intégrations
Choisissez l'outil avec lequel votre site est construit.
WordPress / WooCommerce
MonCash et Natcash à la page de paiement. Commandes confirmées automatiquement. Instructions dans le fichier du plugin.
Dans votre tableau de bord
Wix
Bouton sans code, ou intégration Velo pour un montant par commande. Voir LISEZMOI.md dans le kit.
Dans votre tableau de bord
Shopify
Page de paiement réservée aux prestataires agréés Shopify. Utilisez le bouton sur vos pages produit en attendant.
Voir le boutonSDKs officiels
Client typé pour votre langage : mêmes endpoints que ci-dessus, sans écrire les appels HTTP vous-même.
Node.js
npm install @fredapay/issuing. Node 18+, zéro dépendance. Inclut la vérification de signature webhook.
PHP
composer require freda-pay/issuing. PHP 7.4+, extensions json et curl. Inclut la vérification de signature webhook.
Pas encore sur npm/Packagist : téléchargez le kit depuis la section Plugins et SDK de votre tableau de bord et lisez son README pour l'ajouter directement à votre projet, ou publiez-le vous-même sous votre propre nom de paquet.
Pagination
Tous les endpoints de liste (GET /v1/payments, /v1/payouts, /v1/cards, /dashboard/payment-links, etc.) acceptent deux paramètres de requête :
| Paramètre | Défaut | Description |
|---|---|---|
page | 1 | Numéro de page, à partir de 1. |
page_size | 20 | Résultats par page. Maximum 100. |
curl "https://api.fredapay.com/v1/cards?page=2&page_size=50" \ -H "Authorization: Bearer sk_test_VOTRE_CLE"
Avec le SDK Node.js : freda.payments.list({ page: 1, page_size: 20 }), freda.payouts.list() et freda.cards.list(). Chaque réponse inclut un objet pagination à côté des résultats, ainsi que le champ mode (sandbox ou live) pour les paiements, retraits et cartes :
{
"cards": [ /* … */ ],
"pagination": { "page": 2, "page_size": 50, "has_more": false }
}
Pas de total. Utilisez has_more pour savoir s'il faut charger la page suivante.
Limites de requêtes
Chaque clé API (sk_test_… / sk_live_…) est limitée à 100 requêtes par minute sur les endpoints /v1/…. Trois en-têtes accompagnent chaque réponse :
| En-tête | Signification |
|---|---|
X-RateLimit-Limit | Limite par fenêtre d'une minute (100). |
X-RateLimit-Remaining | Requêtes restantes dans la fenêtre en cours. |
Retry-After | Présent uniquement sur une réponse 429 : secondes à attendre. |
429 jusqu'à la fin de la fenêtre. Espacez vos appels ou regroupez-les ; il n'y a pas de palier supérieur disponible pour l'instant.
Les endpoints /dashboard/… (session du tableau de bord) ne sont pas soumis à cette limite spécifique, mais restent protégés contre les abus évidents (ex. tentatives de connexion).
Versions de l'API
Tous les endpoints publics vivent sous /v1/…. Politique :
- Sans changement de version : nouveau champ optionnel, nouvel endpoint, nouveau paramètre optionnel. Ignorez les champs inconnus.
- Avec nouvelle version (
/v2/…) : champ renommé/supprimé, type changé, paramètre rendu obligatoire./v1reste actif en parallèle. - Préavis de dépréciation : au moins 90 jours annoncés par email à l'adresse du compte avant qu'une version ne soit retirée.
/v1 est la version actuelle et n'a pas de date de retrait prévue.
Erreurs
En cas de problème, l'API répond avec un code HTTP d'erreur et un objet au même format :
{ "error": { "code": "INVALID_AMOUNT", "message": "Le montant minimum est de 20 HTG." } }
Utilisez code dans votre programme, et message pour l'afficher ou le journaliser.
| HTTP | Code | Cause et solution |
|---|---|---|
| 400 | MISSING_FIELDS | Un champ requis manque. Vérifiez le tableau des paramètres. |
| 400 | INVALID_AMOUNT | Montant absent, négatif ou inférieur à 20 HTG. Envoyez un nombre, pas du texte. |
| 400 | INVALID_METHOD | Méthode inconnue. Paiements (POST /v1/payments) : moncash, moncash_ussd, natcash, carte ou all. Retraits (POST /v1/payouts) : moncash ou natcash uniquement. |
| 400 | INVALID_RECIPIENT | Numéro mal formé. Format attendu : 509XXXXXXXX. |
| 400 | INSUFFICIENT_BALANCE | Solde Gateway trop faible pour ce retrait. |
| 400 | INVALID_JSON | Le corps n'est pas du JSON valide. Ajoutez Content-Type: application/json. |
| 401 | MISSING_API_KEY | En-tête Authorization absent ou mal écrit. Il doit commencer par Bearer sk_. |
| 401 | INVALID_API_KEY | Clé révoquée ou inexistante. Créez-en une nouvelle dans le tableau de bord. |
| 403 | LIVE_NOT_ENABLED | Clé live utilisée avant la vérification du compte. Terminez votre KYC. |
| 403 | ISSUING_LIVE_NOT_ENABLED | Clé live utilisée sur /v1/cards avant l'autorisation Issuing Live. Les paiements restent disponibles ; faites la demande depuis le tableau de bord. |
| 400 | BELOW_MINIMUM_BALANCE | Le Master Wallet de l'environnement actuel est insuffisant. En Live, seul un vrai dépôt crédite le solde. |
| 409 | HOLDER_MODE_MISMATCH | Le titulaire a été créé dans l'autre environnement (Sandbox/Live). Créez-le dans l'environnement de la clé utilisée. |
| 503 | CARD_PROVIDER_AUTH | Le service d'émission de cartes est momentanément indisponible côté plateforme (ce n'est pas une erreur de votre requête). Le montant éventuellement débité est remboursé aussitôt. Réessayez plus tard. |
| 503 | CARD_LIVE_NOT_CONFIGURED | L'émission de cartes Live n'est pas encore disponible sur la plateforme. Aucun montant n'est débité. |
| 404 | NOT_FOUND | Identifiant de paiement ou de retrait introuvable pour ce compte. |
| 502 | PROVIDER_ERROR | Le réseau MonCash ou Natcash n'a pas répondu. Réessayez dans quelques instants. |
Bonnes pratiques
- Une référence unique par tentative. Utilisez par exemple le numéro de commande suivi d'un compteur (
CMD-1001-1,CMD-1001-2). Réutiliser une référence renvoie l'opération existante. - Confirmez côté serveur. Ne livrez qu'après
status: "succeeded"obtenu par votre serveur. - Gardez la clé secrète dans une variable d'environnement, jamais dans le code source.
- Enregistrez l'id Freda Pay avec votre commande pour pouvoir la vérifier plus tard.
- Prévoyez les échecs. Affichez le
messaged'erreur et proposez de réessayer.
Une question ? Écrivez-nous à issuing@fredapay.com.
