Skip to content

Keyless

POST /{tenantId}/{productId}/keyless

Anonymous state beacon for devices running without an active license — trial, free tier, or expired. Uses the common headers & path parameters.

FieldTypeRequiredDescription
instance_idstringyesA stable anonymous device id (≤128 chars)
statestringno"trial", "free_tier", or "expired" (default "free_tier")
app_version / sdk_version / platform / sdkstringnoTelemetry — recorded per device and shown on the dashboard’s Free-tier devices table. 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). sdk_version/platform/sdk are sent automatically by the SDKs; app_version is included only if you set it (e.g. the Rust SDK’s .app_version(…)).
Terminal window
curl -X POST https://api.keylight.dev/acme/notes/keyless \
-H "Content-Type: application/json" \
-H "X-Keylight-SDK-Key: $KEYLIGHT_SDK_KEY" \
-d '{ "instance_id": "device-abc123", "state": "trial" }'
{
"received": true,
"free_tier_enabled": true,
"trial_duration_days": 14,
"kid": "k1",
"issued_at": 1788671279,
"expires_at": 1788757679,
"signature": "base64…",
"trial_started_at": 1788671279,
"trial_already_used": false
}

The beacon is how an unlicensed device learns the settings your dashboard owns — it is the keyless counterpart to the settings that ride validate for licensed ones.

FieldTypeDescription
receivedbooleanAlways true on success.
free_tier_enabledbooleanAlways present.
trial_duration_daysnumberAbsent when the tenant has never configured one — keep your own default. 0 means trials are off. Absent and 0 are different values.
kid / issued_at / expires_at / signatureThe signature over the two settings above. Present whenever trial_duration_days is. Byte-identical to the GET /config envelope — verify it the same way. It covers the settings only; the per-device trial_started_at and trial_already_used are outside it.
trial_started_atnumberUnix seconds. When this device first started a trial for this product, server-side. Never moves, so a device that wiped its local store gets its original date back rather than today’s.
trial_already_usedbooleantrue only when this device spent a trial that has since run out.

trial_started_at and trial_already_used are both absent unless the server has a live ledger entry for this device and this beacon was the one that refreshed it. They are absent when you send no machine_hash, when the device has never trialled, when its entry has aged out of the 18-month window, and on beacons the server answers without a database read — the write is gated so cadence can rise without cost.

So absence means no opinion, not false. Leave your cached values alone when they do not arrive; do not read a missing trial_already_used as permission, or a missing trial_started_at as “starts now”.

trial_already_used is deliberately hard to make true. A device inside its first trial, one with no machine_hash, one whose entry expired, and — importantly — any device behind a machine_hash shared by an implausible number of installs all answer false. That last case is cloned VM images and imaged laptop fleets, which share a machine id: an unusual number of installs raises an operator alert and grants, never locks out.

A wrongly granted second trial costs one trial’s usage. A wrongly denied one costs a customer, and denials land on legitimate users in bulk. The asymmetry is the design.

Same as Free tier (400, 401, 404, 429).