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",
"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_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.40",
"reserved_amount": "0.00",
"available_balance": "1520.40",
"low_balance_threshold": "200.00",
"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.
#Tipo de cambio
GET/api/v1/fx-rates?quote=EUR devuelve el tipo de cambio de USD a otra moneda.
Úselo si su tienda vende en una moneda distinta de USD. CardV siempre cobra su monedero en USD. El tipo de cambio solo le ayuda a fijar sus propios precios.
{
"base": "USD",
"quote": "EUR",
"rate": "0.9234",
"as_of": "2026-10-11T07:05:00Z"
}quotees un código de moneda ISO 4217, comoEUR,GBPoJPY.basees opcional. Solo se admiteUSD.rateindica cuántas unidades dequoteobtiene por 1 USD. Es una cadena decimal. No la lea como float.as_ofes cuándo CardV actualizó este tipo de cambio por última vez. Los tipos se actualizan cada hora.quote=USDdevuelve"rate": "1".- Guarde el tipo de cambio en su sistema. Basta con consultarlo unas pocas veces al día.
| Estado | Significado | Qué hacer |
|---|---|---|
| 400 | Falta quote o no es un código de 3 letras, o base no es USD | Corrija la solicitud |
| 404 | No exchange rate for this currency. | Fije usted los precios en esta moneda |
| 503 | Exchange rate temporarily unavailable. | Siga usando el último tipo guardado y vuelva a intentarlo más tarde |
Firme esta solicitud como cualquier otra. La ruta firmada incluye la cadena de consulta, por ejemplo /api/v1/fx-rates?quote=EUR.
#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.
#Modelos de producto
Cada SKU corresponde a uno de estos modelos. Lee denomination_type y product_type para distinguirlos.
| Modelo | Cómo reconocerlo | Precio | Pedido | Qué recibes |
|---|---|---|---|---|
| Monto fijo | denomination_type: "fixed" | merchant_price es el precio de una unidad. | Envía quantity. | Códigos en deliveries. |
| Monto variable | denomination_type: "range" | De merchant_price_min a merchant_price_max. El precio real depende del monto: solicita una cotización. | Envía quantity y amount. | Códigos en deliveries. |
| Recarga directa | product_type: "direct_charge" | Igual que el monto fijo. | Envía también inputs (ver recargas directas). | Normalmente no hay códigos. succeeded significa que la cuenta se recargó. |
| Recarga de celular | No está en este catálogo | Su propia cotización | Su propia API | Ver Recargas de celular. |
verticales la línea de producto, no el modelo. Una recarga directa puede tenervertical: "gift_card".- Cualquier SKU de monto fijo o variable puede entregarse como un enlace de canje de CardV en lugar de un código. En ese caso el SKU tiene
delivery_mode: "voucher". Recibes un enlace (delivery_type: "link", el enlace está enredeem_url). Tu cliente lo abre para obtener el código. Envíaselo a tu cliente igual que un código.
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. GLC = global, EU = Europa |
category | Travel | Categoría comercial, por ejemplo Travel, Digital Wallets & Payment, Game Credits |
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": 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} 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. En un SKU range es solo el precio de min_face_value, un precio inicial. No lo uses para otros montos: pide una cotización con amount. |
availability | available o unavailable. Pide solo SKUs available. |
denomination_type | fixed o range. Ver montos fijos y variables. |
denomination_value | Valor nominal de un SKU fixed. "0" en un SKU range. |
min_face_value, max_face_value | SKU range: el monto mínimo y máximo que puede elegir tu cliente, en face_currency. |
merchant_price_min, merchant_price_max | Precio del monto mínimo y del máximo. En SKU fixed son iguales a merchant_price. Úsalos para mostrar un rango de precios; antes de pedir, solicita siempre una cotización del monto exacto. merchant_price_max es "" cuando ahora mismo no se puede calcular su precio. |
price_label | merchant_price como texto para mostrar. En SKU range empieza por From, por ejemplo From 2.28 USD. |
face_value_label | Valor nominal como texto para mostrar, por ejemplo 10 USD o 50 - 1000 GBP. |
discount | Descuento sobre el valor nominal, por ejemplo 7.50% off face value. Solo en SKU fixed cuya moneda de la tarjeta es la de tu billetera; en otro caso, "". |
amount_step | Solo SKUs de monto variable. El monto debe ser múltiplo de este valor; por ejemplo, "1" significa solo números enteros. null significa que no hay paso. |
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). |
delivery_type | Lo que entrega el proveedor: code, code_pin, code_serial, url o direct_charge. Si delivery_mode es voucher, recibes un enlace en su lugar. |
delivery_mode | code (recibes el propio código) o voucher (recibes un enlace de canje de CardV). |
voucher_instructions | En SKU voucher: texto que explica a tu cliente cómo usar el enlace. En otro caso, "". |
vertical, category | Línea de producto y categoría de negocio, para filtrar y mostrar. |
region, region_name, region_label | Código de país o zona, su nombre y una etiqueta para mostrar. |
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, disclaimer | 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.
- Muestra a tus clientes los precios de tu lista sincronizada. No llames al endpoint de cotización en cada visita a la página ni por cada visitante.
- Los precios de los SKUs de monto variable siguen el costo del proveedor y pueden cambiar varias veces al día. Actualiza
merchant_price_minymerchant_price_maxal sincronizar tus SKUs. filter_optionsmuestra las marcas, regiones y líneas de producto por las que puedes filtrar.
#Sincronizar por producto
Si tu tienda muestra una página por producto con selector de monto, sincroniza productos en lugar de SKU.
GET/api/v1/products lista productos. Cada producto incluye todos los SKU que puedes comprar en él.
GET /api/v1/products?brand=Steam®ion=GB&limit=50&offset=0Admite los mismos filtros y la misma paginación que GET/api/v1/skus. count cuenta productos y sku_count cuenta sus 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} devuelve un producto con sus SKU, además de description, redemption_instructions, terms y disclaimer.
- Cada SKU de
skustiene los mismos campos y valores que enGET/api/v1/skus. Cotiza y pide con susku_idcomo siempre. - Usa
labelcomo texto de la opción, por ejemplo5 GBPoTinder Plus 1 Month. Algunos productos son planes, no montos. - Un producto tiene una sola moneda de valor nominal y un solo tipo de monto. Un SKU de monto variable siempre es un producto aparte.
- La lista no incluye los textos largos. Léelos una vez por producto en
GET/api/v1/products/{product_id}.
#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.
Precio de un SKU de monto variable. En las listas de SKU y de productos, merchant_price y price_label son solo el precio de min_face_value. merchant_price_min y merchant_price_max dan el precio del monto mínimo y del máximo. El precio de un monto intermedio no es un múltiplo simple de ninguno de ellos. Antes de fijar tu precio de venta o de pedir, solicita una cotización con el amount exacto y envía su merchant_price como expected_unit_price.
Algunos SKUs de monto variable también tienen un amount_step. Si no es null, amount debe ser múltiplo de él.
Por ejemplo, con "amount_step": "1" puedes enviar 25 pero no 25.50.
Una cotización o un pedido con un monto que no encaja en el paso devuelve HTTP 400 y no se cobra nada.
#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", "type": "text", "required": true},
{"key": "server", "label": "Server", "type": "text", "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.typees una pista para el campo de tu formulario, por ejemplotext. - 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.25",
"quantity": 2,
"total_price": "18.50",
"min_quantity": 1,
"max_quantity": 100,
"availability": "available"
}- Una cotización no reserva el precio. Los precios pueden cambiar en cualquier momento.
- Cotiza una sola vez, cuando tu cliente pase a pagar, con un tiempo de espera corto, por ejemplo 5 segundos. Si la cotización agota el tiempo, sigue mostrando tu precio sincronizado y vuelve a intentarlo; nunca muestres una página de error porque una cotización tardó.
- Para protegerte, envía
merchant_pricecomoexpected_unit_priceal hacer el pedido. Envía el precio de una cotización, no uno que hayas calculado tú: debe coincidir exactamente, al céntimo. Si el precio cambió, CardV rechaza el pedido y no te cobra nada. - Una cantidad o un monto fuera de rango, o un monto que no es múltiplo de
amount_step, devuelve HTTP 400 con un error enquantityoamount.
#Hacer un pedido
POST/api/v1/orders compra uno o más SKUs y los paga con tu billetera.
Como todas las llamadas, tiene que ir firmada.
{
"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"}
}
]
}| 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. Debe respetar min_face_value, max_face_value y amount_step. |
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.20",
"...": "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.25 to 9.41 USD; refresh quote and confirm again."
}Tu cuenta tiene límites por pedido, de gasto diario y de cantidad de pedidos al día.
GET/api/v1/account los muestra en purchase_limits, junto con lo que ya usaste hoy.
0 significa sin límite. Los límites diarios se reinician a las 00:00 UTC.
#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.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_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",
"card_number": "X1234",
"pin_code": "9876",
"redeem_url": "",
"expiry_date": "2027-09-29",
"instructions": "Redeem at ..."
}| Campo | Qué es |
|---|---|
card_number | El código principal: número de tarjeta, código de cupón o PIN. |
pin_code | El segundo código, como un PIN o código de seguridad. Vacío cuando no hay. |
redeem_url | Enlace de canje. En las entregas link, el propio enlace es el código. |
expiry_date | Último día en que se puede usar el código, en formato YYYY-MM-DD. "" significa que el proveedor no indicó fecha de caducidad. No significa que el código haya caducado. |
- Entrega a tu cliente todos los campos que no estén vacíos:
card_number,pin_code,redeem_url,expiry_dateeinstructions. delivery_typeindica qué recibiste:code,card_pin,link,code_linkoqr.- Mantén
redeem_urlen secreto, igual que un código. - Nunca borres, ocultes ni anules un código porque
expiry_dateesté vacío. Muestra una fecha de caducidad a tu cliente solo cuando no esté vacía. - 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ó. - 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.