/ api · v1

The Keystation API.

A single REST surface for validating, minting and revoking keys across all three key types. All endpoints accept JSON, return JSON, and live under https://keysystem.nabzclan.vip/api/v1.

Examples cookbook
/ overview

Overview

Every API request requires an x-project header carrying your project's public ID (the short string visible at the top of every project page). Validation calls require only this header. Administrative calls additionally require a Authorization: Bearer <api_secret> header — the secret lives under the project's Settings tab.

We never accept the project secret over query parameters. All traffic must be HTTPS. CORS is enabled — but for any administrative endpoint you should be calling from your server, never your client.

/ auth

Authentication

GET/api/v1/me

Returns metadata about the project the supplied x-project header resolves to.

curl
curl https://keysystem.nabzclan.vip/api/v1/me \
  -H "x-project: p_arctic_fox_92"
response.json
{
  "ok": true,
  "project_id": "p_arctic_fox_92",
  "name": "Arctic Fox Hub",
  "type": "script",
  "is_active": true
}
/ errors

Errors

All errors return JSON with ok: false and an error code. Validation failures (invalid key, expired, hwid mismatch, etc.) still return HTTP 200 — only transport or authorization problems return 4xx/5xx.

unauthorized401Missing or invalid x-project / secret
forbidden403Project paused, or surface mismatch
not_found404Resource doesn't exist
rate_limited429Too many requests
invalid_key200Key string isn't recognized
expired200Key has expired
hwid_mismatch200HWID didn't match bound device
activation_limit200Seat cap reached
checkpoints_required200Outstanding checkpoint(s)
/ rate-limits

Rate limits

The validate endpoint is rate-limited at 240 req/min per (project, ip) pair. Keys with a configured rate_limit_per_minute apply on top. Hit the limit and you'll get HTTP 429 with a retry-after hint.

/ validate

Validate key

POST/api/v1/keys/validate

The bread and butter. Send the key value and (for script / license surfaces) a fingerprint. Returns a verdict.

request body

application/json
{
  "key": "KS-7H2P-VLNX-9KJ4",
  "hwid": "first-time-seen-device-id",      // script surfaces
  "machine_id": "fingerprint-string"        // license surfaces
}

example call

curl
curl -X POST https://keysystem.nabzclan.vip/api/v1/keys/validate \
  -H "x-project: p_arctic_fox_92" \
  -H "Content-Type: application/json" \
  -d '{"key":"KS-7H2P-VLNX-9KJ4","hwid":"7a3f2e9b"}'

response

200 ok
{
  "ok": true,
  "valid": true,
  "key_id": "k_xxxxxxxxxxxx",
  "type": "script",
  "expires_at": "2026-05-19T07:34:12.000Z",
  "scopes": [],
  "metadata": null
}

rejection example

200 ok · rejection
{
  "ok": true,
  "valid": false,
  "reason": "hwid_mismatch"
}
/ generate

Generate keys (admin)

POST/api/v1/keys/generaterequires project secret

Mint one or more keys server-side. Inherits project defaults if fields are omitted. Always call from your server — never expose your API secret client-side.

request body fields

countint 1–500Number of keys to mint (default: 1)
labelstringHuman-readable label for the batch
ttl_minutesintExpiry from now in minutes (0 = never)
max_activationsintLicense: seat cap (default: 1)
max_usesintTotal validation use cap
rate_limit_per_minuteintAPI keys: per-key rate limit
scopesstring[]Scopes to attach (API surfaces)
hwidstringPre-bind to a hardware ID (script)
metadataobjectArbitrary JSON stored on the key
curl
curl -X POST https://keysystem.nabzclan.vip/api/v1/keys/generate \
  -H "x-project: p_arctic_fox_92" \
  -H "Authorization: Bearer <api_secret>" \
  -H "Content-Type: application/json" \
  -d '{
    "count": 5,
    "label": "promo-friday",
    "ttl_minutes": 10080,
    "scopes": ["read", "write"],
    "rate_limit_per_minute": 60
  }'
