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

JSON
{
  "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_currency est la devise de votre portefeuille. Tous les prix que vous payez sont dans cette devise.
  • api_access_enabled passe à true dès que CardV a validé votre entreprise.

#Solde

GET/api/v1/balance indique combien vous pouvez dépenser.

JSON
{
  "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_balance est le montant que vous pouvez dépenser tout de suite : balance moins reserved_amount.
  • Une commande supérieure à available_balance est refusée, et rien n'est débité.
  • low_balance_threshold est 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.

JSON
{
  "base": "USD",
  "quote": "EUR",
  "rate": "0.9234",
  "as_of": "2026-10-11T07:05:00Z"
}
  • quote est un code de devise ISO 4217, par exemple EUR, GBP ou JPY.
  • base est facultatif. Seul USD est pris en charge.
  • rate indique combien d'unités de quote vous obtenez pour 1 USD. C'est une chaîne décimale. Ne la lisez pas comme un float.
  • as_of indique quand CardV a mis à jour ce taux pour la dernière fois. Les taux sont mis à jour toutes les heures.
  • quote=USD renvoie "rate": "1".
  • Enregistrez le taux de votre côté. Quelques appels par jour suffisent.
StatutSignificationQue faire
400quote est absent ou n'est pas un code à 3 lettres, ou base n'est pas USDCorriger la requête
404No exchange rate for this currency.Fixez vous-même les prix dans cette devise
503Exchange 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èleComment le reconnaîtrePrixCommandeCe que vous recevez
Montant fixedenomination_type: "fixed"merchant_price est le prix d'une unité.Envoyez quantity.Des codes dans deliveries.
Montant libredenomination_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 directeproduct_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 mobileAbsente de ce catalogueSon propre devisSa propre APIVoir Recharge mobile.
  • vertical est la gamme de produits, pas le modèle. Une recharge directe peut avoir vertical: "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 dans redeem_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 :

HTTP
GET /api/v1/skus?search=steam&region=US&limit=50&offset=0

Filtres (tous facultatifs) :

FiltreExempleRecherche dans
searchsteamIdentifiant de SKU, nom ou marque
brandSteamNom de la marque (majuscules ou minuscules)
regionUSCode ou nom du pays. GLC = monde, EU = Europe
categoryTravelCatégorie commerciale, par exemple Travel, Digital Wallets & Payment, Game Credits
verticalgift_cardGamme de produits
product_typepin_codeMode de livraison

Pagination : envoyez limit (100 par défaut, 500 au maximum) et offset. Augmentez offset à chaque appel jusqu'à ce qu'il atteigne count.

JSON
{
  "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 :

ChampSignification
sku_idL'identifiant à utiliser pour obtenir le prix et commander.
merchant_priceVotre 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.
availabilityavailable ou unavailable. Ne commandez que des SKU available.
denomination_typefixed ou range. Voir montants fixes et montants libres.
denomination_valueValeur faciale d'un SKU fixed. "0" pour un SKU range.
min_face_value, max_face_valueSKU range : montant minimum et maximum que votre client peut choisir, en face_currency.
merchant_price_min, merchant_price_maxPrix 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_labelmerchant_price sous forme de texte affichable. Pour un SKU range, il commence par From, par exemple From 2.28 USD.
face_value_labelValeur faciale sous forme de texte affichable, par exemple 10 USD ou 50 - 1000 GBP.
discountRemise 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_stepSKU à montant libre uniquement. Le montant doit être un multiple de cette valeur ; par exemple, "1" signifie nombres entiers uniquement. null signifie aucun pas.
face_currencyDevise inscrite sur la carte. Elle peut différer de celle de votre portefeuille.
min_quantity, max_quantityNombre d'unités autorisé par ligne de commande.
product_typepin_code (vous recevez un code) ou direct_charge (nous rechargeons un compte).
delivery_typeCe 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_modecode (vous recevez le code lui-même) ou voucher (vous recevez un lien d'utilisation CardV).
voucher_instructionsPour les SKU voucher : texte qui explique à votre client comment utiliser le lien. Sinon "".
vertical, categoryGamme de produits et catégorie commerciale, pour les filtres et l'affichage.
region, region_name, region_labelCode du pays ou de la zone, son nom et un libellé affichable.
required_input_schemaInformations à fournir pour les recharges directes.
brand_logo_url, image_urlImages hébergées par CardV, ou "".
description, redemption_instructions, terms, disclaimerTextes 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_min et merchant_price_max lors de votre synchronisation des SKU.
  • filter_options liste 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.

HTTP
GET /api/v1/products?brand=Steam&region=GB&limit=50&offset=0

Les filtres et la pagination sont les mêmes que pour GET/api/v1/skus. count compte les produits et sku_count leurs SKU.

JSON
{
  "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 skus a les mêmes champs et les mêmes valeurs que dans GET/api/v1/skus. Devis et commande se font comme d'habitude avec son sku_id.
  • Utilisez label comme texte d'option, par exemple 5 GBP ou Tinder 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.

TypePour obtenir le prixPour commander
fixedEnvoyez quantityN'envoyez pas amount
rangeEnvoyez quantity et amountEnvoyez 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 :

JSON
"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 :

JSON
"inputs": {"player_id": "123456789", "server": "EU"}
  • Un champ est obligatoire, sauf s'il porte "required": false. type est une indication pour le champ de votre formulaire, par exemple text.
  • 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.

HTTP
GET /api/v1/skus/S000456/quote?quantity=2
GET /api/v1/skus/S000789/quote?quantity=1&amount=25.00
JSON
{
  "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_price dans expected_unit_price quand 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 erreur quantity ou amount.

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

JSON
{
  "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"}
    }
  ]
}
ChampObligatoireSignification
external_order_idOuiVotre numéro de commande, de 1 à 120 caractères. Il doit être unique.
itemsOuiUne ou plusieurs lignes de commande.
items[].sku_idOuiLe SKU à acheter.
items[].quantityNonLa quantité. 1 par défaut.
items[].amountSKU à montant libreLa valeur faciale à acheter. Doit respecter min_face_value, max_face_value et amount_step.
items[].expected_unit_priceRecommandéLe merchant_price obtenu. Envoyez-le toujours.
items[].inputsRecharges directesInformations 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 :

JSON
{
  "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éCauseQue faire
itemsPrix modifié, SKU indisponible, montant incorrect ou information manquanteRedemandez le prix, corrigez, renvoyez
balanceSolde insuffisant dans votre portefeuilleAjoutez des fonds dans le Portail
riskPlafond par commande ou plafond quotidien dépasséContactez CardV
external_order_idVotre numéro de commande a déjà servi pour une autre commandeVoir Renvoyer sans risque

Exemple de changement de prix :

JSON
{
  "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 envoyezVous recevez
Un nouveau numéro de commandeHTTP 201. Une nouvelle commande. Votre portefeuille est débité.
Le même numéro et la même commandeHTTP 200 et "idempotent_replay": true. La commande existante. Aucun débit.
Le même numéro mais une commande différenteHTTP 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.

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

JSON
{
  "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_amount est le montant débité au moment où la commande a été acceptée.
  • items[].unit_price est le prix bloqué pour cette commande.
  • items[].deliveries contient les codes complets. Traitez cette réponse comme confidentielle.
  • invoice_url et delivery_file_url sont 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

Text
accepted ──► processing ──► succeeded
                  │
                  ├──► partially_succeeded   (une partie des lignes livrée, le reste non)
                  │
                  └──► failed ──► refunded   (argent rendu à votre portefeuille)
StatutTerminée ?Que faire
acceptedNonAttendez. Le portefeuille est débité, la livraison n'a pas commencé.
processingNonAttendez. Ne repassez pas la commande.
succeededOuiRécupérez les codes et remettez-les à votre client.
partially_succeededOuiLivrez ce qui est arrivé. Le reste sera remboursé plus tard.
failedPas encoreAttendez refunded. Un échec n'est pas encore un remboursement.
refundedOuiL'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 :

JSON
{
  "status": "stored",
  "delivery_type": "card_pin",
  "card_number": "X1234",
  "pin_code": "9876",
  "redeem_url": "",
  "expiry_date": "2027-09-29",
  "instructions": "Redeem at ..."
}
ChampCe que c'est
card_numberLe code principal : numéro de carte, code du bon ou PIN.
pin_codeLe second code, par exemple un PIN ou un code de sécurité. Vide s'il n'y en a pas.
redeem_urlLien d'utilisation. Pour les livraisons link, le lien lui-même est le code.
expiry_dateDernier 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_date et instructions.
  • delivery_type indique ce que vous avez reçu : code, card_pin, link, code_link ou qr.
  • Gardez redeem_url secret, comme un code.
  • Ne supprimez, ne masquez et n'annulez jamais un code parce que expiry_date est 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 status est voided.
  • Les produits à recharge directe n'ont généralement pas de livraison. succeeded signifie 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.