Error Codes
HTTP status and license status
HTTP status tells you whether the request was processed. The isValid field tells you whether to allow licensed functionality. Check both. These rules describe public validate and activate requests; admin endpoints have separate authentication requirements.
| Status | Meaning | Next step |
|---|---|---|
| 200 | License decision returned. Missing, revoked, expired, blocked, or exhausted licenses can return isValid: false. | Require isValid === true before unlocking features. |
| 400 | Invalid request or missing activation nonce. | Check fields and key format. Fetch a nonce before activation. |
| 422 | The supplied activation nonce is expired, invalid, or already used. | Fetch a fresh nonce. |
| 429 | An IP, tenant, or license abuse limit was reached. | Honor Retry-After when present and use bounded backoff. |
| 5xx / timeout | No reliable license decision is available. | Retry validation a limited number of times. Offline access requires a verified signed token. |
Error bodies are not always license response objects: request errors may be text or problem details, and a proxy may return HTML. Do not assume every non-200 response is JSON.
Machine-readable rejection reasons
Use errorCode when supplied. Keep message for display, not string matching. Some rejection paths return no error code, so retain a generic invalid-license branch.
| Code | Meaning | Action |
|---|---|---|
| NotFound | No active matching license | Check the key or contact the vendor. |
| ProductInactive | The product is inactive | Vendor checks product status. |
| Expired | Expiry and any post-expiry grace have elapsed | Renew the license. |
| VersionBlocked | App version is outside the configured range | Use an entitled version or update the license. |
| IpBlocked / RegionBlocked | Network restriction rejected the request | Check allowlists and the customer's network. |
| VendorSubscriptionLapsed | Vendor subscription needs attention | Vendor reviews Billing. |
| PoolExhausted | Tenant activation allowance exhausted | Review usage and plan capacity. |
| SeatsExhausted | No seats available on the key | Remove an unused device or increase the key's limit. |
A bounded validation request
This JavaScript example makes at most three attempts and never grants access on connection failure. After a valid response, check your product and feature entitlements. For signed offline caching, use an official SDK.
async function validateLicense(licenseKey) {
const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));
for (let attempt = 0; attempt < 3; attempt++) {
try {
const res = await fetch('https://api.permitcore.dev/api/v1/validate', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ licenseKey, version: '2.3.1' }),
signal: AbortSignal.timeout(10000)
});
if ((res.status === 429 || res.status >= 500) && attempt < 2) {
const header = res.headers.get('Retry-After');
const seconds = header === null ? NaN : Number(header);
const delay = Number.isFinite(seconds) ? seconds * 1000
: header ? Date.parse(header) - Date.now() : NaN;
if (Number.isFinite(delay) && delay > 30000)
return { isValid: false, unavailable: true, retryAfter: header };
await sleep(Number.isFinite(delay) ? Math.max(0, delay) : 1000 * 2 ** attempt);
continue;
}
if (!res.ok) return { isValid: false, httpStatus: res.status };
const data = await res.json();
if (typeof data?.isValid !== 'boolean')
return { isValid: false, unavailable: true };
return data;
} catch {
if (attempt < 2) { await sleep(1000 * 2 ** attempt); continue; }
}
}
return { isValid: false, unavailable: true, message: 'License service unavailable.' };
}Activation retries need extra care
A timeout does not prove activation failed: the server may have committed it before the connection was lost. Blindly retrying count-based activation can consume another seat. Persist successful installation state, use a stable device ID for node-locked licenses, and fetch a new nonce for each new request. Reconcile uncertain results instead of looping indefinitely.
Offline access is a separate decision
A network failure is not a valid license. Offline access requires a previously issued signed token, a trusted public key, a valid expiry, and matching license/device binding where applicable. A cached JSON success or local “activated” flag alone is insufficient. Do not replace an explicit online rejection with an older cached success.