200 ok
{
  "ok": true,
  "count": 5,
  "keys": [
    { "id": "k_a1b2c3", "key": "KS-7H2P-VLNX-9KJ4", "expires_at": "2026-06-01T00:00:00Z" },
    { "id": "k_d4e5f6", "key": "KS-G8XX-LM34-ZZ91", "expires_at": "2026-06-01T00:00:00Z" }
  ]
}
/ revoke

Revoke key (admin)

POST/api/v1/keys/revokerequires project secret

Mark a key as revoked. Subsequent validates return reason: "revoked".

curl
curl -X POST https://keysystem.nabzclan.vip/api/v1/keys/revoke \
  -H "x-project: p_arctic_fox_92" \
  -H "Authorization: Bearer <api_secret>" \
  -H "Content-Type: application/json" \
  -d '{"key": "KS-7H2P-VLNX-9KJ4"}'
/ inspect

Inspect key (admin)

GET/api/v1/keys/{key}requires project secret

Returns the full record for a single key.

curl
curl https://keysystem.nabzclan.vip/api/v1/keys/KS-7H2P-VLNX-9KJ4 \
  -H "x-project: p_arctic_fox_92" \
  -H "Authorization: Bearer <api_secret>"
/ checkpoints

Checkpoints (script)

POST/api/v1/checkpoints/complete

For script projects with checkpoints enabled, mark a checkpoint as cleared by slug. Idempotent — re-completing returns successfully without double-counting.

curl
curl -X POST https://keysystem.nabzclan.vip/api/v1/checkpoints/complete \
  -H "x-project: p_arctic_fox_92" \
  -H "Content-Type: application/json" \
  -d '{"key":"KS-7H2P-VLNX-9KJ4","checkpoint":"step-1-linkvertise"}'
/ activate

License activation

POST/api/v1/license/activate

Register a machine fingerprint to a license key. Subject to the key's max_activations.

curl
curl -X POST https://keysystem.nabzclan.vip/api/v1/license/activate \
  -H "x-project: p_arctic_fox_92" \
  -H "Content-Type: application/json" \
  -d '{"key":"ABCD-EFGH-IJKL-MNOP-QRST","machine_id":"fp-string","machine_name":"Dell-XPS-13"}'
/ deactivate

License deactivation

POST/api/v1/license/deactivate

Remove a machine fingerprint, freeing a seat.

curl
curl -X POST https://keysystem.nabzclan.vip/api/v1/license/deactivate \
  -H "x-project: p_arctic_fox_92" \
  -H "Content-Type: application/json" \
  -d '{"key":"ABCD-EFGH-IJKL-MNOP-QRST","machine_id":"fp-string"}'
/ webhook-events

Webhook events

Subscribe from the project's Webhooks tab. Each delivery is a POST containing:

payload shape
{
  "event": "key.validated",
  "sent_at": "2026-05-11T10:23:01.124Z",
  "data": { "key_id": "k_xxx", "ip": "1.2.3.4" }
}
key.generatedKey was minted
key.validatedA validate call succeeded
key.rejectedA validate call failed
key.revokedKey was revoked
key.expiredKey crossed its TTL
key.activatedLicense seat consumed
key.deactivatedLicense seat freed
checkpoint.completeScript checkpoint cleared
/ webhook-verify

Verifying signatures

Every webhook includes an x-keystation-signature header containing sha256=<hex> — the HMAC of the raw body using the signing secret shown when the subscription was created. Use a raw body parser so the bytes are identical to what was signed.

verify.ts
import crypto from "node:crypto";

export function verifyWebhook(body: string, header: string, secret: string): boolean {
  const expected =
    "sha256=" +
    crypto.createHmac("sha256", secret).update(body).digest("hex");
  try {
    return crypto.timingSafeEqual(Buffer.from(header), Buffer.from(expected));
  } catch {
    return false; // length mismatch
  }
}
verify.py
import hmac, hashlib

def verify_webhook(body: str, header: str, secret: str) -> bool:
    expected = "sha256=" + hmac.new(
        secret.encode(), body.encode(), hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(header, expected)
/ more

See it all working end-to-end

The Examples page has complete, copy-paste flows for every key type — Roblox Lua, Node.js middleware, Python license checks, and webhook handlers in four languages.

Browse examples