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"
}| Field | Required | Description |
|---|---|---|
key | yes | Full license key, dashes and casing tolerated. |
fingerprint | recommended | Stable 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:
reason | Meaning |
|---|---|
not_found | Key doesn't exist in this application. |
checksum | HMAC checksum embedded in the key didn't match. The key is fake or corrupted. |
frozen | License is paused — usually a payment issue. |
expired | Past expiresAt. |
revoked | Hard-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.