Entwickler/Integration

Produkte und Bestellungen

Diese Anleitung führt Sie durch den gesamten Einkauf: Guthaben prüfen, Produkt suchen, Preis abfragen, bestellen und Codes abrufen.

Siehe auch: Authentifizierung · Grundregeln · Webhooks

#Konto und Guthaben

#Konto

GET/api/v1/account zeigt Ihre Firmendaten und ob die API für Sie freigeschaltet ist.

JSON
{
  "merchant_id": "M00000001",
  "name": "Acme Shop",
  "legal_name": "Acme Shop Ltd",
  "tier": "standard",
  "billing_email": "[email protected]",
  "status": "active",
  "kyb_status": "approved",
  "api_access_enabled": true,
  "default_currency": "USD"
}
  • default_currency ist die Währung Ihrer Wallet. Alle Preise, die Sie bezahlen, sind in dieser Währung.
  • api_access_enabled ist true, sobald CardV Ihr Unternehmen freigegeben hat.

#Guthaben

GET/api/v1/balance zeigt, wie viel Guthaben Sie ausgeben können.

JSON
{
  "currency": "USD",
  "balance": "1520.4000",
  "reserved_amount": "0.0000",
  "available_balance": "1520.4000",
  "low_balance_threshold": "200.0000",
  "low_balance_notified_at": null,
  "is_active": true
}
  • available_balance können Sie jetzt ausgeben. Es ist balance minus reserved_amount.
  • Eine Bestellung über available_balance hinaus wird abgelehnt. Es wird nichts abgebucht.
  • low_balance_threshold ist die Schwelle für die E-Mail bei niedrigem Guthaben. Sie legen sie im Portal fest.
  • Guthaben laden Sie im Portal auf.

#Produkte (SKUs)

Eine SKU ist ein einzelnes Produkt, das Sie kaufen können, zum Beispiel „Steam Wallet 10 USD“. Ihre ID sieht so aus: S000456. Preisabfragen und Bestellungen beziehen sich immer auf eine SKU.

GET/api/v1/skus listet die SKUs auf, die Sie kaufen können. Beispiel:

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

Filter (alle optional):

FilterBeispielFindet
searchsteamSKU-ID, Name oder Marke
brandSteamMarkenname (Groß-/Kleinschreibung egal)
regionUSLändercode oder Ländername
verticalgift_cardProduktbereich
product_typepin_codeArt der Lieferung

Seitenweise Abfrage: Senden Sie limit (Standard 100, höchstens 500) und offset. Fragen Sie mit steigendem offset weiter ab, bis offset den Wert count erreicht.

JSON
{
  "count": 7,
  "limit": 1,
  "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.0000",
      "face_currency": "USD",
      "merchant_price": "9.2500",
      "settlement_currency": "USD",
      "availability": "available",
      "min_quantity": 1,
      "max_quantity": 100,
      "required_input_schema": [],
      "...": "more fields"
    }
  ],
  "filter_options": {"brands": [], "regions": [], "verticals": []}
}

GET/api/v1/skus/{sku_id} liefert eine einzelne SKU mit denselben Feldern.

Die wichtigsten Felder:

FeldBedeutung
sku_idDie ID für Preisabfrage und Bestellung.
merchant_priceIhr Preis pro Stück, in settlement_currency.
availabilityavailable oder unavailable. Bestellen Sie nur SKUs mit available.
denomination_typefixed oder range. Siehe feste und freie Beträge.
face_currencyDie Währung auf der Karte. Kann von Ihrer Wallet-Währung abweichen.
min_quantity, max_quantityWie viele Stück eine Bestellposition enthalten darf.
product_typepin_code (Sie erhalten einen Code) oder direct_charge (wir laden ein Konto direkt auf).
required_input_schemaAngaben, die Sie für Direktaufladungen mitsenden müssen.
brand_logo_url, image_urlBilder, die CardV bereitstellt, oder "".
description, redemption_instructions, termsTexte, die Sie Ihren Kunden zeigen können.

