API Kryptis Pay
Recevez des paiements USDT et effectuez des retraits (crypto, Mobile Money) dans votre boutique, votre app ou votre service. API REST · authentification Bearer · webhooks signés HMAC · OpenAPI 3.
Pas encore de nom de domaine — remplacez l'URL ci-dessous par la vôtre en production.
Démarrage en 3 étapes
- Créez un compte et choisissez un username (Profil → Développeurs & API).
- Générez une clé API — elle commence par
ak_et n'est affichée qu'une seule fois. Par défaut la clé a les scopesread,write(case « lecture seule » disponible pour restreindre), une expiration optionnelle (date au choix, ou jamais par défaut), et peut être créée en mode bac à sable (préfixetest_— voir ci-dessous) pour intégrer sans toucher à de vrais fonds. Stockez-la comme un secret (variable d'environnement, jamais dans le code source). - Appelez l'API avec l'en-tête
Authorization: Bearer ak_....
Base URL, authentification & scopes
Toutes les requêtes se font sur /api/v1.
Chaque requête porte votre clé dans l'en-tête Authorization :
Authorization: Bearer ak_xxxxxxxxxxxxxxxxxxxx
- Une clé révoquée ou expirée (depuis « Mes clés API ») répond immédiatement
401. L'expiration se règle à la création de la clé — aucune par défaut. - Deux modes de clé :
ak_...(production, wallet réel) ettest_...(bac à sable, wallet fictif isolé — voir Bac à sable). last_used_atest mis à jour à chaque appel — surveillez vos clés inactives.- Limite de débit : 60 requêtes/minute par clé → au-delà
429. - Scopes :
read(consultation) etwrite(création de lien, retrait, config webhook). Une cléreadseule répond403sur un appelwrite. - Rotation : depuis le tableau de bord (pas via l'API), une clé peut être régénérée sans interruption — l'ancienne est révoquée, une nouvelle est émise avec les mêmes scopes.
- Idempotence : sur les opérations qui déplacent des fonds, ajoutez l'en-tête
Idempotency-Key: <uuid>— une requête rejouée avec la même clé ne débite/crédite qu'une fois (24h de mémorisation).
Qui peut payer — et retirer — sans compte complet ?
Ce n'est pas un processeur de paiement carte bancaire ouvert. Un paiement de lien est un transfert wallet-à-wallet fermé — le payeur doit disposer d'un compte Kryptis Pay, ou en obtenir un en quelques secondes :
- Utilisateur existant — se connecte et paie directement, sans plafond spécifique lié à son statut d'invité.
- Invité anonyme — vérifie son email par un code à usage unique (OTP), obtient automatiquement un wallet léger, le finance (Mobile Money ou crypto) puis paie. Aucune inscription classique requise. Plafond 100 USDT glissant sur 30 jours (dépôts + paiements de liens confondus) tant qu'il n'a pas de compte complet + KYC.
Attention à ne pas confondre ce plafond payeur avec celui qui s'applique à votre propre compte (celui qui détient la clé API) quand vous retirez des fonds — les comptes invités ne peuvent pas créer de clé API, mais un compte complet pas encore vérifié KYC le peut, et reste plafonné :
- Plafond 2000 USDT cumulés à vie sur les retraits (
POST /withdrawals/cryptoet/withdrawals/local), tant quekyc_statusde votre compte n'est pasapproved— partagé entre les deux canaux (crypto + Mobile Money confondus, pas 2000 chacun). Ce cumul ne se réinitialise jamais. - Au-delà :
403avec le solde restant disponible dans le message d'erreur. Seule solution pour continuer à retirer : compléter le KYC du compte (hors périmètre API, depuis le tableau de bord) — au-delà, le plafond ne s'applique plus. - Ce plafond est distinct du plafond de paiement invité (100 USDT/30j ci-dessus) : l'un limite les fonds entrants sans KYC sur un wallet invité, l'autre les fonds sortants sans KYC sur votre propre compte marchand.
Le parcours payeur se passe entièrement sur le site Kryptis Pay (page /pay/@slug) — vous
n'avez rien à implémenter côté payeur, seulement à partager l'URL du lien créé via l'API. Le retrait, lui,
est déclenché directement par votre clé API — voir Retraits (payouts).
Premier appel : consulter le solde
curl /api/v1/balance \
-H "Authorization: Bearer ak_votre_cle"
import requests
r = requests.get(
"/api/v1/balance",
headers={"Authorization": "Bearer ak_votre_cle"},
timeout=10,
)
r.raise_for_status()
print(r.json()) # {'username': 'marc', 'balance_usdt': '42.50', 'kyc_status': 'approved'}
const res = await fetch("/api/v1/balance", {
headers: { Authorization: "Bearer ak_votre_cle" },
});
if (!res.ok) throw new Error(`API ${res.status}`);
const data = await res.json();
console.log(data); // { username: 'marc', balance_usdt: '42.50', kyc_status: 'approved' }
$ch = curl_init("/api/v1/balance");
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ["Authorization: Bearer ak_votre_cle"],
]);
$data = json_decode(curl_exec($ch), true);
curl_close($ch);
print_r($data);
Référence des endpoints — Compte & liens de paiement
/balancereadSolde USDT du compte et statut KYC. Utile pour vérifier la disponibilité des fonds avant une opération.
200{
"username": "marc",
"balance_usdt": "42.50",
"kyc_status": "approved"
}
kyc_status : none · pending · approved · rejected.
/transactions?page=1&limit=20readHistorique des mouvements du wallet, du plus récent au plus ancien.
page | entier ≥ 1 — défaut 1 |
limit | 1 à 100 — défaut 20 (borné automatiquement) |
200{
"total": 128,
"items": [
{
"type": "credit",
"amount_usdt": "25.00",
"ref_type": "paylink",
"created_at": "2026-07-05T14:32:11"
}
]
}
type : credit ou debit. ref_type précise l'origine
(paylink, deposit, withdrawal, local_withdrawal, invest, manual_credit…) et peut être null.
/payment-linkswriteCrée un lien de paiement partageable : vos clients l'ouvrent et vous paient en USDT (voir « Qui peut payer un lien ? » ci-dessus).
Montant fixe (défini) ou libre (laissé null). Maximum 20 liens actifs par compte.
Nécessite un username défini.
label | optionnel — libellé affiché au payeur (ex. « Commande #42 ») |
amount_coins | optionnel — montant USDT fixe ; absent = le payeur choisit |
slug_suffix | optionnel — le lien devient @username-suffix (alphanumérique et tirets) |
curl -X POST /api/v1/payment-links \
-H "Authorization: Bearer ak_votre_cle" \
-H "Content-Type: application/json" \
-d '{"label":"Commande #42","amount_coins":25.0,"slug_suffix":"boutique"}'
201{
"id": "0f4c9a3e-...",
"slug": "marc-boutique",
"url": "/pay/@marc-boutique",
"label": "Commande #42",
"amount_coins": "25.00"
}
URL complète à partager : /pay/@marc-boutique.
Si le slug existe déjà, un suffixe aléatoire de 4 caractères est ajouté.
/payment-linksreadListe vos liens de paiement actifs avec leur compteur de visites.
200{
"items": [
{
"id": "0f4c9a3e-...",
"slug": "marc-boutique",
"url": "/pay/@marc-boutique",
"label": "Commande #42",
"amount_coins": "25.00",
"hits": 17
}
]
}
/payment-links/{id}readStatut détaillé d'un lien : combien de fois il a été payé, et le total encaissé — pratique pour un webhook manqué ou un rapprochement comptable.
200{
"id": "0f4c9a3e-...",
"slug": "marc-boutique",
"status": "active",
"is_active": true,
"payments_count": 3,
"paid_total": "76.50"
}
status : active · paid (lien non réutilisable, déjà payé) · expired · revoked.
/payment-links/{id}writeRévoque définitivement le lien (status=revoked, plus payable). Irréversible.
200{ "id": "0f4c9a3e-...", "status": "revoked" }
Référence des endpoints — Retraits (payouts)
Paiements sortants depuis votre compte, déclenchés par la clé API — équivalent programmatique du retrait fait depuis le tableau de bord. Aucune liste blanche d'adresses : vérifiez la destination côté intégrateur avant l'appel. Les gros montants peuvent être routés vers une validation à deux administrateurs (configuration côté opérateur, désactivée par défaut).
/withdrawals/cryptowriteEnvoie des fonds vers une adresse crypto externe. Mêmes plafonds réglementaires (montant max/transaction et /jour) que le retrait fait depuis le site, plus le plafond non-KYC de 2000 USDT à vie (voir ci-dessus) si votre compte n'est pas encore vérifié — partagé avec les retraits Mobile Money. Un échec certain avant diffusion recrédite automatiquement ; un échec incertain après tentative d'envoi ne recrédite jamais automatiquement (réconciliation manuelle admin) — évite tout double crédit.
crypto_symbol | requis — ex. USDT-TRC20 |
amount_coins | requis — montant USDT à retirer |
destination_address | requis — adresse externe (aucune liste blanche) |
curl -X POST /api/v1/withdrawals/crypto \
-H "Authorization: Bearer ak_votre_cle" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{"crypto_symbol":"USDT-TRC20","amount_coins":50,"destination_address":"T9y...abc"}'
201 — exécution immédiate{
"id": "b6f1...", "crypto_symbol": "USDT-TRC20",
"amount_crypto": "0.50000000", "coins_debited": "50.00000000",
"destination_address": "T9y...abc", "tx_hash": "MOCK_...",
"status": "completed"
}
201 — au-dessus du seuil de validation{ "status": "pending_approval", "approval_id": "3c1a..." }
Dans les deux cas le code HTTP est 201 — seul le champ status
distingue exécution immédiate de mise en attente d'approbation.
/withdrawals/localwriteCrée une demande de retrait Mobile Money — le débit du compte est immédiat, mais l'exécution (le virement réel) reste manuelle, effectuée par un administrateur sous 2h ouvrées. Cet endpoint accélère la création de la demande, pas son exécution. Plafond 2000 USDT à vie sans KYC, partagé avec les retraits crypto (même compteur — voir ci-dessus).
amount_coins | requis — montant USDT à convertir |
currency | requis — devise locale (ex. FCFA) |
payment_method | requis — wave · orange · mtn · moov |
recipient_info | requis — numéro de téléphone du bénéficiaire |
curl -X POST /api/v1/withdrawals/local \
-H "Authorization: Bearer ak_votre_cle" \
-H "Content-Type: application/json" \
-d '{"amount_coins":10,"currency":"FCFA","payment_method":"wave","recipient_info":"+225 07 00 00 00"}'
201{
"id": "9e2b...", "amount_coins": "10.00", "amount_local": "6500.00",
"currency": "FCFA", "payment_method": "wave", "status": "pending",
"message": "Demande de retrait créée — exécution sous 2h ouvrées (traitement manuel)."
}
Bac à sable (mode test)
Pour intégrer et tester sans risque de toucher à de vrais fonds, créez une clé au format test_...
(case « Clé de test » à la création, depuis « Mes clés API »). Elle opère exclusivement sur un
wallet fictif isolé, un par compte, crédité de 10 000 USDT fictifs à sa
première utilisation — jamais lié au wallet réel, aucun appel réseau réel (crypto ou Mobile Money) n'est
déclenché.
test_...GET /balanceetGET /transactionsreflètent le wallet fictif, pas le réel.POST /withdrawals/cryptoet/withdrawals/localdébitent le wallet fictif et renvoientstatus:"completed"instantanément — aucune diffusion crypto, aucune demande Mobile Money réelle, aucune mise en attente d'approbation à deux administrateurs.POST /sandbox/topuprecrédite librement le wallet fictif (aucun plafond).
Les liens de paiement (/payment-links*) et les webhooks (/webhooks/configure)
répondent 403 avec une clé test_... — ils nécessitent un payeur réel, que le
bac à sable ne simule pas.
/sandbox/topupwriteRecrédite le wallet fictif d'une clé test_.... Réservé aux clés de test —
403 avec une clé ak_....
amount_coins | requis — montant USDT fictif à créditer |
curl -X POST /api/v1/sandbox/topup \
-H "Authorization: Bearer test_votre_cle" \
-H "Content-Type: application/json" \
-d '{"amount_coins":5000}'
201{ "balance_usdt": "15000.00000000", "mode": "test" }
Référence des endpoints — Webhooks
/webhooks/configurewriteDéfinit l'URL qui recevra vos événements. Un secret de signature est généré au premier appel et retourné (conservé si déjà configuré) — stockez-le pour vérifier les signatures.
url | requis — URL HTTPS de votre serveur (max 500 caractères) |
200{
"webhook_url": "https://votre-serveur.com/hooks/kryptis",
"webhook_secret": "9f2ab6c1...64 caractères hex",
"note": "Signez/vérifiez avec HMAC-SHA256 (header X-Kryptis-Signature)."
}
Conventions & bonnes pratiques
- Réponses en JSON. Les montants sont des chaînes décimales (
"25.00") — parsez-les en décimal exact (Decimal,BigNumber), jamais en float. - Horodatages au format ISO 8601, UTC.
- En cas de
429ou5xx: réessayez avec un backoff exponentiel (1s, 2s, 4s…). - Sur les retraits, envoyez toujours un
Idempotency-Keyunique par tentative logique — un retry réseau ne doit jamais redéclencher un second envoi de fonds. - Ne partagez jamais une clé
ak_côté client (app mobile, navigateur) — uniquement serveur à serveur. - Créez une clé par intégration (boutique, script, partenaire) pour révoquer sans tout casser, et limitez son scope à
readsi elle n'a pas besoin d'écrire.
Codes d'erreur
| Code | Signification |
|---|---|
| 200 | Succès |
| 201 | Ressource créée (lien, retrait) |
| 400 | Requête invalide (username manquant, maximum de liens atteint, solde insuffisant Mobile Money…) |
| 401 | Clé absente, invalide, révoquée, expirée — ou compte inactif |
| 403 | Scope insuffisant (clé read sur un appel write) |
| 404 | Ressource introuvable |
| 422 | Corps JSON invalide / champ manquant, ou erreur métier sur un retrait (solde insuffisant, montant trop petit pour couvrir les frais réseau…) |
| 429 | Trop de requêtes (limite 60/min par clé) |
| 500 | Erreur serveur — réessayez avec backoff |
Format d'erreur uniforme : {"detail": "message explicatif"}
Webhooks
Une fois votre URL configurée (POST /webhooks/configure), les événements sont envoyés en
POST JSON, signés en HMAC-SHA256 dans l'en-tête
X-Kryptis-Signature (format sha256=<hex>).
Livraison avec retries en backoff exponentiel (jusqu'à 6 tentatives) si votre serveur
ne répond pas 2xx.
payment.completed | Un lien de paiement vient d'être payé. |
{
"event": "payment.completed",
"data": {
"link_id": "0f4c9a3e-...",
"slug": "marc-boutique",
"label": "Commande #42",
"amount_coins": "25.00",
"payer": "jean_dupont",
"payment_id": "7ad1..."
}
}
import hmac, hashlib
def verify(body: bytes, signature: str, secret: str) -> bool:
expected = "sha256=" + hmac.new(secret.encode(), body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, signature)
- Répondez
2xxrapidement (< 5 s) ; faites le traitement lourd en asynchrone. - Rendez votre traitement idempotent : le même événement peut être livré plusieurs fois.
- Rejetez toute requête dont la signature ne correspond pas.