-20 %

20 % de réduction sur Windows et Office

BuyCheapKeys

BuyCheapKeys Partner API

A REST/JSON API to sell Windows and Office keys on your own site. Each order is paid from your prepaid balance and the keys come back in the same response, bought from the cheapest seller with stock at that moment.

Base URL: https://buycheapkeys.com/partner-api/v1 Get API keys Download the OpenAPI 3.1 specification

Overview

Base URL: https://buycheapkeys.com/partner-api/v1. All requests and responses are JSON (UTF-8). Prices are in EUR with two decimals and do not include VAT. Dates are ISO 8601 in UTC.

  • Catalogue: list and incrementally sync every product you can sell, with your partner price.
  • Orders: buy up to 10 lines × 9 units per request; the total is debited from your balance at once.
  • Delivery: the keys are in the response of the order and can be read again with GET /orders/{id}/keys.
  • Claims: a faulty key can be claimed for 14 days from your partner dashboard, with messages and screenshots. A replacement key reaches your order automatically and a refund goes back to your balance.

Authentication and environments

Create keys in Dashboard → Partner → API keys. A key is shown once; we only store its hash. Send it in the Authorization header (X-Api-Key is accepted too).

curl https://buycheapkeys.com/partner-api/v1/ping \
  -H "Authorization: Bearer bck_test_xxxxxxxxxxxx_..."
Key prefixModeBehaviour
bck_test_SandboxReal catalogue and prices, separate test balance (resettable), fake keys like TEST-AB12C-…, no money moves. Webhooks are sent with mode: test.
bck_live_ProductionReal purchases charged to your live balance.
ScopeAllows
catalog:readGET /products, /products/{id}, /meta, /me, /ping, /gift-cards
webhooks:writePUT and DELETE /webhook (register or remove your webhook URL)
orders:writePOST /orders, POST /gift-cards/orders
orders:readGET /orders, /orders/{id}, /orders/{id}/keys, /gift-cards/orders, /gift-cards/orders/{id}, POST /webhooks/test
balance:readGET /balance

Optional IP allowlist (IPv4/IPv6, CIDR) per account. Keys never expire unless revoked; rotate them by creating a new key and revoking the old one.

Quick start

1) Create a bck_test_ key. 2) List products. 3) Place an order: it comes back completed, with the keys. 4) Read the keys again at any time with GET /orders/{id}/keys. Partner accounts still being approved can use sandbox keys.

curl "https://buycheapkeys.com/partner-api/v1/products?limit=100&page=1&inStock=true" \
  -H "Authorization: Bearer $BCK_API_KEY"

Catalogue and synchronisation

GET /products returns up to 100 products ordered by updatedAt (then id). updatedAt only changes when something you care about changes (price, stock, name, region, texts, images). Sync with the cursor: every response has nextCursor and hasMore; pass nextCursor back as ?cursor= until hasMore is false, and keep it for the next run. It never skips or repeats products, even when many share the same timestamp. page and updatedSince remain available for browsing. Filters: inStock, platform, region, productType, deliveryType, search, ids (comma-separated, max 100).

curl "https://buycheapkeys.com/partner-api/v1/products?cursor=$SAVED_NEXT_CURSOR&includeRemoved=true&limit=100" \
  -H "Authorization: Bearer $BCK_API_KEY"

Recommended: full sync once (no cursor), then every 5–15 minutes starting from the stored nextCursor, always with includeRemoved=true. A product with removed: true or inStock: false must not be sold.

{
  "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
}
FieldMeaning
nextCursor / hasMoreResponse fields for sync: pass nextCursor as ?cursor= while hasMore is true; store the last one for the next run.
priceYour unit price in EUR. Re-checked live when you order.
deliveryTypekey = code to redeem · gift = Steam Gift link · account = login credentials · link = activation link. Tell your customer which one they buy.
region / activationRegion label plus the exact ISO list of countries where it can NOT be activated (null = only the region is known).
qtyUnits in stock (capped at 999).
coverImage / screenshotsServed from our CDN; cache them on your side.
activationDetailsRedemption instructions (English) to show your customer.

Orders

