# Convert API

> Convertissez entre cryptomonnaies directement depuis le solde de votre commerce — obtenez une cotation en direct et exécutez au prix du marché.

La Convert API vous permet d'échanger entre les devises détenues dans le solde de votre commerce au prix de marché actuel — le même moteur qui alimente l'onglet **Swap** du tableau de bord commerçant, désormais accessible depuis votre backend.

> **WARNING:** Les endpoints Convert sont signés avec votre **clé API habituelle** — la même que celle utilisée pour les requêtes [Payment API](/docs/payments), **pas** la clé Payout API. Exécuter une conversion débite et crédite immédiatement le solde de votre commerce ; traitez donc cette clé avec la même prudence que tout identifiant qui déplace de l'argent.

## Obtenir une cotation de conversion

Renvoie une cotation indicative pour une conversion au prix de marché actuel — le taux effectif et les montants résultants. Rien n'est débité ni réservé ; appelez-la autant de fois que nécessaire avant d'exécuter.

`POST /v1/convert/price`

### Paramètres de la requête

| Champ | Type | Requis | Description | Valeur |
|-------|------|--------|-------------|--------|
| `from_currency` | string | oui | Devise source | `BTC`, `ETH`, `USDT`, `USDC`, `TRX`, `BNB`, `GRAM`, `SOL`, `POL`, `LTC`, `DASH`, `DOGE`, `ZEC`, `XRP`, `XMR` |
| `to_currency` | string | oui | Devise cible. Doit différer de `from_currency` | `USDT`, `USDC`, `BTC`, `ETH`, `TRX`, `BNB`, `GRAM`, `SOL`, `POL`, `LTC`, `DASH`, `DOGE`, `ZEC`, `XRP`, `XMR` |
| `amount` | decimal | oui | Montant à convertir, supérieur à `0` |  |
| `amount_type` | string | oui | À quel côté `amount` fait référence | `from`, `to` |

> **INFO:** `amount_type=from` dépense exactement `amount` de `from_currency`. `amount_type=to` reçoit exactement `amount` de `to_currency`.

**🟢 200 OK** · `application/json`

```json
{
  "state": 0,
  "result": {
    "success": true,
    "from_currency": "BTC",
    "to_currency": "USDT",
    "amount_type": "from",
    "from_amount": "0.01000000",
    "to_amount": "947.86690000",
    "effective_rate": "94786.69000000",
    "from_amount_usd": "947.87",
    "to_amount_usd": "947.87"
  }
}
```

#### Champs de la réponse

| Champ | Type | Description |
|-------|------|--------------|
| `success` | boolean | Indique si la cotation a été calculée avec succès |
| `from_currency` | string | Devise source |
| `to_currency` | string | Devise cible |
| `amount_type` | string | Reprend le `amount_type` de la requête |
| `from_amount` | string | Montant qui serait débité en `from_currency` |
| `to_amount` | string | Montant qui serait crédité en `to_currency` |
| `effective_rate` | string | Taux appliqué à cette cotation — 1 unité de `from_currency` en `to_currency` (inclut déjà la tarification de la plateforme) |
| `from_amount_usd` | string \| null | Équivalent en USD de `from_amount` |
| `to_amount_usd` | string \| null | Équivalent en USD de `to_amount` |

- La cotation est **purement indicative** — le prix du marché peut évoluer entre la cotation et l'appel d'exécution.
- Cet appel ne débite ni ne réserve aucun solde.

> Use your project UUID and the endpoint-appropriate API key from the merchant dashboard.

#### Interactive request: `POST /v1/convert/price`
  - `from_currency` (enum, required): BTC,ETH,USDT,USDC,TRX,BNB,GRAM,SOL,POL,LTC,DASH,DOGE,ZEC,XRP,XMR
  - `to_currency` (enum, required): USDT,USDC,BTC,ETH,TRX,BNB,GRAM,SOL,POL,LTC,DASH,DOGE,ZEC,XRP,XMR
  - `amount` (decimal, required)
  - `amount_type` (enum, required): from,to

## Exécuter une conversion

Exécute une conversion au prix de marché actuel et met à jour le solde de votre commerce. Il n'y a pas d'étape distincte pour "valider une cotation" — appelez directement avec le montant que vous souhaitez convertir.

`POST /v1/convert`

> **INFO:** **Idempotence.** Répéter exactement la même requête (mêmes `from_currency`, `to_currency`, `amount`, `amount_type`) dans la minute suivant le premier appel renvoie la conversion existante au lieu d'en créer une seconde. Passé ce délai, une requête identique est traitée comme une nouvelle conversion — ne réessayez pas aveuglément après un timeout sans avoir d'abord vérifié le résultat précédent.

