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.
{
"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_currencyes la moneda de tu billetera. Todo lo que pagas está en esa moneda.api_access_enabledpasa atruecuando CardV aprueba tu empresa.
#Saldo
GET/api/v1/balance muestra cuánto dinero puedes gastar.
{
"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_balancees lo que puedes gastar ahora mismo. Esbalancemenosreserved_amount.- Un pedido mayor que
available_balancese rechaza y no se cobra nada. low_balance_thresholdes 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:
GET /api/v1/skus?search=steam®ion=US&limit=50&offset=0Filtros (todos opcionales):
| Filtro | Ejemplo | Busca por |
|---|---|---|
search | steam | ID de SKU, nombre o marca |
brand | Steam | Nombre de la marca (sin importar mayúsculas) |
region | US | Código o nombre del país |
vertical | gift_card | Línea de producto |
product_type | pin_code | Forma 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.
{
"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:
| Campo | Qué significa |
|---|---|
sku_id | El ID que usas para cotizar y pedir. |
merchant_price | Tu precio por unidad, en settlement_currency. |
availability | available o unavailable. Pide solo SKUs available. |
denomination_type | fixed o range. Ver montos fijos y variables. |
face_currency | Moneda impresa en la tarjeta. Puede ser distinta a la de tu billetera. |
min_quantity, max_quantity | Cuántas unidades puede tener una línea del pedido. |
product_type | pin_code (recibes un código) o direct_charge (recargamos una cuenta). |
required_input_schema | Datos que debes enviar para las recargas directas. |
brand_logo_url, image_url | Imágenes alojadas por CardV, o "". |
description, redemption_instructions, terms | Textos 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_optionsmuestra 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.
| Tipo | Al cotizar | Al hacer el pedido |
|---|---|---|
fixed | Envía quantity | No envíes amount |
range | Envía quantity y amount | Enví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:
"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:
"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.
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.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_pricecomoexpected_unit_priceal 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
quantityoamount.
#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.
{
"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"}
}
]
}| Campo | Obligatorio | Qué significa |
|---|---|---|
external_order_id | Sí | Tu número de pedido, de 1 a 120 caracteres. No se puede repetir. |
items | Sí | Una o más líneas del pedido. |
items[].sku_id | Sí | El SKU que quieres comprar. |
items[].quantity | No | Cuántas unidades. 1 por defecto. |
items[].amount | SKUs de monto variable | El valor nominal que quieres comprar. |
items[].expected_unit_price | Recomendado | El merchant_price de la cotización. Envíalo siempre. |
items[].inputs | Recargas directas | Datos 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:
{
"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é:
| Clave | Causa | Qué hacer |
|---|---|---|
items | Cambió el precio, el SKU no está disponible, el monto no es válido o falta un dato | Cotiza de nuevo, corrige y vuelve a enviar |
balance | No hay suficiente dinero en tu billetera | Agrega fondos en el Portal |
risk | Superaste el tamaño máximo de pedido o tu límite diario | Contacta a CardV |
external_order_id | Tu número de pedido ya se usó en otro pedido distinto | Ver Reintentar sin riesgo |
Ejemplo de un cambio de precio:
{
"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ías | Recibes |
|---|---|
| Un número de pedido nuevo | HTTP 201. Un pedido nuevo. Se cobra de tu billetera. |
| El mismo número de pedido y el mismo pedido | HTTP 200 y "idempotent_replay": true. El pedido que ya existía. Sin cobro. |
| El mismo número de pedido pero otro pedido | HTTP 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.
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.
{
"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_amountes lo que se te cobró cuando se aceptó el pedido.items[].unit_pricees el precio fijado para este pedido.items[].deliveriescontiene los códigos completos. Trata esta respuesta como información secreta.invoice_urlydelivery_file_urlson 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
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 |
|---|---|---|
accepted | No | Espera. Ya se cobró de la billetera, pero la entrega no ha empezado. |
processing | No | Espera. No vuelvas a hacer el pedido. |
succeeded | Sí | Lee los códigos y entrégaselos a tu cliente. |
partially_succeeded | Sí | Entrega lo que llegó. El resto se reembolsa más adelante. |
failed | Todavía no | Espera a que pase a refunded. Fallido todavía no significa reembolsado. |
refunded | Sí | 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:
{
"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 unlabely unvalue. Muestra tambiénredeem_url,expiry_dateeinstructionscuando no estén vacíos. kindessecretpara códigos y PINs, yreferencepara datos como números de serie.delivery_typeindica qué recibiste:code,card_pin,link,code_linkoqr. Pueden aparecer tipos nuevos, así que arma siempre lo que muestras a partir dedisplay_fields.- En las entregas
link, el propioredeem_urles el código. Mantenlo en secreto. - Nunca entregues a tu cliente una unidad cuyo
statusseavoided. - Los productos de recarga directa normalmente no tienen entregas.
succeededsignifica que la cuenta ya se recargó. - Algunos datos, como
card_numberypin_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.