POST /orders with externalId (your unique reference) and items (productId, qty 1–9, maxUnitPrice). For each line we check the live price: if it is above maxUnitPrice the whole order is rejected with PRICE_CHANGED and nothing is charged. If it is lower, you pay the lower price. Promo code: add couponCode: MICROSOFT20 to the order body to pay 20 % less for the Microsoft products while the promotion is active (maxUnitPrice is compared with the discounted price; an unknown code or an ended promotion returns INVALID_COUPON and nothing is charged).

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
}
  • Send an Idempotency-Key header (8–100 chars) and reuse it when you retry: you get the original response and are never charged twice. externalId is also unique per account.
  • The order is completed in the same request: the response has status completed and a keys array. A replayed request (same Idempotency-Key) returns the order without its keys and keysOmitted: true: read them with GET /orders/{id}/keys.
  • If there is no stock or your balance is too low nothing is charged and no key is used (OUT_OF_STOCK, INSUFFICIENT_BALANCE). maxUnitPrice is a ceiling for every key; a line can be split in several items when sellers with different prices are needed.
  • If a request times out, send it again with the same externalId: you get DUPLICATE_EXTERNAL_ID with the order id and can read its keys.
StatusMeaning
completedAll units delivered. The keys are in the response.
partially_refundedSome keys were refunded to your balance after claims.
refundedEvery key of the order was refunded to your balance.

Key delivery

GET /orders/{id}/keys returns every delivered unit: value holds the code, the Steam Gift URL, the account credentials or the activation link depending on deliveryType. contentType is text/plain or an image type (then value is base64).

Store keys encrypted and show them only to the buyer. The first download of each order is logged on our side as delivery evidence.

Claims and refunds

Each key comes from a seller who is paid after a holding period, so a faulty key can be claimed for 14 days. Open the order in Dashboard → Orders (API orders), pick the key, write to the seller and attach screenshots of the activation error. The seller can send a replacement key (it appears in your order and you get the key.replaced webhook) or refund the key (the price returns to your API balance once and you get order.refunded). If you do not agree, escalate and BuyCheapKeys decides.

Check the region, excludedCountries and the licence type before selling, and show them to your customer. A refund to your balance does not refund your own customer: that is your decision.

BuyCheapKeys Gift Cards in bulk

Buy our own gift cards to resell: codes BCK-XXXX-XXXX-XXXX-XXXX that add their value to the BuyCheapKeys Wallet of whoever redeems them at buycheapkeys.com/wallet. GET /gift-cards returns the values on sale, the volume discounts, the maximums per order and your Wallet balance. POST /gift-cards/orders buys N cards of one value and returns the codes at once; GET /gift-cards/orders/{id} shows them again with the status of each code.

curl https://buycheapkeys.com/partner-api/v1/gift-cards \
  -H "Authorization: Bearer $BCK_API_KEY"
  • Paid only from the BuyCheapKeys Wallet of your shop account (top it up at buycheapkeys.com/wallet), not from the API balance. Price = face value minus the volume discount for that quantity.
  • Values, discounts and maximums can change: send maxTotal to refuse a higher total (PRICE_CHANGED, nothing charged). Idempotency-Key and the unique externalId work as for orders.
  • Sale paused: PRODUCT_UNAVAILABLE (409). Value not sold or quantity out of range: VALIDATION_ERROR. Order too large: ORDER_LIMIT_EXCEEDED. Not enough Wallet balance: INSUFFICIENT_BALANCE (402).
  • Sandbox keys return fake BCK-TEST-… codes that cannot be redeemed and charge nothing.
  • Sold codes are final. Each code has an expiry date (expiresAt) and redemption limits per account. A code we block for fraud is no longer shown (code: null, status blocked). Store the codes encrypted.

Webhooks

Set an https URL in Dashboard → Webhooks; you get a signing secret (whsec_…). Every event is a POST with headers X-BCK-Event, X-BCK-Delivery (unique id — de-duplicate on it) and X-BCK-Signature: t=<unix>,v1=<hex> where v1 = HMAC-SHA256(secret, t + "." + rawBody).

EventWhen
order.completedAn order was completed.
order.refundedA claim ended in a refund to your balance (`refundedSale`, `amount`).
product.updatedPrice, stock or sheet of a product changed (`data.product` has the full product, `removed: true` when it is retired).
key.replacedA seller replaced a key: `oldKeyId`, `newKeyId`; read the new key with GET /orders/{id}/keys.
balance.creditedA top-up was added to your balance.
webhook.testSent by POST /webhooks/test or the dashboard button.
{
  "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}" }
}
  • Answer 2xx within 8 s and do the work asynchronously. Redirects are not followed.
  • Failed deliveries are retried after 10 s, 30 s, 1 min, 5 min, 15 min, 30 min, 1 h, 2 h, 4 h, 6 h and 12 h (~26 h).
  • Events may arrive out of order or more than once: always re-read GET /orders/{id} when in doubt.

Errors

Errors use HTTP status codes and a stable code. Quote requestId when contacting support.