> **WARNING:** Cet endpoint est limité à **10 requêtes par minute** par appelant — plus strict que la limite générale de l'API — car chaque appel déplace un solde réel.

### Paramètres de la requête

| Champ | Type | Requis | Description | Valeur |
|-------|------|--------|-------------|--------|
| `from_currency` | string | oui | Devise source | `BTC`, `ETH`, `USDT`, `USDC`, `TRX`, `BNB`, `GRAM`, `SOL`, `POL`, `LTC`, `DASH`, `DOGE`, `ZEC`, `XRP`, `XMR` |
| `to_currency` | string | oui | Devise cible. Doit différer de `from_currency` | `USDT`, `USDC`, `BTC`, `ETH`, `TRX`, `BNB`, `GRAM`, `SOL`, `POL`, `LTC`, `DASH`, `DOGE`, `ZEC`, `XRP`, `XMR` |
| `amount` | decimal | oui | Montant à convertir, supérieur à `0` |  |
| `amount_type` | string | oui | À quel côté `amount` fait référence | `from`, `to` |

**🟢 200 OK** · `application/json`

```json
{
  "state": 0,
  "result": {
    "id": 12345,
    "type": "manual",
    "status": "completed",
    "from_currency": "BTC",
    "to_currency": "USDT",
    "from_amount": "0.01000000",
    "requested_from_amount": "0.01000000",
    "refund_amount": null,
    "to_amount": "947.86690000",
    "exchange_rate": "94786.69000000",
    "fee_amount": "0.00000000",
    "from_amount_usd": "947.87",
    "to_amount_usd": "947.87",
    "processed_at": "2026-01-20T15:30:24Z",
    "created_at": "2026-01-20T15:30:22Z"
  }
}
```

#### Champs de la réponse

| Champ | Type | Description |
|-------|------|--------------|
| `id` | int | ID de l'ordre de conversion attribué par le système |
| `type` | string | Toujours `manual` pour cette API |
| `status` | string | Statut actuel (voir « Statuts de conversion » ci-dessous) |
| `from_currency` | string | Devise source |
| `to_currency` | string | Devise cible |
| `from_amount` | string | Montant débité en `from_currency` |
| `requested_from_amount` | string \| null | Votre montant source initialement demandé lorsque `amount_type = from`. `null` lorsque `amount_type = to` |
| `refund_amount` | string \| null | Partie du montant prédébité qui vous a été remboursée après une exécution partielle. `null` si l'ordre a été entièrement exécuté |
| `to_amount` | string | Montant crédité en `to_currency` |
| `exchange_rate` | string | Taux réellement appliqué à cette conversion — 1 unité de `from_currency` en `to_currency` (inclut déjà la tarification de la plateforme) |
| `fee_amount` | string | Frais de plateforme prélevés sur cette conversion, exprimés en `from_currency` ou `to_currency` selon le sens de l'opération. Déjà reflétés dans `exchange_rate` — affichés à titre de transparence |
| `from_amount_usd` | string \| null | Équivalent en USD de `from_amount` |
| `to_amount_usd` | string \| null | Équivalent en USD de `to_amount` |
| `processed_at` | string (ISO 8601) \| null | Moment où la conversion a terminé de s'exécuter. `null` tant qu'elle est en cours |
| `created_at` | string (ISO 8601) | Moment où l'ordre de conversion a été créé |

#### Statuts de conversion

| Statut | Description |
|--------|-------------|
| `pending` | Créé, pas encore envoyé au marché |
| `processing` | Solde verrouillé et ordre placé sur le marché |
| `completed` | Entièrement exécuté — `to_amount` a été crédité sur votre solde |
| `failed` | Impossible à exécuter — tout montant prédébité a été remboursé automatiquement |
| `partially_completed` | Uniquement pour les paires de devises sans marché direct (routées via une devise intermédiaire) : la première étape a réussi mais la seconde a échoué. Vous êtes crédité de la devise intermédiaire au lieu de `to_currency` — reconvertissez depuis celle-ci pour atteindre votre objectif initial |

#### Interactive request: `POST /v1/convert`
  - `from_currency` (enum, required): BTC,ETH,USDT,USDC,TRX,BNB,GRAM,SOL,POL,LTC,DASH,DOGE,ZEC,XRP,XMR
  - `to_currency` (enum, required): USDT,USDC,BTC,ETH,TRX,BNB,GRAM,SOL,POL,LTC,DASH,DOGE,ZEC,XRP,XMR
  - `amount` (decimal, required)
  - `amount_type` (enum, required): from,to

