Xenoware
← Home

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/licenses to generate a product key. We bill your wholesale balance and return an RS-XXXX-XXXX-XXXX key 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:

  1. The RS-XXXX-XXXX-XXXX product key.
  2. 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. The detail field 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; check Retry-After header.
  • 5xx server_error — transient; retry with backoff. Idempotency via externalId means 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

FieldTypeRequiredDescription
externalIdstringyesYour internal order ID (max 64 chars). Ensures idempotency — re-posting the same value returns the original key without charging again.
gamestringyes"eft" or "dayz". New games announced as they ship.
planstringyesOne 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

FieldTypeDescription
idstringLicense ID. Use this to get, extend, or revoke.
productKeystringThe RS-XXXX-XXXX-XXXX key to give your customer.
gamestringGame the key is for.
planstringDuration plan.
expiresAtstringISO 8601 expiry. Timer starts at issuance, not activation.
wholesaleCentsnumberYour wholesale cost (our cut) in cents.
resellerPriceCentsnumberThe full reseller price in cents (your price to customers, if you use our default).
currencystringCurrency code (default EUR).
idempotentbooleanPresent 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

ParameterTypeDefaultDescription
limitnumber50Results per page (max 200).
cursorstringOpaque cursor from a previous response's nextCursor.
gamestringFilter 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)

FieldTypeDescription
hwidstring | nullHardware ID bound on first launcher use. Null until the customer activates.
claimedAtstring | nullWhen 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

FieldTypeRequiredDescription
planstringyesDuration to add: "1d", "1w", "1m", "3m".
externalIdstringyesYour 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

FieldTypeRequiredDescription
reasonstringnoHuman-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 stateWholesale creditCustomer effect
Not yet activated (no HWID bound)Full wholesale credited back to your balanceKey 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

FieldTypeDescription
resellerIdstringYour reseller ID.
namestringDisplay name of your reseller account.
emailstringContact email on file.
ownerPercentnumberOur cut percentage. Your margin = priceCents - ownerCutCents.
outstandingCentsnumberUnpaid wholesale balance in cents. Invoiced weekly.
currencystringBilling currency.
gamesarrayAll supported games with display labels.
pricesobjectPer-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

EventWhen it fires
key.activatedCustomer used the key for the first time (HWID bound).
key.expiredSubscription ran out.
key.revokedKey revoked via /revoke endpoint.
key.bannedCustomer was system-banned (TOS violation). Wholesale charge is not refunded.
key.hwid_resetAdmin 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:

  1. You issue a key via the API and receive an RS-XXXX-XXXX-XXXX product key.
  2. You give the customer the key + the launcher download link:
    https://xenoware.xyz/api/loader/download/reseller
  3. Customer opens the launcher, pastes the key, clicks ACTIVATE.
  4. 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.