Desarrolladores/Integración

Catálogo y pedidos

Esta guía recorre todo el proceso de compra: consultar tu saldo, buscar un producto, ver su precio, hacer el pedido y leer los códigos.

Ver también: Autenticación · Convenciones · Webhooks

#Cuenta y saldo

#Cuenta

GET/api/v1/account muestra los datos de tu empresa y si la API está activada.

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 es la moneda de tu billetera. Todo lo que pagas está en esa moneda.
  • api_access_enabled pasa a true cuando CardV aprueba tu empresa.

#Saldo

GET/api/v1/balance muestra cuánto dinero puedes gastar.

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 es lo que puedes gastar ahora mismo. Es balance menos reserved_amount.
  • Un pedido mayor que available_balance se rechaza y no se cobra nada.
  • low_balance_threshold es el monto a partir del cual recibes el correo de saldo bajo. Lo configuras en el Portal.
  • Para agregar fondos, usa el Portal.

#Productos (SKUs)

Un SKU es un producto concreto que puedes comprar, por ejemplo "Steam Wallet 10 USD". Su ID tiene este formato: S000456. Lo que cotizas y pides son SKUs.

GET/api/v1/skus muestra los SKUs que puedes comprar. Ejemplo:

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

Filtros (todos opcionales):

FiltroEjemploBusca por
searchsteamID de SKU, nombre o marca
brandSteamNombre de la marca (sin importar mayúsculas)
regionUSCódigo o nombre del país
verticalgift_cardLínea de producto
product_typepin_codeForma de entrega

Paginación: envía limit (100 por defecto, máximo 500) y offset. Sigue pidiendo con un offset mayor hasta que offset llegue a count.

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} devuelve un solo SKU con los mismos campos.

Los campos más útiles:

CampoQué significa
sku_idEl ID que usas para cotizar y pedir.
merchant_priceTu precio por unidad, en settlement_currency.
availabilityavailable o unavailable. Pide solo SKUs available.
denomination_typefixed o range. Ver montos fijos y variables.
face_currencyMoneda impresa en la tarjeta. Puede ser distinta a la de tu billetera.
min_quantity, max_quantityCuántas unidades puede tener una línea del pedido.
product_typepin_code (recibes un código) o direct_charge (recargamos una cuenta).
required_input_schemaDatos que debes enviar para las recargas directas.
brand_logo_url, image_urlImágenes alojadas por CardV, o "".
description, redemption_instructions, termsTextos que puedes mostrar a tus clientes.

Consejos:

  • Solo ves los SKUs activos y habilitados para tu cuenta. Los demás devuelven 404.
  • Sincroniza la lista de SKUs cada 5 a 15 minutos. Pide siempre una cotización justo antes de hacer el pedido.
  • filter_options muestra las marcas, regiones y líneas de producto por las que puedes filtrar.

#Montos fijos y variables

La mayoría de los SKUs tienen un valor nominal fijo, como 10 USD. Algunos tienen un rango: tu cliente elige el monto, por ejemplo de 5 a 500 USD.

TipoAl cotizarAl hacer el pedido
fixedEnvía quantityNo envíes amount
rangeEnvía quantity y amountEnvía amount

En un SKU de monto variable, amount debe estar entre min_face_value y max_face_value. Se expresa en face_currency.

#Recargas directas

Algunos productos recargan directamente la cuenta de tu cliente, por ejemplo una cuenta de juego. Para estos, CardV necesita los datos de la cuenta. El SKU indica cuáles son:

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

Envía los valores en el campo inputs de la línea del pedido, usando cada key:

JSON
"inputs": {"player_id": "123456789", "server": "EU"}
  • Todos los campos son obligatorios, salvo los que indican "required": false.
  • Si falta un valor obligatorio, el pedido se rechaza con un error en items.
  • Estos valores son datos personales de tu cliente. Protégelos (ver Seguridad).

#Cotización

Una cotización te dice el precio actual para una cantidad.

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"
}
  • Una cotización no reserva el precio. Los precios pueden cambiar en cualquier momento.
  • Para protegerte, envía merchant_price como expected_unit_price al hacer el pedido. Si el precio cambió, CardV rechaza el pedido y no te cobra nada.
  • Una cantidad o un monto fuera de rango devuelve HTTP 400 con un error en quantity o amount.

#Hacer un pedido

POST/api/v1/orders compra uno o más SKUs y los paga con tu billetera. Esta llamada tiene que ir firmada.

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"}
    }
  ]
}
CampoObligatorioQué significa
external_order_idSíTu número de pedido, de 1 a 120 caracteres. No se puede repetir.
itemsSíUna o más líneas del pedido.
items[].sku_idSíEl SKU que quieres comprar.
items[].quantityNoCuántas unidades. 1 por defecto.
items[].amountSKUs de monto variableEl valor nominal que quieres comprar.
items[].expected_unit_priceRecomendadoEl merchant_price de la cotización. Envíalo siempre.
items[].inputsRecargas directasDatos de la cuenta para las recargas directas.

Cuando CardV acepta el pedido, cobra el total completo de tu billetera en ese momento. Después, la entrega se procesa en segundo plano.

La respuesta es 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"
  }
}

Guarda order.order_id. Esta respuesta nunca incluye los códigos. Los lees después (Consultar un pedido).

#Pedidos rechazados

Un pedido rechazado devuelve HTTP 400 y no se cobra nada. La clave del error te dice por qué:

ClaveCausaQué hacer
itemsCambió el precio, el SKU no está disponible, el monto no es válido o falta un datoCotiza de nuevo, corrige y vuelve a enviar
balanceNo hay suficiente dinero en tu billeteraAgrega fondos en el Portal
riskSuperaste el tamaño máximo de pedido o tu límite diarioContacta a CardV
external_order_idTu número de pedido ya se usó en otro pedido distintoVer Reintentar sin riesgo

Ejemplo de un cambio de precio:

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

El nivel de tu cuenta define los límites por pedido, de gasto diario y de cantidad de pedidos al día. Los límites diarios se reinician a las 00:00 UTC. Pregunta a CardV cuáles son tus límites.

#Reintentar sin riesgo

Tu número de pedido (external_order_id) evita que compres dos veces. Si vuelves a enviar el mismo pedido con el mismo número de pedido, CardV no te cobra otra vez. Te devuelve el pedido que ya existe.

Si envíasRecibes
Un número de pedido nuevoHTTP 201. Un pedido nuevo. Se cobra de tu billetera.
El mismo número de pedido y el mismo pedidoHTTP 200 y "idempotent_replay": true. El pedido que ya existía. Sin cobro.
El mismo número de pedido pero otro pedidoHTTP 400 en external_order_id. No pasa nada.

"El mismo pedido" significa las mismas líneas, en el mismo orden, con el mismo SKU, cantidad, monto e inputs. Si envías expected_unit_price, debe coincidir con el precio del primer pedido.

CardV reconoce un pedido repetido antes de revisar el saldo y el precio. Por eso siempre devuelve el primer pedido, aunque el precio haya cambiado desde entonces.

#Flujo de reintento seguro

Si no recibes una respuesta clara, simplemente vuelve a enviar el mismo pedido.

Text
POST /orders con el número de pedido R
 ├─ 201 o 200 → guarda order_id. Listo.
 ├─ 400 items / balance / risk → no se creó ningún pedido.
 │      Corrige la causa y vuelve a enviar. Puedes reutilizar R.
 ├─ 400 external_order_id → R pertenece a otro pedido. Detente y revisa.
 ├─ 403 error de firma → firma de nuevo y envía el mismo cuerpo.
 ├─ 429 → espera lo que indica Retry-After, firma de nuevo y envía el mismo cuerpo.
 └─ tiempo agotado, 5xx o conexión perdida
        → vuelve a enviar el mismo cuerpo con el mismo R.
          Recibes 201 (el primer intento no llegó) o 200 (sí llegó).

Reglas:

  • Nunca crees un número de pedido nuevo porque se perdió una respuesta. Si la primera solicitud sí llegó, un número nuevo compraría todo dos veces.
  • Cada reenvío necesita una marca de tiempo, un nonce y una firma nuevos. El cuerpo no cambia.

#Consultar un pedido

GET/api/v1/orders/{order_id} devuelve el pedido, su estado y sus códigos.

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 es lo que se te cobró cuando se aceptó el pedido.
  • items[].unit_price es el precio fijado para este pedido.
  • items[].deliveries contiene los códigos completos. Trata esta respuesta como información secreta.
  • invoice_url y delivery_file_url son rutas a la factura y a los códigos en CSV. Las dos solo funcionan en el Portal. Con una clave de API devuelven HTTP 403.
  • También vienen id (un número antiguo, no lo uses), events (un historial, solo para mostrar) y el avance de entrega de cada línea. Puedes ignorarlos.
  • Un ID de pedido desconocido devuelve HTTP 404.

#Estado del pedido y códigos

#Estado

Text
accepted ──► processing ──► succeeded
                  │
                  ├──► partially_succeeded   (algunas líneas se entregaron y otras no)
                  │
                  └──► failed ──► refunded   (el dinero vuelve a tu billetera)
Estado¿Terminado?Qué hacer
acceptedNoEspera. Ya se cobró de la billetera, pero la entrega no ha empezado.
processingNoEspera. No vuelvas a hacer el pedido.
succeededSíLee los códigos y entrégaselos a tu cliente.
partially_succeededSíEntrega lo que llegó. El resto se reembolsa más adelante.
failedTodavía noEspera a que pase a refunded. Fallido todavía no significa reembolsado.
refundedSíEl dinero ya volvió a tu billetera.

Si no usas webhooks, consulta el pedido así: a los 5 segundos, luego a los 10 s, 30 s y 60 s, y después cada 5 minutos. Respeta el límite de solicitudes. La mayoría de los pedidos terminan en segundos. Algunos necesitan una revisión manual y pueden tardar horas.

#Códigos

Cada unidad entregada es un objeto dentro de 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
}
  • Muestra a tu cliente los display_fields: cada uno tiene un label y un value. Muestra también redeem_url, expiry_date e instructions cuando no estén vacíos.
  • kind es secret para códigos y PINs, y reference para datos como números de serie.
  • delivery_type indica qué recibiste: code, card_pin, link, code_link o qr. Pueden aparecer tipos nuevos, así que arma siempre lo que muestras a partir de display_fields.
  • En las entregas link, el propio redeem_url es el código. Mantenlo en secreto.
  • Nunca entregues a tu cliente una unidad cuyo status sea voided.
  • Los productos de recarga directa normalmente no tienen entregas. succeeded significa que la cuenta ya se recargó.
  • Algunos datos, como card_number y pin_code, también aparecen repetidos como campos separados. Pueden venir vacíos.
  • Los webhooks nunca incluyen códigos. Consulta el pedido cuando llegue un webhook.

¿Dudas sobre tu integración? Escribe a [email protected] con tu Merchant ID y el ID del pedido o de la solicitud.