## Erreurs

En cas d'échec, la réponse a `state: 1` et un `error_code` — commun à `/v1/convert/price` et `/v1/convert` :

**🔴 422 / 400** · `application/json`

```json
{
  "state": 1,
  "error_code": "amount_too_small",
  "errors": {
    "amount": "Amount is too small for this conversion. Please increase the amount and try again."
  }
}
```

| `error_code` | Statut HTTP | Description |
|--------------|-------------|--------------|
| `validation_failed` | 422 | Paramètres invalides ou manquants, ou rejet lié à une règle métier (p. ex. solde insuffisant) — voir le champ `errors` pour les détails |
| `amount_too_small` | 422 | `amount` est inférieur à la taille minimale négociable pour cette paire de devises |
| `convert_unavailable` | 400 | La conversion n'a pas pu être exécutée pour le moment (données de marché indisponibles ou aucune route entre les deux devises) — réessayez sous peu |
| `internal_error` | 400 | Erreur serveur interne inattendue lors du traitement de la requête |

## Conversion automatique des paiements entrants

La conversion automatique est un paramètre de projet pour les factures entrantes et les crédits de portefeuille statique. Il est configuré dans le tableau de bord du commerçant, et non pas en ajoutant des champs à `/v1/payment`. Chaque règle sélectionne une ou plusieurs devises sources et une devise cible.

Une fois la conversion terminée, les informations de paiement et les webhooks du commerçant peuvent inclure :

```json
{
  "payment_amount": "0.14800000",
  "merchant_amount": "0.146520000000000000",
  "payer_currency": "XMR",
  "convert": {
    "to_currency": "USDT",
    "commission": "0.09000000",
    "rate": "323.21000000",
    "amount": "47.262015740000000000"
  }
}
```

Les domaines des montants sont intentionnellement séparés :

- `payment_amount` — ce qui a été détecté sur la blockchain dans la devise de paiement source ;
- `merchant_amount` — le montant net source attribuable au commerçant avant conversion ;
- `convert.amount` — le montant crédité dans `convert.to_currency` ;
- `convert.rate` et `convert.commission` — le résultat de conversion exécuté, pas un prix que vous devriez recalculer localement.

> **WARNING:** L'absence de `convert` est significative : la conversion peut ne pas avoir été terminée, peut ne pas être configurée pour cette source, ou peut être revenue au crédit en devise source. Ne jamais inventer un montant cible à partir de `/exchange-rates` ou d'un prix de marché public.

### Échec de la conversion automatique et retour en

La conversion est en aval de la réception du paiement blockchain. La disponibilité sur le marché, les tailles minimales de commande, les limites de précision, les délais d'échange et la liquidité exécutable insuffisante peuvent retarder ou empêcher la conversion.

- Les dépôts inférieurs au minimum global/projet contournent le pipeline de conversion et créditent la devise source.
- Les échecs transitoires peuvent être réessayés de manière asynchrone.
- Les dépôts importants ou non négociables peuvent revenir à un crédit en devise source après épuisement de la politique de réessai.
- Un paiement peut donc être valide même lorsque la conversion en devise cible souhaitée n'a pas eu lieu.

Votre intégration doit d'abord enregistrer le paiement vérifié, puis rapprocher la devise réellement créditée à partir des informations de paiement, du bloc optionnel `convert` et des soldes marchands. Ne bloquez pas la confirmation du webhook de paiement en attendant vos propres systèmes d'analyse ou de notification.

### Tests d'acceptation de conversion automatique

Testez au moins : conversion directe réussie, conversion via un pont/multi-saut, poussière sous le minimum, nouvelle tentative transitoire, retour à la monnaie source, sous-paiement, trop-payé, webhook en double, `convert` manquant, et réconciliation après un timeout ambigu.

## Cas limites de conversion manuelle

- `/v1/convert/price` est un aperçu indicatif ; le mouvement du marché peut changer le résultat de l'exécution.
- `amount_type: from` corrige la demande côté source, tandis que `amount_type: to` demande un montant côté cible. Ne changez pas le sens lors de la présentation de l'interface de confirmation.
- Une paire sans marché direct peut être acheminée via une devise intermédiaire. Si une seule étape se termine, `partially_completed` rapporte le crédit intermédiaire.
- Si un appel d'exécution expire, réconciliez avant de réessayer. Un ordre au marché peut s'exécuter même lorsque sa réponse HTTP est perdue.
- Traitez `failed` comme un état à réconcilier, et non comme une permission d'appliquer une écriture locale de solde compensatoire ; la plateforme possède la comptabilité des débits/remboursements.