Développeurs/Intégrer
Catalogue et commandes
Ce guide décrit tout le parcours d'achat : consulter votre solde, trouver un produit, obtenir son prix, passer commande et récupérer les codes.
Voir aussi : Authentification · Conventions · Webhooks
#Compte et solde
#Compte
GET/api/v1/account affiche les informations de votre entreprise et indique si l'accès API est activé.
{
"merchant_id": "M00000001",
"name": "Acme Shop",
"legal_name": "Acme Shop Ltd",
"billing_email": "[email protected]",
"status": "active",
"kyb_status": "approved",
"api_access_enabled": true,
"default_currency": "USD",
"purchase_limits": {
"currency": "USD",
"verified": true,
"max_order_amount": "1000.00",
"daily_order_amount_limit": "10000.00",
"daily_order_count_limit": 1000,
"used_today_amount": "120.50",
"used_today_count": 3,
"day_resets_at": "2026-10-01T00:00:00+00:00"
}
}default_currencyest la devise de votre portefeuille. Tous les prix que vous payez sont dans cette devise.api_access_enabledpasse àtruedès que CardV a validé votre entreprise.
#Solde
GET/api/v1/balance indique combien vous pouvez dépenser.
{
"currency": "USD",
"balance": "1520.40",
"reserved_amount": "0.00",
"available_balance": "1520.40",
"low_balance_threshold": "200.00",
"low_balance_notified_at": null,
"is_active": true
}available_balanceest le montant que vous pouvez dépenser tout de suite :balancemoinsreserved_amount.- Une commande supérieure à
available_balanceest refusée, et rien n'est débité. low_balance_thresholdest le seuil qui déclenche l'e-mail de solde bas. Vous le réglez dans le Portail.- Pour ajouter des fonds, passez par le Portail.
#Taux de change
GET/api/v1/fx-rates?quote=EUR donne le taux de change de l'USD vers une autre devise.
Utilisez-le si votre boutique vend dans une devise autre que l'USD. CardV débite toujours votre portefeuille en USD. Ce taux sert uniquement à fixer vos propres prix.
{
"base": "USD",
"quote": "EUR",
"rate": "0.9234",
"as_of": "2026-10-11T07:05:00Z"
}quoteest un code de devise ISO 4217, par exempleEUR,GBPouJPY.baseest facultatif. SeulUSDest pris en charge.rateindique combien d'unités dequotevous obtenez pour 1 USD. C'est une chaîne décimale. Ne la lisez pas comme un float.as_ofindique quand CardV a mis à jour ce taux pour la dernière fois. Les taux sont mis à jour toutes les heures.quote=USDrenvoie"rate": "1".- Enregistrez le taux de votre côté. Quelques appels par jour suffisent.
| Statut | Signification | Que faire |
|---|---|---|
| 400 | quote est absent ou n'est pas un code à 3 lettres, ou base n'est pas USD | Corriger la requête |
| 404 | No exchange rate for this currency. | Fixez vous-même les prix dans cette devise |
| 503 | Exchange rate temporarily unavailable. | Continuez avec le dernier taux enregistré et réessayez plus tard |
Signez cette requête comme les autres. Le chemin signé inclut la chaîne de requête, par exemple /api/v1/fx-rates?quote=EUR.
#Produits (SKU)
Un SKU est un produit que vous pouvez acheter, par exemple « Steam Wallet 10 USD ».
Son identifiant ressemble à S000456. C'est sur les SKU que vous demandez un prix et passez commande.
#Modèles de produit
Chaque SKU relève de l'un de ces modèles. Lisez denomination_type et product_type pour les distinguer.
| Modèle | Comment le reconnaître | Prix | Commande | Ce que vous recevez |
|---|---|---|---|---|
| Montant fixe | denomination_type: "fixed" | merchant_price est le prix d'une unité. | Envoyez quantity. | Des codes dans deliveries. |
| Montant libre | denomination_type: "range" | De merchant_price_min à merchant_price_max. Le prix réel dépend du montant : demandez un devis. | Envoyez quantity et amount. | Des codes dans deliveries. |
| Recharge directe | product_type: "direct_charge" | Comme pour un montant fixe. | Envoyez aussi inputs (voir recharges directes). | En général, aucun code. succeeded signifie que le compte a été rechargé. |
| Recharge mobile | Absente de ce catalogue | Son propre devis | Sa propre API | Voir Recharge mobile. |
verticalest la gamme de produits, pas le modèle. Une recharge directe peut avoirvertical: "gift_card".- Tout SKU à montant fixe ou libre peut être livré sous forme de lien d'utilisation CardV au lieu d'un code. Le SKU a alors
delivery_mode: "voucher". Vous recevez un lien (delivery_type: "link", le lien est dansredeem_url). Votre client l'ouvre pour obtenir le code. Transmettez-le à votre client comme un code.
GET/api/v1/skus liste les SKU que vous pouvez acheter. Exemple :
GET /api/v1/skus?search=steam®ion=US&limit=50&offset=0Filtres (tous facultatifs) :
| Filtre | Exemple | Recherche dans |
|---|---|---|
search | steam | Identifiant de SKU, nom ou marque |
brand | Steam | Nom de la marque (majuscules ou minuscules) |
region | US | Code ou nom du pays. GLC = monde, EU = Europe |
category | Travel | Catégorie commerciale, par exemple Travel, Digital Wallets & Payment, Game Credits |
vertical | gift_card | Gamme de produits |
product_type | pin_code | Mode de livraison |
Pagination : envoyez limit (100 par défaut, 500 au maximum) et offset.
Augmentez offset à chaque appel jusqu'à ce qu'il atteigne count.
{
"count": 7,
"limit": 2,
"results": [
{
"sku_id": "S000456",
"product_id": "P000123",
"name": "Steam Wallet 10 USD",
"product_name": "Steam Wallet US",
"brand": "Steam",
"region": "US",
"vertical": "gift_card",
"product_type": "pin_code",
"denomination_type": "fixed",
"denomination_value": "10",
"face_currency": "USD",
"merchant_price": "9.25",
"merchant_price_min": "9.25",
"merchant_price_max": "9.25",
"settlement_currency": "USD",
"availability": "available",
"min_quantity": 1,
"max_quantity": 100,
"required_input_schema": [],
"...": "more fields"
},
{
"sku_id": "S006126",
"product_id": "P002210",
"name": "Apple ES",
"product_name": "Apple ES",
"brand": "Apple",
"region": "ES",
"vertical": "gift_card",
"product_type": "pin_code",
"denomination_type": "range",
"denomination_value": "0",
"min_face_value": "2",
"max_face_value": "250",
"amount_step": null,
"face_currency": "EUR",
"merchant_price": "2.28",
"merchant_price_min": "2.28",
"merchant_price_max": "284.15",
"price_label": "From 2.28 USD",
"settlement_currency": "USD",
"availability": "available",
"...": "more fields"
}
],
"filter_options": {"brands": [], "regions": [], "categories": [], "verticals": []}
}GET/api/v1/skus/{sku_id} renvoie un seul SKU, avec les mêmes champs.
Les champs les plus utiles :
| Champ | Signification |
|---|---|
sku_id | L'identifiant à utiliser pour obtenir le prix et commander. |
merchant_price | Votre prix unitaire, en settlement_currency. Pour un SKU range, c'est uniquement le prix de min_face_value, un prix de départ. Ne l'utilisez pas pour d'autres montants : demandez un devis avec amount. |
availability | available ou unavailable. Ne commandez que des SKU available. |
denomination_type | fixed ou range. Voir montants fixes et montants libres. |
denomination_value | Valeur faciale d'un SKU fixed. "0" pour un SKU range. |
min_face_value, max_face_value | SKU range : montant minimum et maximum que votre client peut choisir, en face_currency. |
merchant_price_min, merchant_price_max | Prix du montant minimum et du montant maximum. Égaux à merchant_price pour les SKU fixed. Utilisez-les pour afficher une fourchette de prix ; demandez toujours un devis pour le montant exact avant de commander. merchant_price_max vaut "" quand il ne peut pas être calculé pour le moment. |
price_label | merchant_price sous forme de texte affichable. Pour un SKU range, il commence par From, par exemple From 2.28 USD. |
face_value_label | Valeur faciale sous forme de texte affichable, par exemple 10 USD ou 50 - 1000 GBP. |
discount | Remise sur la valeur faciale, par exemple 7.50% off face value. Uniquement pour les SKU fixed dont la devise de la carte est celle de votre portefeuille ; sinon "". |
amount_step | SKU à montant libre uniquement. Le montant doit être un multiple de cette valeur ; par exemple, "1" signifie nombres entiers uniquement. null signifie aucun pas. |
face_currency | Devise inscrite sur la carte. Elle peut différer de celle de votre portefeuille. |
min_quantity, max_quantity | Nombre d'unités autorisé par ligne de commande. |
product_type | pin_code (vous recevez un code) ou direct_charge (nous rechargeons un compte). |
delivery_type | Ce que livre le fournisseur : code, code_pin, code_serial, url ou direct_charge. Quand delivery_mode vaut voucher, vous recevez un lien à la place. |
delivery_mode | code (vous recevez le code lui-même) ou voucher (vous recevez un lien d'utilisation CardV). |
voucher_instructions | Pour les SKU voucher : texte qui explique à votre client comment utiliser le lien. Sinon "". |
vertical, category | Gamme de produits et catégorie commerciale, pour les filtres et l'affichage. |
region, region_name, region_label | Code du pays ou de la zone, son nom et un libellé affichable. |
required_input_schema | Informations à fournir pour les recharges directes. |
brand_logo_url, image_url | Images hébergées par CardV, ou "". |
description, redemption_instructions, terms, disclaimer | Textes que vous pouvez montrer à vos clients. |
Conseils :
- Vous ne voyez que les SKU actifs et ouverts à votre compte. Les autres renvoient 404.
- Synchronisez la liste des SKU toutes les 5 à 15 minutes. Demandez toujours le prix juste avant de commander.
- Affichez à vos clients les prix de votre liste synchronisée. N'appelez pas l'endpoint de prix à chaque page vue ou pour chaque visiteur.
- Les prix des SKU à montant libre suivent le coût fournisseur et peuvent changer plusieurs fois par jour. Mettez à jour
merchant_price_minetmerchant_price_maxlors de votre synchronisation des SKU. filter_optionsliste les marques, les pays et les gammes de produits sur lesquels vous pouvez filtrer.
#Synchroniser par produit
Si votre boutique affiche une page par produit avec un choix de montant, synchronisez les produits plutôt que les SKU.
GET/api/v1/products liste les produits. Chaque produit contient tous les SKU que vous pouvez y acheter.
GET /api/v1/products?brand=Steam®ion=GB&limit=50&offset=0Les filtres et la pagination sont les mêmes que pour GET/api/v1/skus. count compte les produits et sku_count leurs SKU.
{
"count": 1,
"sku_count": 4,
"limit": 50,
"results": [
{
"product_id": "P000123",
"product_name": "Steam Wallet UK",
"brand": "Steam",
"category": "Gaming",
"region": "GB",
"product_type": "pin_code",
"image_url": "https://b2b.cardv.net/api/v1/catalog-assets/steam.png",
"required_input_schema": [],
"skus": [
{
"sku_id": "S000456",
"name": "Steam Wallet 5 GBP",
"label": "5 GBP",
"denomination_type": "fixed",
"denomination_value": "5",
"face_currency": "GBP",
"merchant_price": "7.61",
"settlement_currency": "USD",
"availability": "available",
"min_quantity": 1,
"max_quantity": 100,
"...": "more fields"
}
]
}
],
"filter_options": {"brands": [], "regions": [], "categories": [], "verticals": []}
}GET/api/v1/products/{product_id} renvoie un produit avec ses SKU, ainsi que description, redemption_instructions, terms et disclaimer.
- Chaque SKU de
skusa les mêmes champs et les mêmes valeurs que dansGET/api/v1/skus. Devis et commande se font comme d'habitude avec sonsku_id. - Utilisez
labelcomme texte d'option, par exemple5 GBPouTinder Plus 1 Month. Certains produits sont des forfaits, pas des montants. - Un produit a une seule devise de valeur faciale et un seul type de montant. Un SKU à montant libre est toujours un produit à part.
- La liste n'inclut pas les textes longs. Lisez-les une fois par produit avec
GET/api/v1/products/{product_id}.
#Montants fixes et montants libres
La plupart des SKU ont une valeur fixe, par exemple 10 USD. D'autres ont un montant libre dans une fourchette : votre client choisit le montant, par exemple entre 5 et 500 USD.
| Type | Pour obtenir le prix | Pour commander |
|---|---|---|
fixed | Envoyez quantity | N'envoyez pas amount |
range | Envoyez quantity et amount | Envoyez amount |
Pour un SKU à montant libre, amount doit être compris entre min_face_value et max_face_value.
Il est exprimé en face_currency.
Prix d'un SKU à montant libre. Dans les listes de SKU et de produits, merchant_price et price_label correspondent uniquement au prix de min_face_value. merchant_price_min et merchant_price_max donnent le prix du montant minimum et du montant maximum. Le prix d'un montant intermédiaire n'est un simple multiple d'aucun des deux. Avant de fixer votre prix de vente ou de commander, demandez un devis avec le amount exact et envoyez son merchant_price comme expected_unit_price.
Certains SKU à montant libre ont aussi un amount_step. S'il n'est pas null, amount doit en être un multiple.
Par exemple, avec "amount_step": "1", vous pouvez envoyer 25 mais pas 25.50.
Un devis ou une commande dont le montant ne respecte pas le pas renvoie HTTP 400 et rien n'est débité.
#Recharges directes
Certains produits rechargent directement le compte de votre client, par exemple un compte de jeu. Pour ces produits, CardV a besoin des informations du compte. Le SKU indique lesquelles :
"required_input_schema": [
{"key": "player_id", "label": "Player ID", "type": "text", "required": true},
{"key": "server", "label": "Server", "type": "text", "required": false}
]Envoyez les valeurs dans le champ inputs de la ligne de commande, avec chaque key :
"inputs": {"player_id": "123456789", "server": "EU"}- Un champ est obligatoire, sauf s'il porte
"required": false.typeest une indication pour le champ de votre formulaire, par exempletext. - S'il manque une valeur obligatoire, la commande est refusée avec une erreur
items. - Ces valeurs sont des données personnelles de votre client. Protégez-les (voir Sécurité).
#Prix actuel
Une demande de prix (quote) vous donne le prix actuel pour une quantité donnée.
GET /api/v1/skus/S000456/quote?quantity=2
GET /api/v1/skus/S000789/quote?quantity=1&amount=25.00{
"sku_id": "S000456",
"settlement_currency": "USD",
"merchant_price": "9.25",
"quantity": 2,
"total_price": "18.50",
"min_quantity": 1,
"max_quantity": 100,
"availability": "available"
}- Le prix obtenu n'est pas garanti. Il peut changer à tout moment.
- Demandez le prix une seule fois, au moment où votre client passe au paiement, avec un délai d'attente court, par exemple 5 secondes. Si la demande expire, continuez d'afficher votre prix synchronisé et réessayez ; n'affichez jamais une page d'erreur parce qu'une demande de prix a été lente.
- Pour vous protéger, renvoyez
merchant_pricedansexpected_unit_pricequand vous commandez. Envoyez le prix obtenu par une demande de prix, pas un prix que vous avez calculé vous-même : il doit correspondre exactement, au centime près. Si le prix a changé entre-temps, CardV refuse la commande et ne débite rien. - Une quantité ou un montant hors limites, ou un montant qui n'est pas un multiple de
amount_step, renvoie HTTP 400 avec une erreurquantityouamount.
#Passer une commande
POST/api/v1/orders achète un ou plusieurs SKU et les paie avec votre portefeuille.
Comme tout appel, il doit être signé.
{
"external_order_id": "SHOP-20260929-10001",
"items": [
{"sku_id": "S000456", "quantity": 2, "expected_unit_price": "9.25"},
{"sku_id": "S000789", "amount": "25.00", "expected_unit_price": "23.75"},
{
"sku_id": "S000900",
"expected_unit_price": "4.90",
"inputs": {"player_id": "123456789"}
}
]
}| Champ | Obligatoire | Signification |
|---|---|---|
external_order_id | Oui | Votre numéro de commande, de 1 à 120 caractères. Il doit être unique. |
items | Oui | Une ou plusieurs lignes de commande. |
items[].sku_id | Oui | Le SKU à acheter. |
items[].quantity | Non | La quantité. 1 par défaut. |
items[].amount | SKU à montant libre | La valeur faciale à acheter. Doit respecter min_face_value, max_face_value et amount_step. |
items[].expected_unit_price | Recommandé | Le merchant_price obtenu. Envoyez-le toujours. |
items[].inputs | Recharges directes | Informations du compte pour les recharges directes. |
Dès que CardV accepte la commande, il débite immédiatement le montant total de votre portefeuille. La livraison démarre ensuite en arrière-plan.
La réponse est un HTTP 201 :
{
"idempotent_replay": false,
"order": {
"order_id": "O-00001234",
"external_order_id": "SHOP-20260929-10001",
"status": "accepted",
"total_amount": "46.20",
"...": "more fields"
}
}Enregistrez order.order_id. Cette réponse ne contient jamais de codes : vous les récupérerez ensuite (voir Consulter une commande).
#Commandes refusées
Une commande refusée renvoie HTTP 400 et rien n'est débité. La clé d'erreur indique la raison :
| Clé | Cause | Que faire |
|---|---|---|
items | Prix modifié, SKU indisponible, montant incorrect ou information manquante | Redemandez le prix, corrigez, renvoyez |
balance | Solde insuffisant dans votre portefeuille | Ajoutez des fonds dans le Portail |
risk | Plafond par commande ou plafond quotidien dépassé | Contactez CardV |
external_order_id | Votre numéro de commande a déjà servi pour une autre commande | Voir Renvoyer sans risque |
Exemple de changement de prix :
{
"items": "SKU S000456 price changed from 9.25 to 9.41 USD; refresh quote and confirm again."
}Votre compte a des plafonds par commande, de dépense quotidienne et de nombre de commandes par jour.
GET/api/v1/account les indique dans purchase_limits, avec ce que vous avez déjà utilisé aujourd'hui.
0 signifie aucun plafond. Les plafonds quotidiens repartent à zéro à 00:00 UTC.
#Renvoyer sans risque
Votre numéro de commande (external_order_id) vous évite d'acheter deux fois.
Si vous renvoyez la même commande avec le même numéro, CardV ne vous débite pas une seconde fois.
Il vous renvoie la commande déjà enregistrée.
| Vous envoyez | Vous recevez |
|---|---|
| Un nouveau numéro de commande | HTTP 201. Une nouvelle commande. Votre portefeuille est débité. |
| Le même numéro et la même commande | HTTP 200 et "idempotent_replay": true. La commande existante. Aucun débit. |
| Le même numéro mais une commande différente | HTTP 400 sur external_order_id. Il ne se passe rien. |
« La même commande » signifie les mêmes lignes, dans le même ordre, avec les mêmes SKU, quantités, montants et inputs.
Si vous envoyez expected_unit_price, il doit être identique à celui de la première commande.
Une commande en double est détectée avant la vérification du solde et du prix. Elle renvoie donc toujours la première commande, même si le prix a changé depuis.
#Renvoyer une commande pas à pas
Si vous n'obtenez pas de réponse claire, renvoyez simplement la même commande.
POST /orders avec le numéro de commande R
├─ 201 ou 200 → enregistrez order_id. Terminé.
├─ 400 items / balance / risk → aucune commande n'a été créée.
│ Corrigez la cause et renvoyez. Vous pouvez réutiliser R.
├─ 400 external_order_id → R correspond à une autre commande. Arrêtez et vérifiez.
├─ 403 erreur de signature → signez à nouveau et envoyez le même corps.
├─ 429 → attendez Retry-After, signez à nouveau, envoyez le même corps.
└─ délai dépassé, 5xx ou connexion perdue
→ renvoyez le même corps avec le même R.
Vous recevez 201 (le premier envoi n'était pas arrivé) ou 200 (il était arrivé).Règles :
- Ne créez jamais un nouveau numéro de commande parce qu'une réponse s'est perdue. Si la première requête était bien arrivée, un nouveau numéro vous ferait tout acheter deux fois.
- Chaque renvoi demande un nouvel horodatage, un nouveau nonce et une nouvelle signature. Le corps, lui, ne change pas.
#Consulter une commande
GET/api/v1/orders/{order_id} renvoie la commande, son statut et ses codes.
{
"order_id": "O-00001234",
"external_order_id": "SHOP-20260929-10001",
"status": "succeeded",
"currency": "USD",
"total_amount": "18.50",
"created_at": "2026-09-29T08:15:30.123456Z",
"updated_at": "2026-09-29T08:15:41.004211Z",
"items": [
{
"sku_id": "S000456",
"product_name": "Steam Wallet US",
"quantity": 2,
"unit_price": "9.25",
"total_price": "18.50",
"delivery_count": 2,
"deliveries": [{"...": "see Codes below"}]
}
],
"...": "more fields"
}total_amountest le montant débité au moment où la commande a été acceptée.items[].unit_priceest le prix bloqué pour cette commande.items[].deliveriescontient les codes complets. Traitez cette réponse comme confidentielle.invoice_urletdelivery_file_urlsont les chemins de la facture et du fichier CSV des codes. Ils sont réservés au Portail : avec une clé API, ils renvoient HTTP 403.- La réponse contient aussi
id(un ancien numéro, à ne pas utiliser),events(un historique destiné à l'affichage) et l'avancement de la livraison ligne par ligne. Vous pouvez les ignorer. - Un numéro de commande inconnu renvoie HTTP 404.
#Statut de la commande et codes
#Statut
accepted ──► processing ──► succeeded
│
├──► partially_succeeded (une partie des lignes livrée, le reste non)
│
└──► failed ──► refunded (argent rendu à votre portefeuille)| Statut | Terminée ? | Que faire |
|---|---|---|
accepted | Non | Attendez. Le portefeuille est débité, la livraison n'a pas commencé. |
processing | Non | Attendez. Ne repassez pas la commande. |
succeeded | Oui | Récupérez les codes et remettez-les à votre client. |
partially_succeeded | Oui | Livrez ce qui est arrivé. Le reste sera remboursé plus tard. |
failed | Pas encore | Attendez refunded. Un échec n'est pas encore un remboursement. |
refunded | Oui | L'argent est revenu dans votre portefeuille. |
Si vous n'utilisez pas les webhooks, interrogez l'API à ce rythme : après 5 secondes, puis 10 s, 30 s, 60 s, puis toutes les 5 minutes. Restez sous la limite de requêtes. La plupart des commandes se terminent en quelques secondes. Certaines demandent une vérification manuelle et peuvent prendre plusieurs heures.
#Codes
Chaque unité livrée correspond à un objet dans items[].deliveries :
{
"status": "stored",
"delivery_type": "card_pin",
"card_number": "X1234",
"pin_code": "9876",
"redeem_url": "",
"expiry_date": "2027-09-29",
"instructions": "Redeem at ..."
}| Champ | Ce que c'est |
|---|---|
card_number | Le code principal : numéro de carte, code du bon ou PIN. |
pin_code | Le second code, par exemple un PIN ou un code de sécurité. Vide s'il n'y en a pas. |
redeem_url | Lien d'utilisation. Pour les livraisons link, le lien lui-même est le code. |
expiry_date | Dernier jour où le code peut être utilisé, au format YYYY-MM-DD. "" signifie que le fournisseur n'a indiqué aucune date d'expiration. Cela ne signifie pas que le code a expiré. |
- Remettez à votre client tous les champs non vides :
card_number,pin_code,redeem_url,expiry_dateetinstructions. delivery_typeindique ce que vous avez reçu :code,card_pin,link,code_linkouqr.- Gardez
redeem_urlsecret, comme un code. - Ne supprimez, ne masquez et n'annulez jamais un code parce que
expiry_dateest vide. N'affichez une date d'expiration à votre client que si elle n'est pas vide. - Ne remettez jamais à votre client une unité dont le
statusestvoided. - Les produits à recharge directe n'ont généralement pas de livraison.
succeededsignifie que le compte a été rechargé. - Les webhooks ne contiennent jamais de codes. Consultez la commande après avoir reçu un webhook.
Une question sur votre intégration ? Écrivez à [email protected] en indiquant votre Merchant ID et l’identifiant de commande ou de requête.