Tipps:

  • Sie sehen nur SKUs, die aktiv und für Ihr Konto freigegeben sind. Andere SKUs liefern 404.
  • Aktualisieren Sie die SKU-Liste alle 5–15 Minuten. Fragen Sie den Preis immer direkt vor der Bestellung ab.
  • filter_options nennt die Marken, Regionen und Produktbereiche, nach denen Sie filtern können.

#Feste und freie Beträge

Die meisten SKUs haben einen festen Nennwert, zum Beispiel 10 USD. Einige SKUs haben einen Bereich: Ihr Kunde wählt den Betrag selbst, zum Beispiel 5 bis 500 USD.

TypBei der PreisabfrageBei der Bestellung
fixedquantity sendenamount weglassen
rangequantity und amount sendenamount senden

Bei einer SKU mit Bereich muss amount zwischen min_face_value und max_face_value liegen. Der Betrag ist in face_currency angegeben.

#Direktaufladungen

Manche Produkte laden das Konto Ihres Kunden direkt auf, zum Beispiel ein Spielkonto. Dafür braucht CardV die Kontodaten. Welche das sind, steht in der SKU:

JSON
"required_input_schema": [
  {"key": "player_id", "label": "Player ID", "required": true},
  {"key": "server", "label": "Server", "required": false}
]

Senden Sie die Werte in den inputs der Bestellposition, jeweils unter dem passenden key:

JSON
"inputs": {"player_id": "123456789", "server": "EU"}
  • Ein Feld ist Pflicht, außer es steht "required": false dabei.
  • Fehlt ein Pflichtwert, wird die Bestellung mit einem items-Fehler abgelehnt.
  • Diese Werte sind personenbezogene Daten Ihrer Kunden. Schützen Sie sie (siehe Sicherheit).

#Preisabfrage

Eine Preisabfrage nennt Ihnen den aktuellen Preis für eine bestimmte Menge.

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.2500",
  "quantity": 2,
  "total_price": "18.5000",
  "min_quantity": 1,
  "max_quantity": 100,
  "availability": "available"
}
  • Eine Preisabfrage reserviert den Preis nicht. Preise können sich jederzeit ändern.
  • Zu Ihrem Schutz senden Sie merchant_price bei der Bestellung als expected_unit_price mit. Hat sich der Preis geändert, lehnt CardV die Bestellung ab und bucht nichts ab.
  • Liegen Menge oder Betrag außerhalb des erlaubten Bereichs, erhalten Sie HTTP 400 mit einem quantity- oder amount-Fehler.

#Bestellen

POST/api/v1/orders kauft eine oder mehrere SKUs und bezahlt aus Ihrer Wallet. Diese Anfrage muss signiert sein.

JSON
{
  "external_order_id": "SHOP-20260929-10001",
  "items": [
    {"sku_id": "S000456", "quantity": 2, "expected_unit_price": "9.2500"},
    {"sku_id": "S000789", "amount": "25.00", "expected_unit_price": "23.7500"},
    {
      "sku_id": "S000900",
      "expected_unit_price": "4.9000",
      "inputs": {"player_id": "123456789"}
    }
  ]
}
FeldPflichtBedeutung
external_order_idJaIhre Bestellnummer, 1–120 Zeichen. Muss eindeutig sein.
itemsJaEine oder mehrere Bestellpositionen.
items[].sku_idJaDie SKU, die Sie kaufen.
items[].quantityNeinAnzahl. Standard ist 1.
items[].amountBei SKUs mit BereichDer gewünschte Nennwert.
items[].expected_unit_priceEmpfohlenDer abgefragte merchant_price. Immer mitsenden.
items[].inputsBei DirektaufladungenKontodaten für Direktaufladungen.

Nimmt CardV die Bestellung an, wird der gesamte Betrag sofort von Ihrer Wallet abgebucht. Die Lieferung läuft danach im Hintergrund.

Die Antwort kommt mit HTTP 201:

JSON
{
  "idempotent_replay": false,
  "order": {
    "order_id": "O-00001234",
    "external_order_id": "SHOP-20260929-10001",
    "status": "accepted",
    "total_amount": "46.2000",
    "...": "more fields"
  }
}

