HTTP API
The Swift SDK talks to three public endpoints. You can also call them directly if you’re integrating from a non-Swift platform.
All three:
- Are scoped
/<tenant-id>/<product-id>/<action>. - Require an
X-Keylight-SDK-Keyheader (see SDK key). - Accept and return JSON.
- Are hard-gated by your monthly API-call quota. When exceeded, Keylight returns
429with aRetry-Afterheader.
POST /:tenantId/:productId/activate
Section titled “POST /:tenantId/:productId/activate”Registers a new instance of a license and returns a signed Ed25519 lease. Called by the SDK the first time a license key is entered.
Headers
Section titled “Headers”| Header | Required | Value |
|---|---|---|
Content-Type | yes | application/json |
X-Keylight-SDK-Key | yes (once tenant has set one) | Full SDK key string |
Request body
Section titled “Request body”{ "license_key": "ACME-XXXX-XXXX-XXXX-XXXX", "instance_name": "Nicolas's MacBook Pro"}license_key- the raw license key the customer pasted in. Keylight normalizes it server-side.instance_name- a human-readable device label shown in the dashboard. The SDK sendsProcessInfo.processInfo.hostNameby default.
Success (200)
Section titled “Success (200)”{ "activated": true, "instance_id": "5f7e7b9a-...", "lease": { "kid": "primary", "licenseKeyHash": "4e7a...", "instanceId": "5f7e7b9a-...", "issuedAt": 1713546000, "expiresAt": 1714150800, "status": "active", "signature": "MEQCIB..." }}The instance_id is Keylight-issued - store it alongside the license key. You’ll send it back on every subsequent /validate or /deactivate call.
Error responses
Section titled “Error responses”| Status | Condition |
|---|---|
400 | Invalid JSON body |
401 | Missing or invalid X-Keylight-SDK-Key |
402 | Plan instance limit reached (hard cap) |
403 | Tenant lifecycle state does not allow activation (e.g. suspended, pending_verification) |
404 | Tenant / product / license key not found |
422 | Missing required field; license key expired |
429 | Monthly API-call quota exceeded (returns Retry-After header) |
If the same license key is re-activated from the same device, Keylight detects the reactivation and returns the existing instance_id without consuming a new activation slot.
POST /:tenantId/:productId/validate
Section titled “POST /:tenantId/:productId/validate”Refreshes a lease for an already-activated instance. Called periodically by the SDK.
Request body
Section titled “Request body”{ "license_key": "ACME-XXXX-XXXX-XXXX-XXXX", "instance_id": "5f7e7b9a-..."}Success (200)
Section titled “Success (200)”{ "valid": true, "lease": { "kid": "primary", "...": "..." }}License expired (422)
Section titled “License expired (422)”Unlike other errors, expiry returns a signed expired lease so the SDK can update local state immediately:
{ "valid": false, "error": "License expired", "lease": { "kid": "primary", "status": "expired", "...": "..." }}Other errors
Section titled “Other errors”| Status | Condition |
|---|---|
401 | Missing / invalid SDK key |
403 | Tenant lifecycle state blocks validation |
404 | Tenant / product / license key / license record not found |
422 | License revoked, instance deactivated, or instance not found |
429 | Quota exceeded |
Suspended tenants can still validate by design - already-active apps keep rotating leases through the grace window so end users aren’t disrupted. See Billing & plans.
POST /:tenantId/:productId/deactivate
Section titled “POST /:tenantId/:productId/deactivate”Releases an instance slot so the license can be activated on another device.
Request body
Section titled “Request body”{ "license_key": "ACME-XXXX-XXXX-XXXX-XXXX", "instance_id": "5f7e7b9a-..."}Success (200)
Section titled “Success (200)”{ "deactivated": true }Errors
Section titled “Errors”| Status | Condition |
|---|---|
401 | Missing / invalid SDK key |
404 | Tenant / product / license key not found |
422 | Instance already deactivated or not found |
Lease format
Section titled “Lease format”Every activate and validate response includes a lease object. The lease is Ed25519-signed by Keylight and verified offline by the SDK.
| Field | Type | Meaning |
|---|---|---|
kid | string | Key ID in your app’s trusted-keys map |
licenseKeyHash | hex string | SHA-256 of the normalized license key |
instanceId | UUID | The activation this lease is bound to |
issuedAt | unix seconds | When Keylight signed |
expiresAt | unix seconds | issuedAt + 7 days |
status | string | "active" or "expired" |
entitlements | string[] | Sorted feature strings granted to this instance (may be empty) |
signature | base64 | Ed25519 over v3|kid|keyHash|instanceId|issuedAt|expiresAt|status|entitlements_csv |
entitlements_csv is the entitlements array sorted and comma-joined (empty
string when there are no entitlements). Sorting is required so the client and
server reconstruct an identical payload regardless of the order in which
entitlements were added on either side.
Signature verification rules and the rationale for the 7-day TTL are covered in Ed25519 leases.
Rate limits and quotas
Section titled “Rate limits and quotas”- Monthly API calls - hard quota per plan;
429withRetry-Afteronce exceeded. Counters reset at the start of each calendar month. - Instance cap - hard per-plan cap on concurrent active instances.
/activatereturns402when exceeded. - License count - soft 100k ceiling per account; enforced at mint time, not at activation.
Plan quotas are listed on Billing & plans.