API de partners de BuyCheapKeys
API REST/JSON para vender claves de Windows y Office en tu propia web. Cada pedido se paga con tu saldo prepago y las claves llegan en la misma respuesta, compradas al vendedor más barato con stock en ese momento.
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: las claves vienen en la respuesta del pedido y se pueden volver a leer con GET /orders/{id}/keys.
- Reclamaciones: una clave defectuosa se puede reclamar durante 14 días desde tu panel de partner, con mensajes y capturas. Una clave de sustitución llega sola a tu pedido y un reembolso vuelve a tu saldo.
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, /me, /ping, /gift-cards |
| webhooks:write | PUT y DELETE /webhook (registrar o quitar tu URL de webhook) |
| orders:write | POST /orders, POST /gift-cards/orders |
| orders:read | GET /orders, /orders/{id}, /orders/{id}/keys, /gift-cards/orders, /gift-cards/orders/{id}, 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: vuelve completado y con las claves. 4) Vuelve a leer las claves cuando quieras con GET /orders/{id}/keys. Las cuentas de partner que aún se están aprobando pueden usar claves sandbox.
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 en stock (máx. 999). |
| 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. Código promocional: añade couponCode: MICROSOFT20 al cuerpo del pedido para pagar un 20 % menos en los productos Microsoft mientras la promoción esté activa (maxUnitPrice se compara con el precio ya rebajado; un código desconocido o una promoción terminada devuelve INVALID_COUPON y no se cobra nada).
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. - El pedido se completa en la misma petición: la respuesta trae estado
completedy un arraykeys. Una petición repetida (misma Idempotency-Key) devuelve el pedido sin las claves y conkeysOmitted: true: léelas con GET /orders/{id}/keys. - Si no hay stock o tu saldo no alcanza no se cobra nada ni se usa ninguna clave (
OUT_OF_STOCK,INSUFFICIENT_BALANCE).maxUnitPricees un tope para cada clave; una línea puede dividirse en varios items si hacen falta vendedores con precios distintos. - Si una petición agota el tiempo, envíala otra vez con el mismo externalId: recibes
DUPLICATE_EXTERNAL_IDcon el id del pedido y puedes leer sus claves.
| Estado | Significado |
|---|---|
| completed | Todas las unidades entregadas. Las claves vienen en la respuesta. |
| partially_refunded | Algunas claves se reembolsaron a tu saldo tras reclamaciones. |
| refunded | Todas las claves del pedido se reembolsaron a tu saldo. |
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.
Reclamaciones y reembolsos
Cada clave viene de un vendedor al que se paga tras un periodo de retención, así que una clave defectuosa se puede reclamar durante 14 días. Abre el pedido en Panel → Pedidos (API), elige la clave, escribe al vendedor y adjunta capturas del error de activación. El vendedor puede enviar una clave de sustitución (aparece en tu pedido y recibes el webhook key.replaced) o reembolsar la clave (el precio vuelve a tu saldo de la API una sola vez y recibes order.refunded). Si no estás de acuerdo, escala y decide BuyCheapKeys.
Revisa la región, excludedCountries y el tipo de licencia antes de vender y muéstraselos a tu cliente. Un reembolso a tu saldo no reembolsa a tu propio cliente: eso lo decides tú.
Tarjetas regalo BuyCheapKeys en bloque
Compra nuestras propias tarjetas regalo para revenderlas: códigos BCK-XXXX-XXXX-XXXX-XXXX que suman su valor a la Wallet de BuyCheapKeys de quien los canjea en buycheapkeys.com/wallet. GET /gift-cards devuelve los importes a la venta, los descuentos por volumen, los máximos por pedido y el saldo de tu Wallet. POST /gift-cards/orders compra N tarjetas de un mismo importe y devuelve los códigos al momento; GET /gift-cards/orders/{id} los vuelve a mostrar con el estado de cada código.
curl https://buycheapkeys.com/partner-api/v1/gift-cards \
-H "Authorization: Bearer $BCK_API_KEY"- Se paga solo con la Wallet de BuyCheapKeys de tu cuenta de la tienda (recárgala en buycheapkeys.com/wallet), no con el saldo de la API. Precio = valor facial menos el descuento por volumen de esa cantidad.
- Los importes, descuentos y máximos pueden cambiar: envía
maxTotalpara rechazar un total mayor (PRICE_CHANGED, sin cobro).Idempotency-Keyy elexternalIdúnico funcionan igual que en los pedidos. - Venta en pausa:
PRODUCT_UNAVAILABLE(409). Importe que no se vende o cantidad fuera de rango:VALIDATION_ERROR. Pedido demasiado grande:ORDER_LIMIT_EXCEEDED. Saldo insuficiente en la Wallet:INSUFFICIENT_BALANCE(402). - Las claves de sandbox devuelven códigos falsos
BCK-TEST-…que no se pueden canjear y no cobran nada. - Los códigos vendidos son definitivos. Cada código caduca (
expiresAt) y tiene límites de canje por cuenta. Un código que bloqueemos por fraude deja de mostrarse (code: null, estadoblocked). Guarda los códigos cifrados.
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 | Un pedido se ha completado. |
| order.refunded | Una reclamación acabó en reembolso a tu saldo (`refundedSale`, `amount`). |
| product.updated | Ha cambiado el precio, el stock o la ficha de un producto (`data.product` trae el producto completo, `removed: true` si se retira). |
| key.replaced | Un vendedor sustituyó una clave: `oldKeyId`, `newKeyId`; lee la nueva con GET /orders/{id}/keys. |
| 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. |
| INVALID_COUPON | 400 | couponCode desconocido o promoción terminada. No se ha cobrado nada. | Quita el código o envía el vigente (MICROSOFT20). |
| 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 — nunca repitas un pedido tras un timeout: repite el mismo externalId y lee las claves del pedido existente.
- 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.