Speichern Sie order.order_id. Diese Antwort enthält nie Codes. Die rufen Sie später ab (Bestellung abrufen).

#Abgelehnte Bestellungen

Eine abgelehnte Bestellung liefert HTTP 400. Es wird nichts abgebucht. Der Fehlerschlüssel nennt den Grund:

SchlüsselUrsacheWas tun
itemsPreis geändert, SKU nicht verfügbar, ungültiger Betrag oder fehlende AngabePreis neu abfragen, korrigieren, erneut senden
balanceNicht genug Guthaben in Ihrer WalletGuthaben im Portal aufladen
riskBestellgröße oder Tageslimit überschrittenAn CardV wenden
external_order_idIhre Bestellnummer gehört schon zu einer anderen BestellungSiehe Sicher erneut senden

Beispiel für eine Preisänderung:

JSON
{
  "items": "SKU S000456 price changed from 9.2500 to 9.4100 USD; refresh quote and confirm again."
}

Ihre Kontostufe legt die Limits fest: pro Bestellung, für den Tagesumsatz und für die Zahl der Bestellungen pro Tag. Tageslimits beginnen um 00:00 UTC neu. Ihre Limits erfahren Sie bei CardV.

#Sicher erneut senden

Ihre Bestellnummer (external_order_id) schützt Sie vor doppelten Käufen. Senden Sie dieselbe Bestellung mit derselben Bestellnummer noch einmal, bucht CardV nicht erneut ab. Sie erhalten die Bestellung zurück, die es schon gibt.

Sie sendenSie erhalten
Eine neue BestellnummerHTTP 201. Eine neue Bestellung. Ihre Wallet wird belastet.
Dieselbe Bestellnummer und dieselbe BestellungHTTP 200 und "idempotent_replay": true. Die bestehende Bestellung. Keine Abbuchung.
Dieselbe Bestellnummer, aber eine andere BestellungHTTP 400 bei external_order_id. Es passiert nichts.

„Dieselbe Bestellung“ heißt: dieselben Positionen in derselben Reihenfolge, jeweils mit gleicher SKU, Menge, gleichem Betrag und gleichen Angaben. Wenn Sie expected_unit_price mitsenden, muss der Wert zum Preis der ersten Bestellung passen.

Eine wiederholte Bestellung wird vor der Guthaben- und Preisprüfung erkannt. Deshalb erhalten Sie immer die erste Bestellung zurück, auch wenn sich der Preis inzwischen geändert hat.

#Ablauf beim erneuten Senden

Wenn Sie keine eindeutige Antwort erhalten, senden Sie einfach dieselbe Bestellung noch einmal.

Text
POST /orders mit Bestellnummer R
 ├─ 201 oder 200 → order_id speichern. Fertig.
 ├─ 400 items / balance / risk → es wurde keine Bestellung angelegt.
 │      Ursache beheben und erneut senden. R darf wiederverwendet werden.
 ├─ 400 external_order_id → R gehört zu einer anderen Bestellung. Anhalten und prüfen.
 ├─ 403 Signaturfehler → neu signieren und denselben Body senden.
 ├─ 429 → Retry-After abwarten, neu signieren, denselben Body senden.
 └─ Timeout, 5xx oder Verbindung abgebrochen
        → denselben Body mit demselben R erneut senden.
          Sie erhalten 201 (der erste Versuch kam nicht an) oder 200 (er kam an).

Regeln:

  • Vergeben Sie nie eine neue Bestellnummer, nur weil eine Antwort verloren ging. Ist die erste Anfrage doch angekommen, kaufen Sie mit einer neuen Nummer alles doppelt.
  • Jedes erneute Senden braucht einen neuen Zeitstempel, eine neue Nonce und eine neue Signatur. Der Body bleibt gleich.

#Bestellung abrufen

GET/api/v1/orders/{order_id} liefert die Bestellung, ihren Status und ihre Codes.

