SDK Register
owner/product pair. After registration the user is signed in immediately: gate checks, device binding, and session issuance all happen in the same request.
This is not the operator console registration. This creates customer accounts that sign into your application through your software.
Request
hwid, os, appVersion, components, and virtual for you:
client.register(key, user, pass, { hash, label: "Alex's desktop" }). There is no twofa option here — accounts are created without 2FA.
Parameters
key, hwid, and os are capped at 256 characters.
Responses
data: an offline assertion (cached by the SDK for checkOffline()) and a soft product.updateAvailable hint with latest and optional downloadUrl when autoUpdate is on and your build is behind. session is omitted when the operator’s plan has no token system.
Behavior
- The license key is looked up by its hash. An unknown key answers
LICENSE_NOT_FOUND(401) and writes nothing to the audit log — an unauthenticated caller must not be able to fill another account’s trail with noise. It also adds abuse weight against the caller’s IP. - A key already linked to another user answers
LICENSE_IN_USE(409, not 403). Two registrations racing on one key cannot both attach: the claim check and the link happen inside the same guarded flow. - Username uniqueness is per-product (
@@unique([productId, username])), not global. The same username can exist on different products. A taken name and a malformed name give the same answer: 400USERNAME_TAKEN(Username is taken or invalid) — there is no separateINVALID_USERNAMEcode. - A missing/short password answers 400
WEAK_PASSWORD. A missingkey/hwidanswers 400MISSING_FIELDS. - After the account is created, the full activation flow runs: gates (kill switch, license state, blacklist, platform, version, build hash, VM block), device binding, and session token issuance. Gate failures get their own accurate code from
runGates. - There is no per-plan account cap on this route — no
USER_LIMIT_REACHEDcode exists server-side. - Session tokens are only returned when the operator’s plan includes the token system. Without it, the response omits the
sessionfield. - Audit trail: successful registrations write a
LicenseEvent(account <user> registered) and emit anactivation.succeededwebhook — the same event as a key activation, withuserattached. Nouser.registeredevent exists. - SDK result:
register()stores the session/license/device/account/subscriptions and emitsregistered, thenactivated.client.snapshot()returns{ app, license, product, device, user, account, subscriptions, valid }.
Signed Responses
Every response from this endpoint carries Ed25519 signature headers when a signing key is configured (v2 scheme):
The SDK verifies all of this automatically and throws
FrostAuthError (carrying the server’s errorCode as code, plus status and requestId) on failure. Unsigned 429s from the rate limiter are treated as retryable, not as forgeries.
When requireSignedRequests is enabled for the product, inbound freshness is enforced before the handler runs: timestamp within 60 seconds, nonce 16–128 chars and unique (Redis SET NX, 180s TTL). Stale requests get SESSION_EXPIRED, replays get FORBIDDEN.
Rate Limits
The register limiter’s own answer (
Too many registration attempts, slow down) carries no errorCode. The IP floor answers with errorCode: "RATE_LIMITED"; the abuse guard adds retryAfterSeconds.
Errors
Operators can reword any message per product — match onerrorCode, never on message text.