{
  "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"
  }
}
codeHTTPMeaningWhat to do
UNAUTHORIZED401Missing, invalid, revoked or expired key.Check the key; create a new one in the dashboard.
ACCOUNT_NOT_ACTIVE403Partner account pending, suspended or rejected.Contact us.
IP_NOT_ALLOWED403Request IP not in your allowlist.Add the IP in Dashboard → Webhooks & security.
INSUFFICIENT_SCOPE403The key lacks the scope for this endpoint.Create a key with the scope.
NOT_FOUND404Unknown product, order or route.—
VALIDATION_ERROR400Invalid body or parameters (details lists each field).Fix the request; do not retry as is.
INSUFFICIENT_BALANCE402Balance too low. Nothing was charged.Top up and retry.
PRICE_CHANGED409Live unit price above maxUnitPrice. Nothing was charged.Re-price (details.currentPrice) and retry.
INVALID_COUPON400couponCode unknown, or the promotion has ended. Nothing was charged.Remove the code or send the active one (MICROSOFT20).
OUT_OF_STOCK409Not enough units available. Nothing was charged.Retry later or hide the product.
PRODUCT_UNAVAILABLE409Product not sellable through the API.Remove it from your shop.
DUPLICATE_EXTERNAL_ID409externalId already used; details.orderId is the existing order.Fetch that order instead.
IDEMPOTENCY_CONFLICT409Idempotency-Key reused with a different body.Use a new key per order.
IDEMPOTENCY_IN_PROGRESS409Same Idempotency-Key still being processed.Retry in a few seconds.
ORDER_LIMIT_EXCEEDED422Order total above your per-order limit.Split the order or ask for a higher limit.
DAILY_LIMIT_EXCEEDED42224-hour spend limit reached.Wait or ask for a higher limit.
RATE_LIMITED429Too many requests.Back off (details.retryAfterMs).
SERVICE_UNAVAILABLE503Temporarily unable to sell. Nothing was charged.Retry with exponential backoff.
INTERNAL_ERROR500Unexpected error (logged with requestId).Retry with the same Idempotency-Key.

Limits

LimitValue
Requests per key20 / second and 600 / minute
Orders per account120 / minute
Key downloads per account300 / minute
Lines per order / units per line10 / 9
Order value and 24-hour spendPer account (see Dashboard → Overview); ask us to raise them
Page size100 products or orders

Balance and top-ups

GET /balance returns the balance of the key's mode. Top up from Dashboard → Balance: you receive a unique reference to include in the bank transfer; the amount is credited when it arrives (e-mail + balance.credited webhook). Every movement appears in the ledger.

Implementing with an AI assistant / full example

Machine-readable docs: https://buycheapkeys.com/developers.md (this page as Markdown), https://buycheapkeys.com/developers.es.md (Spanish), the OpenAPI 3.1 spec https://buycheapkeys.com/partner-api/v1/openapi.json and https://buycheapkeys.com/llms.txt. Give these URLs to your AI assistant together with the prompt below.

Complete reference integration (Node.js 18+, no dependencies): https://buycheapkeys.com/developers/bck-partner-client.mjs — catalogue sync with cursor, safe ordering with retries and idempotency, key download and a webhook receiver with signature verification.

curl -O https://buycheapkeys.com/developers/bck-partner-client.mjs
export BCK_API_KEY=bck_test_...   # sandbox key from Dashboard → Partner → API keys
node bck-partner-client.mjs balance
node bck-partner-client.mjs sync
node bck-partner-client.mjs order <productId> 1 <maxUnitPrice>
  • Invariant 1 — sell only products with inStock=true and removed=false; re-sync every 5–15 min from the stored nextCursor.
  • Invariant 2 — one customer order = one externalId = one Idempotency-Key; reuse both on every retry.
  • Invariant 3 — always send maxUnitPrice; 4xx business errors are final and nothing was charged.
  • Invariant 4 — retry only network errors, 429, 5xx and IDEMPOTENCY_IN_PROGRESS, with exponential backoff.
  • Invariant 5 — never re-order after a timeout: repeat the same externalId and read the keys of the existing order.
  • Invariant 6 — keep the API key server-side; verify webhook signatures; show customers the deliveryType and activation region.

Security checklist

  • Keep API keys on your server only (never in browsers or apps) and in a secret manager / environment variables.
  • Use separate keys per system and the minimum scopes; enable the IP allowlist.
  • Always send maxUnitPrice and an Idempotency-Key.
  • Verify every webhook signature and timestamp before trusting it.
  • Rotate keys and the webhook secret if you suspect a leak, then revoke the old ones.