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.

Swagger interactif Créer un compte

Pas encore de nom de domaine — remplacez l'URL ci-dessous par la vôtre en production.

Démarrage en 3 étapes

  1. Créez un compte et choisissez un username (Profil → Développeurs & API).
  2. 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 scopes read,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éfixe test_ — voir ci-dessous) pour intégrer sans toucher à de vrais fonds. Stockez-la comme un secret (variable d'environnement, jamais dans le code source).
  3. 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) et test_... (bac à sable, wallet fictif isolé — voir Bac à sable).
  • last_used_at est 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) et write (création de lien, retrait, config webhook). Une clé read seule répond 403 sur un appel write.
  • 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/crypto et /withdrawals/local), tant que kyc_status de votre compte n'est pas approved — partagé entre les deux canaux (crypto + Mobile Money confondus, pas 2000 chacun). Ce cumul ne se réinitialise jamais.
  • Au-delà : 403 avec 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

GET/balanceread

Solde USDT du compte et statut KYC. Utile pour vérifier la disponibilité des fonds avant une opération.

Réponse 200
{
  "username": "marc",
  "balance_usdt": "42.50",
  "kyc_status": "approved"
}

kyc_status : none · pending · approved · rejected.

GET/transactions?page=1&limit=20read

Historique des mouvements du wallet, du plus récent au plus ancien.

Paramètres
pageentier ≥ 1 — défaut 1
limit1 à 100 — défaut 20 (borné automatiquement)
Réponse 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.

POST/payment-linkswrite

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

Corps (JSON)
labeloptionnel — libellé affiché au payeur (ex. « Commande #42 »)
amount_coinsoptionnel — montant USDT fixe ; absent = le payeur choisit
slug_suffixoptionnel — le lien devient @username-suffix (alphanumérique et tirets)
Exemple
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"}'
Réponse 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é.

GET/payment-linksread

Liste vos liens de paiement actifs avec leur compteur de visites.

Réponse 200
{
  "items": [
    {
      "id": "0f4c9a3e-...",
      "slug": "marc-boutique",
      "url": "/pay/@marc-boutique",
      "label": "Commande #42",
      "amount_coins": "25.00",
      "hits": 17
    }
  ]
}
GET/payment-links/{id}read

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

Réponse 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.

DELETE/payment-links/{id}write

Révoque définitivement le lien (status=revoked, plus payable). Irréversible.

Réponse 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).

POST/withdrawals/cryptowrite

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

Corps (JSON)
crypto_symbolrequis — ex. USDT-TRC20
amount_coinsrequis — montant USDT à retirer
destination_addressrequis — adresse externe (aucune liste blanche)
Exemple
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"}'
Réponse 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"
}
Réponse 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.

POST/withdrawals/localwrite

Cré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).

Corps (JSON)
amount_coinsrequis — montant USDT à convertir
currencyrequis — devise locale (ex. FCFA)
payment_methodrequis — wave · orange · mtn · moov
recipient_inforequis — numéro de téléphone du bénéficiaire
Exemple
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"}'
Réponse 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é.

Ce que fait une clé test_...
  • GET /balance et GET /transactions reflètent le wallet fictif, pas le réel.
  • POST /withdrawals/crypto et /withdrawals/local débitent le wallet fictif et renvoient status:"completed" instantanément — aucune diffusion crypto, aucune demande Mobile Money réelle, aucune mise en attente d'approbation à deux administrateurs.
  • POST /sandbox/topup recrédite librement le wallet fictif (aucun plafond).
Indisponible en mode test

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.

POST/sandbox/topupwrite

Recrédite le wallet fictif d'une clé test_.... Réservé aux clés de test — 403 avec une clé ak_....

Corps (JSON)
amount_coinsrequis — montant USDT fictif à créditer
Exemple
curl -X POST /api/v1/sandbox/topup \
  -H "Authorization: Bearer test_votre_cle" \
  -H "Content-Type: application/json" \
  -d '{"amount_coins":5000}'
Réponse 201
{ "balance_usdt": "15000.00000000", "mode": "test" }

Référence des endpoints — Webhooks

POST/webhooks/configurewrite

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

Corps (JSON)
urlrequis — URL HTTPS de votre serveur (max 500 caractères)
Réponse 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 429 ou 5xx : réessayez avec un backoff exponentiel (1s, 2s, 4s…).
  • Sur les retraits, envoyez toujours un Idempotency-Key unique 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 à read si 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.

Événement disponible
payment.completedUn lien de paiement vient d'être payé.
Format d'événement
{
  "event": "payment.completed",
  "data": {
    "link_id": "0f4c9a3e-...",
    "slug": "marc-boutique",
    "label": "Commande #42",
    "amount_coins": "25.00",
    "payer": "jean_dupont",
    "payment_id": "7ad1..."
  }
}
Vérifier la signature (obligatoire)
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 2xx rapidement (< 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.

Cas d'usage

Boutique en ligne Livraison Freelance Billetterie Jeux Distribution / payouts