Skip to main content
Variables let you store configuration, feature flags, or runtime data in the cloud instead of hardcoding it into your binary. Two scopes exist:
  • App variables — set by the operator in the dashboard, read-only from the SDK. Encrypted at rest with AES-256-GCM keyed per product.
  • User variables — per-account key-value pairs, stored in plaintext. The SDK can read and write them unless the operator marked a variable as readonly (server-writable only, for things like credit balances or ranks).
All routes require a valid SDK session token (Authorization: Bearer <token> header, token body field, or ?token= query on GET). The JS SDK handles this — variable(), listVars(), getVar(), and setVar() need an active session first, otherwise the SDK throws NOT_ACTIVATED before any request is sent:
variable() and getVar() unwrap to the raw value (just the string). listVars() returns the { key, value, readonly } array. setVar() returns the raw response data ({ key, value } — no readonly echo).

Get App Variable

Reads an operator-defined application variable.

Parameters

Responses

Behavior

  • Version gate: if the variable has a minVersion and the device’s appVersion is older, access is refused with UPDATE_REQUIRED.
  • Tier gate: if the variable has a requiredTier that doesn’t match the license’s tier (case-insensitive), access is refused with TIER_REQUIRED. There is no minVersion/requiredTier comparison beyond that — exact tier match, nothing hierarchical.
  • Decryption failure: if the master encryption key was rotated and the ciphertext is unreadable, the server answers 500 Variable unreadable (not 404) — recoverable by the operator re-saving the value.

List User Variables

Returns all variables stored for the authenticated end-user account.
Requires an AppUser-linked session. License-only sessions are refused with USER_NOT_FOUND (401).

Responses


Get User Variable

Reads a single user variable by key.

Parameters

Responses

Note the 200 shape carries readonly too — the list shape and the single-get shape match. There is no key-format validation on these routes — a missing variable and a malformed key give the same 404. No 400 exists here.

Set User Variable

Creates or updates a user variable. If the variable already exists and is marked readonly by the operator, the write is refused.

Request

Parameters

Responses

The write response is { key, value } only — no readonly echo. The only 400 is an empty key after slicing — the value is never missing-required, it’s coerced (String(value ?? ""), sliced to 2,048) and upserted.

Behavior

  • Readonly check: if the variable exists and readonly is true, the write is refused with VARIABLE_READONLY. This is how operators store values the app may read but must not forge (ranks, credit balances, flags). A missing variable is always writable — readonly can only block, never pre-exist.
  • No per-user cap: there is no variable-count limit — no USER_VARIABLE_MAX, no FORBIDDEN. Create freely.
  • Upsert semantics: if the key doesn’t exist it’s created; if it does, the value is overwritten.
User variable values are stored in plaintext, unlike app variables which are encrypted. Don’t store secrets here.

Rate Limits

All four routes share one limiter, plus the usual guards: No separate read/write limits — GETs and the PUT share the 120/min bucket. The limiter answers Too many requests, slow down with errorCode: "RATE_LIMITED"; the abuse guard adds retryAfterSeconds. Signed responses (v2 Ed25519 scheme) and requireSignedRequests freshness behave exactly as on login.

Errors

Operators can reword any message per product — match on errorCode, never on message text.