JSON
{
  "order_id": "O-00001234",
  "external_order_id": "SHOP-20260929-10001",
  "status": "succeeded",
  "currency": "USD",
  "total_amount": "18.5000",
  "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.2500",
      "total_price": "18.5000",
      "delivery_count": 2,
      "deliveries": [{"...": "see Codes below"}]
    }
  ],
  "...": "more fields"
}
  • total_amount ist der Betrag, der bei Annahme der Bestellung abgebucht wurde.
  • items[].unit_price ist der Preis, der für diese Bestellung festgeschrieben ist.
  • items[].deliveries enthält die vollständigen Codes. Behandeln Sie die Antwort als geheim.
  • invoice_url und delivery_file_url sind Pfade zur Rechnung und zu den Codes als CSV. Beide funktionieren nur im Portal. Mit einem API-Key liefern sie HTTP 403.
  • Außerdem enthalten: id (eine alte Nummer, nicht verwenden), events (ein Verlauf nur zur Anzeige) und der Lieferfortschritt pro Position. Diese Felder können Sie ignorieren.
  • Eine unbekannte Bestell-ID liefert HTTP 404.

#Bestellstatus und Codes

#Status

Text
accepted ──► processing ──► succeeded
                  │
                  ├──► partially_succeeded   (einige Positionen geliefert, andere nicht)
                  │
                  └──► failed ──► refunded   (Geld zurück in Ihrer Wallet)
StatusAbgeschlossen?Was tun
acceptedNeinWarten. Die Wallet ist belastet, die Lieferung hat noch nicht begonnen.
processingNeinWarten. Bestellen Sie nicht noch einmal.
succeededJaCodes abrufen und an Ihren Kunden weitergeben.
partially_succeededJaDas Gelieferte weitergeben. Der Rest wird später erstattet.
failedNoch nichtAuf refunded warten. failed bedeutet noch keine Erstattung.
refundedJaDas Geld ist wieder in Ihrer Wallet.

Wenn Sie keine Webhooks nutzen, fragen Sie so ab: nach 5 Sekunden, dann nach 10 s, 30 s, 60 s, danach alle 5 Minuten. Bleiben Sie dabei im Anfragelimit. Die meisten Bestellungen sind in Sekunden fertig. Manche brauchen eine manuelle Prüfung und können Stunden dauern.

#Codes

Jede gelieferte Einheit ist ein Objekt in items[].deliveries:

JSON
{
  "status": "stored",
  "delivery_type": "card_pin",
  "display_fields": [
    {"key": "card_number", "label": "Card number", "value": "X1234", "kind": "secret"},
    {"key": "pin_code", "label": "PIN", "value": "9876", "kind": "secret"}
  ],
  "redeem_url": "",
  "expiry_date": "2027-09-29",
  "instructions": "Redeem at ...",
  "is_masked": false
}
  • Zeigen Sie Ihrem Kunden die display_fields: Jedes Feld hat ein label und einen value. Zeigen Sie auch redeem_url, expiry_date und instructions, wenn sie nicht leer sind.
  • kind ist secret bei Codes und PINs und reference bei Angaben wie Seriennummern.
  • delivery_type sagt, was Sie erhalten haben: code, card_pin, link, code_link oder qr. Es können neue Typen hinzukommen. Bauen Sie die Anzeige deshalb immer aus display_fields auf.
  • Bei link-Lieferungen ist die redeem_url selbst der Code. Halten Sie sie geheim.
  • Geben Sie Ihrem Kunden nie eine Einheit mit dem status voided.
  • Direktaufladungen haben meist keine Lieferungen. succeeded heißt dort: Das Konto wurde aufgeladen.
  • Kopien wie card_number und pin_code stehen auch als eigene Felder in der Antwort. Sie können leer sein.
  • Webhooks enthalten nie Codes. Rufen Sie die Bestellung ab, nachdem ein Webhook eingegangen ist.

Fragen zur Integration? Schreiben Sie an [email protected] und nennen Sie Ihre Merchant ID sowie die Bestell- oder Request-ID.