Documentation

Validating licenses

POST /v1/validate is the endpoint your product calls every time it launches (or on a timer). It's the hot path in KeyStack: HMAC-signed, Redis-cached for 15-30 s, indexed lookups, single-digit-ms p50.

Request

POST /v1/validate
Host: api.keystack.dev
Authorization: Bearer ak_live_4f2a...
X-KeyStack-Timestamp: 1731600000
X-KeyStack-Signature: 7b3d8a1f...
 
{
  "key": "KS-7F3K9-9XYLM-4N2A1-Q7C9F-WT2K",
  "fingerprint": "a3f9e1b84cf7-windows-amd64-7f"
}
FieldRequiredDescription
keyyesFull license key, dashes and casing tolerated.
fingerprintrecommendedStable per-machine identifier (UUID, hardware hash). Used to scope the response to a specific device.

The signature is HMAC-SHA256(secret, timestamp + "." + raw_body), hex-encoded. See Authentication for the full algorithm.

Response — valid

{
  "valid": true,
  "status": "ACTIVE",
  "licenseId": "ckxz1ab2c0000abcd",
  "issuedAt": "2026-01-04T09:12:11.000Z",
  "expiresAt": "2027-01-04T09:12:11.000Z",
  "plan": {
    "code": "lifetime-pro",
    "name": "Lifetime Pro",
    "features": { "exports_per_day": 50, "premium_themes": true }
  },
  "customer": { "id": "cust_42", "email": "alice@example.com" },
  "activations": { "used": 1, "max": 3 }
}

Response — invalid

{
  "valid": false,
  "status": "EXPIRED",
  "reason": "expired",
  "licenseId": "ckxz1ab2c0000abcd",
  "expiresAt": "2025-12-31T23:59:59.000Z",
  "plan": { "code": "annual-pro", "name": "Annual Pro", "features": {} },
  "customer": { "id": "cust_42", "email": "alice@example.com" }
}

Possible reason values:

reasonMeaning
not_foundKey doesn't exist in this application.
checksumHMAC checksum embedded in the key didn't match. The key is fake or corrupted.
frozenLicense is paused — usually a payment issue.
expiredPast expiresAt.
revokedHard-revoked, will never come back.

status is one of ACTIVE, FROZEN, EXPIRED, REVOKED, or INVALID (for checksum / not_found).

Caching

KeyStack returns Cache-Control: private, max-age=15. On the server side we cache the decision in Redis for 30 s (valid) or 5–10 s (invalid). That means:

  • If you call validate 1 000 times in 30 s for the same key, only the first one hits the database.
  • When you freeze, revoke or activate a device, KeyStack busts the cache automatically.