Reseller API
Issue Xenoware product keys to your customers programmatically. Your storefront, your payments, your prices. No account needed — just a key and the launcher.
Getting startedOverview
Resell Xenoware. The Reseller API lets you issue product keys to your customers, manage their lifecycle, and receive webhooks on state changes. Your customers don't need a xenoware.xyz account — they just enter the key in the launcher and play.
- Flat wholesale discount off retail for every approved reseller. No volume tiers, no minimum.
- You take payment from your customer in your own currency, through your own payment processor — you keep the customer relationship and the margin above wholesale.
- Your backend calls
POST /api/reseller/licensesto generate a product key. We bill your wholesale balance and return anRS-XXXX-XXXX-XXXXkey you give to your customer. - We invoice you weekly for the wholesale cost of all keys issued in the period (net-7 default).
Getting startedQuick start
End-to-end walkthrough: from your first API call to a customer playing. You can follow along with curl or any HTTP client.
Step 1 — Check your pricing
Hit the account endpoint to see which games and plans are enabled for your key, and what they cost.
curl -s https://xenoware.xyz/api/reseller/account \
-H "Authorization: Bearer rsk_live_YOUR_KEY" | jq .Look at the prices object. Each game lists plans with priceCents (what you pay) and ownerCutCents (our cut). Your profit is the difference between what you charge the customer and priceCents.
Step 2 — Issue a key
When your customer pays, call the issue endpoint with your own order ID as externalId. This is idempotent — if your request times out and you retry, you won't get double-billed.
curl -s -X POST https://xenoware.xyz/api/reseller/licenses \
-H "Authorization: Bearer rsk_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"externalId": "order_001",
"game": "eft",
"plan": "1m"
}' | jq .You get back a productKey like RS-HGKM-4NWP-T8VB. This is what you deliver to your customer.
Step 3 — Deliver the key to your customer
Send your customer two things:
- The
RS-XXXX-XXXX-XXXXproduct key. - The launcher download link:
https://xenoware.xyz/api/loader/download/reseller
That's it. No account creation needed on their end.
Step 4 — Customer activates
Your customer downloads the launcher, opens it, pastes the product key, and clicks ACTIVATE. The launcher handles everything from there — engine download, driver loading, injection, and auto-updates. HWID is bound on first use.
Step 5 — Monitor & manage
Use the API to track the key's lifecycle:
- Check status:
GET /api/reseller/licenses/<id>— see if the customer has activated, their HWID, expiry date. - Extend:
POST /api/reseller/licenses/<id>/extend— customer wants to renew? Add more time to the same key. - Revoke:
POST /api/reseller/licenses/<id>/revoke— chargeback or fraud? Kill the key immediately. - Webhooks: Set up a webhook URL to get notified when keys are activated, expire, or get banned.
Step 6 — Pay your invoice
Every Monday we invoice the wholesale cost of all keys issued that week. Pay within 7 days (net-7). Your outstanding balance is always visible via GET /api/reseller/account.
Getting startedAuthentication
Base URL: https://xenoware.xyz/api/reseller
All endpoints require a bearer token in the Authorizationheader. Your reseller API key is the bearer token - no separate token exchange.
Authorization: Bearer rsk_live_d8e2c5...Keys come in two flavours: rsk_test_ (sandbox environment, no real money) and rsk_live_ (production, billed). Use sandbox until your integration is clean end-to-end.
Getting startedErrors
All errors follow this shape:
{ "error": "code", "detail": "optional human description" }Common codes:
401 invalid_key— Authorization header missing or API key not recognised.403 account_suspended— your reseller account has been frozen (typically for non-payment).400 invalid_request— Zod validation failed. Thedetailfield includes which field was bad.400 invalid_game— the game id is not recognised.400 plan_not_configured— no price is set for the requested plan. Contact us to enable it.400 already_revoked— the license has already been revoked.429 rate_limited— slow down; checkRetry-Afterheader.5xx server_error— transient; retry with backoff. Idempotency viaexternalIdmeans retry is always safe.
Getting startedRate limits
- 120 issuances per minute per reseller key (issue + extend share this limit). Email [email protected] ahead of a flash sale if you expect a spike.
- 600 reads per minute — list + detail combined.
- 60 webhook deliveries per minute, per URL — we retry failed deliveries with exponential backoff (1m, 5m, 30m, 2h, 12h) for up to 24h.
LicensesIssue a license
Generates a product key for the specified game + plan. Idempotent on externalId: re-posting the same external id returns the existing key rather than double-billing.
POST /api/reseller/licenses
Authorization: Bearer rsk_live_…
Content-Type: application/json
{
"externalId": "order_19238",
"game": "eft",
"plan": "1m"
}Request fields
| Field | Type | Required | Description |
|---|---|---|---|
externalId | string | yes | Your internal order ID (max 64 chars). Ensures idempotency — re-posting the same value returns the original key without charging again. |
game | string | yes | "eft" or "dayz". New games announced as they ship. |
plan | string | yes | One of "1d", "1w", "1m", "3m". |
Response (200)
{
"id": "abc123...",
"productKey": "RS-HGKM-4NWP-T8VB",
"game": "eft",
"plan": "1m",
"expiresAt": "2026-07-21T13:42:01.000Z",
"wholesaleCents": 1350,
"resellerPriceCents": 4500,
"currency": "EUR"
}Response fields
| Field | Type | Description |
|---|---|---|
id | string | License ID. Use this to get, extend, or revoke. |
productKey | string | The RS-XXXX-XXXX-XXXX key to give your customer. |
game | string | Game the key is for. |
plan | string | Duration plan. |
expiresAt | string | ISO 8601 expiry. Timer starts at issuance, not activation. |
wholesaleCents | number | Your wholesale cost (our cut) in cents. |
resellerPriceCents | number | The full reseller price in cents (your price to customers, if you use our default). |
currency | string | Currency code (default EUR). |
idempotent | boolean | Present and true when this is a replay of a previous issuance (same externalId). No additional charge was applied. |
Give the productKeyto your customer along with the launcher download link. That's all they need — no email, no account, no activation URL.
LicensesList licenses
Newest first. Cursor-paginated.
GET /api/reseller/licenses?limit=50&game=eft&cursor=…
Authorization: Bearer rsk_live_…Query parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
limit | number | 50 | Results per page (max 200). |
cursor | string | — | Opaque cursor from a previous response's nextCursor. |
game | string | — | Filter to a single game. |
Response (200)
{
"data": [
{
"id": "abc123...",
"externalId": "order_19238",
"productKey": "RS-HGKM-4NWP-T8VB",
"game": "eft",
"plan": "1m",
"hwid": null,
"revokedAt": null,
"revokedReason": null,
"wholesaleCents": 1350,
"currency": "EUR",
"expiresAt": "2026-07-21T13:42:01.000Z",
"createdAt": "2026-06-21T13:42:01.000Z"
}
],
"nextCursor": "2026-06-20T13:42:01.000Z"
}nextCursor is null when there are no more pages. Pass it as the cursor query parameter to fetch the next page.
LicensesGet a license
Fetch full details for a single license by ID.
GET /api/reseller/licenses/<id>
Authorization: Bearer rsk_live_…Response (200)
{
"licenseId": "abc123...",
"externalId": "order_19238",
"productKey": "RS-HGKM-4NWP-T8VB",
"game": "eft",
"plan": "1m",
"hwid": "A1B2C3D4...",
"claimedAt": "2026-06-21T14:05:00.000Z",
"revokedAt": null,
"revokedReason": null,
"wholesaleCents": 1350,
"currency": "EUR",
"expiresAt": "2026-07-21T13:42:01.000Z",
"createdAt": "2026-06-21T13:42:01.000Z"
}Response fields (beyond list)
| Field | Type | Description |
|---|---|---|
hwid | string | null | Hardware ID bound on first launcher use. Null until the customer activates. |
claimedAt | string | null | When the customer first used the key in the launcher. Null if not yet activated. |
LicensesExtend a license
Adds time to an existing key. The customer keeps the same product key — the expiry is pushed forward. Pricing follows the same wholesale rate as a new issuance.
POST /api/reseller/licenses/<id>/extend
Authorization: Bearer rsk_live_…
Content-Type: application/json
{
"plan": "1m",
"externalId": "order_19238_renew"
}Request fields
| Field | Type | Required | Description |
|---|---|---|---|
plan | string | yes | Duration to add: "1d", "1w", "1m", "3m". |
externalId | string | yes | Your internal reference for this renewal (max 64 chars). Use a unique ID per extension. |
Response (200)
{
"ok": true,
"previousExpiresAt": "2026-07-21T13:42:01.000Z",
"newExpiresAt": "2026-08-20T13:42:01.000Z",
"wholesaleCents": 1350,
"resellerPriceCents": 4500,
"currency": "EUR"
}If the key is already expired, the new time is added from now (not from the old expiry). If the key is still active, the new time is added to the current expiry.
Extending a revoked license returns 400 already_revoked. Issue a new key instead.
LicensesRevoke a license
Revokes the key immediately. The customer's launcher stops working on next login attempt. Use this for fraud, chargebacks, or support resolutions on your end.
POST /api/reseller/licenses/<id>/revoke
Authorization: Bearer rsk_live_…
Content-Type: application/json
{ "reason": "chargeback on order 19238" }Request fields
| Field | Type | Required | Description |
|---|---|---|---|
reason | string | no | Human-readable reason (max 500 chars). Defaults to "reseller revoked". |
Response (200)
{
"ok": true,
"revokedAt": "2026-06-22T12:00:00.000Z",
"creditedCents": 1350
}Refund logic
| Key state | Wholesale credit | Customer effect |
|---|---|---|
| Not yet activated (no HWID bound) | Full wholesale credited back to your balance | Key becomes invalid |
| Activated (customer is using it) | No automatic credit (creditedCents: 0) | Subscription terminated immediately |
For pro-rata refunds on activated keys, email [email protected] for manual review.
AccountAccount info
Returns your account details, outstanding balance, and the full wholesale price table for all games and plans.
GET /api/reseller/account
Authorization: Bearer rsk_live_…Response (200)
{
"resellerId": "rsl_a1b2c3...",
"name": "Acme Cheats Ltd.",
"email": "[email protected]",
"ownerPercent": 30,
"outstandingCents": 12450,
"currency": "EUR",
"games": [
{ "id": "eft", "label": "Escape from Tarkov" },
{ "id": "dayz", "label": "DayZ" }
],
"prices": {
"eft": {
"1d": { "priceCents": 500, "ownerCutCents": 150, "planLabel": "1 day" },
"1w": { "priceCents": 2500, "ownerCutCents": 750, "planLabel": "1 week" },
"1m": { "priceCents": 4500, "ownerCutCents": 1350, "planLabel": "1 month" },
"3m": { "priceCents": 8500, "ownerCutCents": 2550, "planLabel": "3 months" }
},
"dayz": { "...": "same structure" }
}
}Response fields
| Field | Type | Description |
|---|---|---|
resellerId | string | Your reseller ID. |
name | string | Display name of your reseller account. |
email | string | Contact email on file. |
ownerPercent | number | Our cut percentage. Your margin = priceCents - ownerCutCents. |
outstandingCents | number | Unpaid wholesale balance in cents. Invoiced weekly. |
currency | string | Billing currency. |
games | array | All supported games with display labels. |
prices | object | Per-game, per-plan pricing. priceCents is what you pay, ownerCutCents is our portion. |
AccountBilling & refunds
- Invoices are generated weekly (Mondays, 09:00 UTC) and settled net-7 by default.
- Unclaimed keys (customer never activated) are automatically credited on revoke — the wholesale charge is removed from your balance.
- Claimed keys (customer activated and used the product) are not automatically credited on revoke. Email [email protected] within 24 hours for manual review.
- Keys banned for TOS violation are not creditable — the charge stands.
WebhooksConfiguring
Set your webhook URL in the reseller dashboard. We POST JSON payloads when meaningful state transitions happen to licenses you've issued. All payloads are HMAC-SHA256 signed.
WebhooksEvent types
| Event | When it fires |
|---|---|
key.activated | Customer used the key for the first time (HWID bound). |
key.expired | Subscription ran out. |
key.revoked | Key revoked via /revoke endpoint. |
key.banned | Customer was system-banned (TOS violation). Wholesale charge is not refunded. |
key.hwid_reset | Admin reset the hardware ID binding (rare). |
WebhooksSignature verification
Each delivery carries an X-Reseller-Signature header with the HMAC-SHA256 hex digest of the raw request body, signed with your webhook signing secret. Compare in constant time:
import { createHmac, timingSafeEqual } from "crypto";
function verify(rawBody, signatureHeader, secret) {
const expected = createHmac("sha256", secret)
.update(rawBody).digest("hex");
const a = Buffer.from(expected, "hex");
const b = Buffer.from(signatureHeader ?? "", "hex");
return a.length === b.length && timingSafeEqual(a, b);
}Example payload:
POST https://your-app.example/xenoware-webhook
X-Reseller-Signature: 7c3a9e1d...
Content-Type: application/json
{
"event": "key.activated",
"timestamp": "2026-05-14T10:12:33.123Z",
"id": "abc123...",
"productKey": "RS-HGKM-4NWP-T8VB",
"externalId": "order_19238",
"game": "eft",
"plan": "1m",
"expiresAt": "2026-06-14T13:42:01.000Z"
}OtherSandbox
Use rsk_test_keys against the same base URL. Sandbox issuances don't accrue real wholesale charges and don't fire production webhooks (a sandbox: true field is added to test events so your handler can no-op on them). Test licenses auto-revoke after 24 hours regardless of plan.
OtherCustomer experience
Your customers don't need a xenoware.xyz account. The flow is:
- You issue a key via the API and receive an
RS-XXXX-XXXX-XXXXproduct key. - You give the customer the key + the launcher download link:
https://xenoware.xyz/api/loader/download/reseller - Customer opens the launcher, pastes the key, clicks ACTIVATE.
- Done. The launcher handles everything — engine download, driver loading, auto-updates.
HWID is bound on first use. If the customer needs to switch machines, you revoke and re-issue, or contact us for a manual HWID reset.
The launcher auto-updates itself — you don't need to redistribute the binary when we push a new version. The download link always serves the latest build.
OtherReference client
A minimal Node.js reseller client is published at github.com/xxxenomus/xenoware-reseller-js (sample only - not officially supported). It demonstrates idempotent issuance, webhook signature verification, and retry with backoff in ~100 lines.