Validate
/{tenantId}/{productId}/validate Re-checks an activated instance and returns a fresh lease. The SDK calls this periodically and on launch. Uses the common headers & path parameters.
Request body
Section titled “Request body”| Field | Type | Required | Description |
|---|---|---|---|
license_key | string | yes | The license key |
instance_id | string | yes | The instance_id returned by activate |
app_version / sdk_version / platform / sdk | string | no | Telemetry. platform is the operating system (macos, windows, linux, ios, …), or the runtime (web, workers) where no OS applies; sdk is which SDK is reporting (swift, rust, js, csharp, cpp) |
curl -X POST https://api.keylight.dev/acme/notes/validate \ -H "Content-Type: application/json" \ -H "X-Keylight-SDK-Key: $KEYLIGHT_SDK_KEY" \ -d '{ "license_key": "ACME-XXXX-XXXX-XXXX-XXXX", "instance_id": "5f7e7b9a-1c2d-4e3f-9a8b-7c6d5e4f3a2b" }'// kl from the activate example; the instance_id is persisted by the SDKconst res = await kl.validate();// res.valid, res.leaseResponse (200)
Section titled “Response (200)”{ "valid": true, "license_expires_at": null, "lease": { "kid": "k1", "status": "active", "entitlements": ["pro"], "...": "…" }, "free_tier_enabled": true, "trial_duration_days": 14, "kid": "k1", "issued_at": 1788671279, "expires_at": 1788757679, "signature": "base64…"}A time-limited license past its expiry but configured to fall back (not hard-expire) returns 200 with valid: false and a lease whose status is "fallback".
Server-owned settings ride along
Section titled “Server-owned settings ride along”Every validate response — including the valid: false and expired ones — carries the settings your dashboard owns, so a licensed install learns them without a second request.
| Field | Type | Description |
|---|---|---|
free_tier_enabled | boolean | Always present. |
trial_duration_days | number | Absent when the tenant has never configured one — keep your own default. 0 means trials are off. Absent and 0 are different values, not one falsy case. |
kid / issued_at / expires_at / signature | The signature over the two settings above. Present whenever trial_duration_days is. |
The envelope is byte-identical to the one GET /config returns, and the rules for checking it are the same — verify the signature, including the one that matters most: a present trial_duration_days must carry a valid signature, or you ignore it. Verify-if-present is not a check.
Note the two signatures in this response are independent and cover different things: the lease is signed over entitlement, and this envelope is signed over the settings. Neither stands in for the other.
License expired (422)
Section titled “License expired (422)”A hard-expired license returns a signed expired lease so the SDK can update local state at once:
{ "valid": false, "error": "License expired", "lease": { "kid": "k1", "status": "expired", "...": "…" }}Errors
Section titled “Errors”| Status | Condition |
|---|---|
400 | Invalid JSON body |
401 | Missing / invalid SDK key |
403 | Tenant state blocks validation |
404 | Tenant / product / license not found |
422 | License revoked, or instance not found / deactivated |
429 | Quota exceeded |
500 | Tenant not fully configured |
Suspended tenants can still validate by design — active apps keep rotating leases through the grace window so end users aren’t disrupted.