API de partners de BuyCheapKeys
API REST/JSON para importar nuestro catálogo y venderlo en tu propia web. Los pedidos se pagan con tu saldo prepago y se entregan automáticamente desde el proveedor más barato disponible.
Visión general
URL base: https://buycheapkeys.com/partner-api/v1. Peticiones y respuestas en JSON (UTF-8). Los precios están en EUR con dos decimales y no incluyen IVA. Las fechas son ISO 8601 en UTC.
- Catálogo: lista y sincroniza de forma incremental todos los productos que puedes vender, con tu precio de partner.
- Pedidos: hasta 10 líneas × 9 unidades por petición; el total se descuenta de tu saldo al momento.
- Entrega: claves, enlaces de Steam Gift, credenciales de cuenta o enlaces de activación, por API o webhook.
- Reembolsos: las unidades que no se pudieron entregar se abonan automáticamente. Las claves, Steam Gifts, cuentas y enlaces entregados son definitivos: no se pueden devolver ni reembolsar.
Autenticación y entornos
Crea las claves en Panel → Partner → Claves API. Cada clave se muestra una sola vez; solo guardamos su hash. Envíala en la cabecera Authorization (también se acepta X-Api-Key).
curl https://buycheapkeys.com/partner-api/v1/ping \
-H "Authorization: Bearer bck_test_xxxxxxxxxxxx_..."| Prefijo | Modo | Comportamiento |
|---|---|---|
| bck_test_ | Sandbox | Catálogo y precios reales, saldo de prueba separado (reiniciable), claves ficticias tipo TEST-AB12C-…, sin mover dinero. Los webhooks llegan con mode: test. |
| bck_live_ | Producción | Compras reales cargadas a tu saldo real. |
| Permiso | Permite |
|---|---|
| catalog:read | GET /products, /products/{id}, /meta, /ping |
| orders:write | POST /orders |
| orders:read | GET /orders, /orders/{id}, /orders/{id}/keys, POST /webhooks/test |
| balance:read | GET /balance |
Lista blanca de IPs opcional (IPv4/IPv6, CIDR) por cuenta. Las claves no caducan salvo que las revoques; para rotarlas crea una nueva y revoca la antigua.
Inicio rápido
1) Crea una clave bck_test_. 2) Lista productos. 3) Haz un pedido con ?wait=20 para recibirlo ya completado. 4) Descarga las claves.
curl "https://buycheapkeys.com/partner-api/v1/products?limit=100&page=1&inStock=true" \
-H "Authorization: Bearer $BCK_API_KEY"Catálogo y sincronización
GET /products devuelve hasta 100 productos ordenados por updatedAt (y luego id). updatedAt solo cambia cuando cambia algo relevante (precio, stock, nombre, región, textos, imágenes). Sincroniza con el cursor: cada respuesta trae nextCursor y hasMore; pasa nextCursor como ?cursor= hasta que hasMore sea false y guárdalo para la siguiente ejecución. Nunca salta ni repite productos, aunque muchos compartan marca de tiempo. page y updatedSince siguen disponibles para navegar. Filtros: inStock, platform, region, productType, deliveryType, search, ids (separados por comas, máx. 100).
curl "https://buycheapkeys.com/partner-api/v1/products?cursor=$SAVED_NEXT_CURSOR&includeRemoved=true&limit=100" \
-H "Authorization: Bearer $BCK_API_KEY"Recomendado: una sincronización completa (sin cursor) y después cada 5–15 minutos desde el nextCursor guardado, siempre con includeRemoved=true. Un producto con removed: true o inStock: false no debe venderse.
{
"id": "prod-17298",
"name": "Terraria (Steam Gift)",
"platform": "Steam",
"region": "Europe",
"productType": "game",
"deliveryType": "gift",
"price": 3.71,
"currency": "EUR",
"inStock": true,
"qty": 12,
"activation": { "excludedCountries": ["RU", "BY", "..."], "allowedCountryCount": 53 },
"coverImage": "https://buycheapkeys.com/api/media/catalog/category/.../cover.jpg",
"screenshots": ["https://buycheapkeys.com/api/media/..."],
"description": "Dig, Fight, Explore, Build...",
"activationDetails": "You will receive a gift link...",
"languages": ["English", "Spanish", "German"],
"genres": ["Action", "Adventure"],
"developer": "Re-Logic", "publisher": "Re-Logic", "releaseDate": "2011-05-16", "ageRating": "PEGI 12",
"systemRequirements": [ { "system": "Windows", "requirement": ["..."] } ],
"url": "https://buycheapkeys.com/en/product/terraria-steam-europe-238023",
"updatedAt": "2026-10-01T16:05:12.881Z",
"removed": false
}| Campo | Significado |
|---|---|
| nextCursor / hasMore | Campos de la respuesta para sincronizar: pasa nextCursor como ?cursor= mientras hasMore sea true; guarda el último para la siguiente ejecución. |
| price | Tu precio por unidad en EUR. Se vuelve a comprobar en vivo al pedir. |
| deliveryType | key = código para canjear · gift = enlace de Steam Gift · account = credenciales de cuenta · link = enlace de activación. Indícale a tu cliente qué compra. |
| region / activation | Región y la lista ISO exacta de países donde NO se puede activar (null = solo se conoce la región). |
| qty | Unidades aproximadas disponibles (máx. 99); null = desconocido. |
| coverImage / screenshots | Servidas desde nuestro CDN; guárdalas en caché en tu lado. |
| activationDetails | Instrucciones de canje (en inglés) para mostrar a tu cliente. |
Pedidos
POST /orders con externalId (tu referencia única) e items (productId, qty 1–9, maxUnitPrice). En cada línea comprobamos el precio en vivo: si supera maxUnitPrice se rechaza todo el pedido con PRICE_CHANGED y no se cobra nada. Si es menor, pagas el precio menor.
const res = await fetch("https://buycheapkeys.com/partner-api/v1/orders?wait=20", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.BCK_API_KEY}`,
"Content-Type": "application/json",
"Idempotency-Key": `shop-order-${order.id}`, // same value on every retry
},
body: JSON.stringify({
externalId: String(order.id),
items: [{ productId: "prod-17298", qty: 1, maxUnitPrice: 4.5 }],
}),
});
const data = await res.json();
if (!res.ok) throw new Error(`${data.error.code}: ${data.error.message}`);
if (data.status === "completed") {
const { keys } = await (await fetch(`https://buycheapkeys.com/partner-api/v1/orders/${data.id}/keys`, {
headers: { Authorization: `Bearer ${process.env.BCK_API_KEY}` },
})).json();
// deliver keys[].value to your customer
}- Envía la cabecera
Idempotency-Key(8–100 caracteres) y repítela al reintentar: recibes la respuesta original y nunca se cobra dos veces.externalIdtambién es único por cuenta. ?wait=N(0–25 s) espera a que termine antes de responder. Sin él, el pedido vuelve enprocessingy te avisamos por webhook o consultandoGET /orders/{id}.- El total se descuenta al aceptar el pedido. Las unidades que no se puedan entregar se abonan automáticamente (
refunded).
| Estado | Significado |
|---|---|
| processing | Pagado; se están comprando/entregando las claves (normalmente segundos; productos sin lanzar pueden tardar más). |
| completed | Todas las unidades entregadas. |
| partially_completed | Parte entregada; el resto se ha abonado a tu saldo. |
| failed | No se pudo entregar nada; reembolsado por completo. |
Entrega de claves
GET /orders/{id}/keys devuelve cada unidad entregada: value contiene el código, la URL del Steam Gift, las credenciales de la cuenta o el enlace de activación según deliveryType. contentType es text/plain o un tipo de imagen (entonces value va en base64).
Guarda las claves cifradas y muéstralas solo al comprador. La primera descarga de cada pedido queda registrada en nuestro lado como prueba de entrega.
Sin devoluciones
Cada unidad entregada (clave, enlace de Steam Gift, cuenta o enlace de activación) es definitiva: se compra al proveedor para ti en el momento de la entrega y no se puede devolver ni reembolsar, aunque no se haya canjeado. Solo se reembolsan, automáticamente, las unidades que no se pudieron entregar. Revisa la región, activation.excludedCountries y deliveryType antes de vender y muéstraselos a tu cliente.
Si una clave entregada no funciona, escribe a [email protected] en los 7 días siguientes con el id del pedido y una captura del error de activación. La reclamación se traslada al proveedor; solo habrá sustitución o abono si el proveedor sustituye o reembolsa esa clave.
Webhooks
Configura una URL https en Panel → Webhooks y recibirás un secreto de firma (whsec_…). Cada evento es un POST con las cabeceras X-BCK-Event, X-BCK-Delivery (id único: úsalo para descartar duplicados) y X-BCK-Signature: t=<unix>,v1=<hex> donde v1 = HMAC-SHA256(secreto, t + "." + cuerpo).
| Evento | Cuándo |
|---|---|
| order.completed | Todas las unidades entregadas. |
| order.partially_completed | Parte entregada y el resto reembolsado. |
| order.failed | Nada entregado, reembolso completo. |
| balance.credited | Se ha añadido una recarga a tu saldo. |
| webhook.test | Enviado por POST /webhooks/test o el botón del panel. |
{
"id": "evt_4m2k9q7x1z8c3v5b6n0p",
"event": "order.completed",
"mode": "live",
"createdAt": "2026-10-01T16:20:31.512Z",
"data": { "id": "po_7f3k2m9q4x8z1c5v6b0n", "externalId": "shop-10045", "status": "completed", "total": 3.71, "...": "same object as GET /orders/{id}" }
}- Responde 2xx en menos de 8 s y procesa en segundo plano. No se siguen redirecciones.
- Los envíos fallidos se reintentan tras 10 s, 30 s, 1 min, 5 min, 15 min, 30 min, 1 h, 2 h, 4 h, 6 h y 12 h (~26 h).
- Los eventos pueden llegar desordenados o repetidos: ante la duda vuelve a leer GET /orders/{id}.
Errores
Los errores usan códigos HTTP y un code estable. Indica el requestId al contactar con soporte.
{
"error": {
"code": "PRICE_CHANGED",
"message": "The price of prod-17298 is now 3.95 EUR, above your maxUnitPrice.",
"details": { "productId": "prod-17298", "currentPrice": 3.95 },
"requestId": "req_k3m9x2q8z7c1v4b6"
}
}| code | HTTP | Significado | Qué hacer |
|---|---|---|---|
| UNAUTHORIZED | 401 | Clave ausente, inválida, revocada o caducada. | Revisa la clave o crea otra en el panel. |
| ACCOUNT_NOT_ACTIVE | 403 | Cuenta de partner pendiente, suspendida o rechazada. | Contáctanos. |
| IP_NOT_ALLOWED | 403 | La IP no está en tu lista blanca. | Añádela en Panel → Webhooks y seguridad. |
| INSUFFICIENT_SCOPE | 403 | La clave no tiene permiso para este endpoint. | Crea una clave con ese permiso. |
| NOT_FOUND | 404 | Producto, pedido o ruta desconocidos. | — |
| VALIDATION_ERROR | 400 | Cuerpo o parámetros inválidos (details indica cada campo). | Corrige la petición; no la repitas igual. |
| INSUFFICIENT_BALANCE | 402 | Saldo insuficiente. No se ha cobrado nada. | Recarga y reintenta. |
| PRICE_CHANGED | 409 | El precio real supera maxUnitPrice. No se ha cobrado nada. | Actualiza el precio (details.currentPrice) y reintenta. |
| OUT_OF_STOCK | 409 | No hay unidades suficientes. No se ha cobrado nada. | Reintenta más tarde u oculta el producto. |
| PRODUCT_UNAVAILABLE | 409 | Producto no vendible por API. | Retíralo de tu tienda. |
| DUPLICATE_EXTERNAL_ID | 409 | externalId ya usado; details.orderId es el pedido existente. | Consulta ese pedido. |
| IDEMPOTENCY_CONFLICT | 409 | Idempotency-Key reutilizada con otro cuerpo. | Usa una clave nueva por pedido. |
| IDEMPOTENCY_IN_PROGRESS | 409 | La misma Idempotency-Key se está procesando. | Reintenta en unos segundos. |
| ORDER_LIMIT_EXCEEDED | 422 | Total por encima de tu límite por pedido. | Divide el pedido o pide un límite mayor. |
| DAILY_LIMIT_EXCEEDED | 422 | Límite de gasto de 24 h alcanzado. | Espera o pide un límite mayor. |
| RATE_LIMITED | 429 | Demasiadas peticiones. | Espera (details.retryAfterMs). |
| SERVICE_UNAVAILABLE | 503 | No se puede vender temporalmente. No se ha cobrado nada. | Reintenta con espera exponencial. |
| INTERNAL_ERROR | 500 | Error inesperado (registrado con requestId). | Reintenta con la misma Idempotency-Key. |
Límites
| Límite | Valor |
|---|---|
| Peticiones por clave | 20 / segundo y 600 / minuto |
| Pedidos por cuenta | 120 / minuto |
| Descargas de claves por cuenta | 300 / minuto |
| Líneas por pedido / unidades por línea | 10 / 9 |
| Importe por pedido y gasto en 24 h | Por cuenta (ver Panel → Resumen); pídenos ampliarlos |
| Tamaño de página | 100 productos o pedidos |
Saldo y recargas
GET /balance devuelve el saldo del modo de la clave. Recarga desde Panel → Saldo: recibes una referencia única para indicar en la transferencia; el importe se abona al recibirlo (e-mail + webhook balance.credited). Todos los movimientos aparecen en el extracto.
Implementarlo con una IA / ejemplo completo
Documentación legible por máquinas: https://buycheapkeys.com/developers.es.md (esta página en Markdown), https://buycheapkeys.com/developers.md (inglés), la especificación OpenAPI 3.1 https://buycheapkeys.com/partner-api/v1/openapi.json y https://buycheapkeys.com/llms.txt. Pásale estas URLs a tu asistente de IA junto con el prompt de abajo.
Integración de referencia completa (Node.js 18+, sin dependencias): https://buycheapkeys.com/developers/bck-partner-client.mjs — sincronización del catálogo con cursor, pedidos seguros con reintentos e idempotencia, descarga de claves y un receptor de webhooks con verificación de firma.
curl -O https://buycheapkeys.com/developers/bck-partner-client.mjs
export BCK_API_KEY=bck_test_... # clave sandbox de Panel → Partner → Claves API
node bck-partner-client.mjs balance
node bck-partner-client.mjs sync
node bck-partner-client.mjs order <productId> 1 <maxUnitPrice>- Regla 1 — vende solo productos con inStock=true y removed=false; resincroniza cada 5–15 min desde el nextCursor guardado.
- Regla 2 — un pedido de cliente = un externalId = una Idempotency-Key; reutiliza ambos en cada reintento.
- Regla 3 — envía siempre maxUnitPrice; los errores 4xx de negocio son definitivos y no se ha cobrado nada.
- Regla 4 — reintenta solo errores de red, 429, 5xx e IDEMPOTENCY_IN_PROGRESS, con espera exponencial.
- Regla 5 — un pedido en processing ya está pagado: espera al webhook o consulta GET /orders/{id}; nunca lo repitas.
- Regla 6 — guarda la clave API en el servidor; verifica la firma de los webhooks; muestra al cliente el deliveryType y la región de activación.
Lista de seguridad
- Guarda las claves API solo en tu servidor (nunca en navegadores ni apps), en variables de entorno o un gestor de secretos.
- Usa claves distintas por sistema y con los permisos mínimos; activa la lista blanca de IPs.
- Envía siempre maxUnitPrice y una Idempotency-Key.
- Verifica la firma y la marca de tiempo de cada webhook antes de fiarte de él.
- Si sospechas una filtración, crea claves nuevas, rota el secreto del webhook y revoca las antiguas.