Kubian Integrity Documentation
Getting Started · 01

Kubian Integrity

Kubian Integrity is an enterprise-grade platform integrity service for Android applications and VR headsets. It combines hardware-backed attestation (TEE and X.509), application binary verification, and real-time environment protection into a single pipeline you can trust before letting a player into your game.

What Kubian Integrity verifies

Every verdict is produced server-side by Static Labs infrastructure. The client never reports its own integrity — it only presents cryptographic proof the server validates. All tokens are encrypted with AXE (Abstract Xpress Encryption) to prevent reverse engineering.

Hardware

Hardware Verification · TEE / X.509

Attestation keys anchored to Android StrongBox or a Trusted Execution Environment (TEE), presented as a signed X.509 certificate chain rooted at Google Root Certificates.

Application

Application Verification

The package identifier and binary SHA-256 digest are bound into the attestation and cross-checked against the values registered for your project.

Real-time

Real-time Environment Protection

Continuous detection of root (soft and hard), Frida, Magisk, code injection, debuggers, and anti-debug bypasses — with configurable alert, kick, and ban actions.

Getting Started · 02

Hardware Verification (TEE / X.509)

Attestation keys are generated inside Android StrongBox or a Trusted Execution Environment and presented as a signed X.509 certificate chain rooted at Google Root Certificates. Here is how Kubian validates the hardware state.

When a device runs an attestation flow, Kubian's native layer generates an Elliptic Curve key pair inside the Android KeyStore. On supported devices (Android 9+) the key is generated in StrongBox — a physically isolated secure chip. Otherwise the standard TEE (Trusted Execution Environment) is enforced.

The resulting key is presented as an X.509 certificate chain signed by hardware. The leaf certificate carries the Android Key Attestation ASN.1 extension, which reports hardware-guaranteed values: security level, verified boot state, bootloader lock status, OS patch level, OS version, and the application certificate digest.

On the server, Kubian:

  • Validates the full certificate chain signature by-link, verifying each intermediate against its issuer.
  • Checks the root certificate against trusted Google Root Certificates (GRCs) and known VR hardware keys.
  • Parses the raw ASN.1 attestation extension to read the hardware-reported device state.
  • Enforces a minimum OS security patch level (January 2026).
  • Cross-references intermediate certificates against the hardware revocation list (CRL).
  • Requires verified boot to be Trusted and the bootloader to be locked.

Security levels

Level Backing Description
Advanced StrongBox Key isolated in a separate, highly secure hardware chip.
Basic TEE Backed by the main processor's secure enclave. Secure.
NotTrusted Software Generated at software level — a common sign the device or application is compromised.

Verified boot states

State Description
Trusted The OS is official with no immediate threats reported by hardware.
Unofficial The OS has been replaced and is most likely compromised.
NotTrusted The OS has been modified and re-signed by an unknown publisher.
Compromised The OS has been compromised and its security stripped away.
Unknown Kubian could not find information on the OS due to missing or corrupt attestation data.

Device integrity state

Kubian folds the raw hardware signals — security level, verified boot state, bootloader lock status, and the device's Widevine security level — into a single computed string enum carried as the top-level device_integrity_state field of the integrity token. Your backend does not need to interpret the individual signals; the computed state is the verdict.

State Condition Meaning
strong_integrity Bootloader locked, verified boot Trusted, security level Advanced/StrongBox, Widevine security level L1. Key isolated in a dedicated hardware security chip and DRM content decoded in secure hardware. The highest integrity state.
tee_integrity Bootloader locked, verified boot Trusted, security level Basic/TEE, Widevine security level L1. Backed by the main processor's secure enclave (TEE) with hardware-backed Widevine. Secure.
simple_tee_integrity All boot and security conditions passed, but Widevine reported L2 or L3 instead of L1. StrongBox devices are demoted to this state too, since a dedicated security chip should never report L1. The device itself looks genuine, but Widevine could not confirm hardware-level DRM. Rejected unless "Allow Simple Integrity" is enabled.
simple_integrity All boot and security conditions passed, but no Widevine information was available (null/Unknown). Genuine device with unverifiable Widevine state. Rejected unless "Allow Simple Integrity" is enabled.
unmet_integrity Bootloader unlocked, verified boot not Trusted, an unrecognized security level, or the attestation itself reported the device as compromised. The device is not running a genuine, untampered OS. Verification fails.
Computed server-side The device_integrity_state enum is derived on Kubian's backend from the hardware-attested signals — it is never reported by the client. A device that fails any boot or security condition is always folded into unmet_integrity.
Simple integrity states are opt-in By default Kubian rejects simple_tee_integrity and simple_integrity during verify_token. Project owners can enable the "Allow Simple Integrity" toggle under Device checks in API Settings to accept them. Note that the minimum Security Level still applies: with "TEE integrity and above", enabled simple states must be at least simple_tee_integrity, and "Strong integrity only" never accepts simple states. When simple states apply, the version check runs before any other verification step.
Getting Started · 03

Application Verification

Kubian verifies not only the hardware but also the application running on it. The package identifier and binary digest are bound into the attestation and cross-checked against your project registration.

The attestation extension binds the application's certificate digest and package identifier into the certificate chain. The server decodes these values and produces a recognition verdict for each:

  • Application binary: the SHA-256 digest of your application is compared to the application-sha256 registered on your project.
  • Package identifier: the package name reported by hardware is compared to the project_identifier registered on your project.
  • Recognition results: AppRecognized / AppNotRecognized and AppIdRecognized / AppIdNotRecognized.

A package identifier is fixed at build time and cannot change without rebuilding the app, so any mismatch is treated as evidence of tampering or an unregistered build.

Kubian Integrity AC · 42

Real-time Environment Protection

Beyond one-shot attestation, Kubian continuously monitors the running environment with a high-frequency scan loop, certificate-authenticated telemetry, and server-side enforcement decisions.

Architecture overview

When KubianCore.Init() is called, the native library spawns a detached monitor thread that executes a continuous scan loop at a configurable interval (default: 2000 ms). Each iteration performs the following in order:

  1. Hard scan — Frida (5-layer + behavioral + patched-binary + fd-leak), Widevine hook detection, injection, root (hard), root (soft), debugger detection.
  2. Soft scan — SELinux status, build properties, emulator indicators, busybox, Quest headlock abuse.
  3. Ban poll (every 10 s) — queries the server for the device's current ban status. If a ban is issued after protection started, the player is kicked immediately.
  4. Tier recheck (every 60 s) — re-fetches the protection tier from the server. If the tier drops from Max, realtime checks are disabled and only the ban poll continues.

New detections are compared against the previous iteration. A detection that matches the previous result is suppressed — only new or changed detections are reported to the server and acted upon.

Anti-debug armament

Before the scan loop begins, the library arms an anti-attach defense. On aarch64, this is a raw svc #0 syscall for ptrace(PTRACE_TRACEME), which sets the kernel's traced flag — any subsequent PTRACE_ATTACH or PTRACE_SEIZE from another process is rejected by the kernel. This is followed by prctl(PR_SET_DUMPABLE, 0) to disable core dumps and prevent /proc/[pid]/mem reads. The same pattern is used on arm32 and x86_64 with their respective syscall numbers. All three architectures use raw syscall instructions that bypass userland function hooks.

Detection checks

Frida detection (frida)

Five-layer Frida detection:

  1. Scans /proc/self/maps for 12 Frida library signatures: frida, gum-js, gadget, linjector, frida-agent, libgadget, frida-inject, frida-gadget, gmain, gdbus, frida-server, re.frida.
  2. Detects non-system RWX memory segments — Frida injects its agent as executable code.
  3. Reads /proc/self/environ for FRIDA_ environment variables that leak Frida's presence.
  4. Probes 17 filesystem paths including /data/local/tmp/, /data/adb/, system paths, and renamed variants (fs-server, .frida).
  5. Checks /proc/net/tcp and /proc/net/tcp6 for default ports 27042/27043, and /proc/self/cmdline for frida in arguments.

Injection detection (injection)

Two-pronged injection detection:

  1. Scans /proc/self/maps for 36 framework signatures: substrate, xposed, riru, zygisk, lsposed, edxposed, shadowhook, dobby, libwhale, sandhook, shizuku, kernelsu, ksud, riru_core, zygisk_next, liblsplant, libepoxy, spirit, hideroot, magiskhide, blink, libmemtraces, frida_gadget, libfrida, libxposed_art, libnativehook, liblspd, libhpicore, libjiagu, libDexHelper, libexecmain, libchaosvmp, libjiagu_art, libtuplus, libprotect, libapp_shield.
  2. Detects executable r-xp mappings from /data/local/tmp/ (injected native payloads) and RWX segments in /data/ excluding /data/app/ and /data/dalvik-cache/.

Root — hard (root_hard)

Six-pronged hard root detection. A single hard signal is conclusive.

  1. Filesystem probe: checks 47 known su/root paths including Magisk, KernelSU, APatch, ZygiskSU, Shamiko, LSPosed, TrickyStore, and init .rc scripts.
  2. UID check: calls getuid() — if the process runs as uid 0, it has root.
  3. Mount parsing: reads /proc/self/mountinfo for magisk, KSU, kernelsu, /data/adb, ksu, zygisk, riru mount entries.
  4. Hidden root in maps: scans /proc/self/maps for 12 hidden root module libraries (zygisk, riru, lsposed, magisk, kernelsu, ksud, magiskhide, shamiko, zygisk_next, riru_core, liblsposed, lspd).
  5. Command-line: reads /proc/self/cmdline for magisk or ksu in process arguments.
  6. Module enumeration: enumerates /data/adb/modules/ via raw getdents syscall looking for su binaries inside module directories.

Root — soft (root_soft)

Multi-layered soft root and emulator detection:

  1. Reads /sys/fs/selinux/enforce0 means permissive.
  2. Checks ro.build.tags for test-keys and dev-keys.
  3. Checks ro.secure for 0 (insecure boot).
  4. Checks ro.debuggable for 1.
  5. Checks ro.build.type for eng and userdebug.
  6. Checks ro.adb.secure for 0 (unauthorized adb).
  7. Reads ro.boot.selinux for permissive.
  8. Full emulator detection: 15 emulator keywords (goldfish, ranchu, bluestacks, nox, ldplayer, memu, genymotion, etc.) checked against 6 property names.
  9. Filesystem checks: /dev/socket/qemud, /dev/qemu_pipe, /system/lib/libc_malloc_debug_qemu.so, /sys/qemu_trace.
  10. Reads /proc/cpuinfo for emulator CPU signatures.
  11. Checks /proc/tty/drivers for goldfish.
  12. Detects busybox in maps and filesystem.

Debugger detection (debugger)

Four-layer debugger detection:

  1. Reads /proc/self/status and parses TracerPid — non-zero means a debugger is attached via ptrace.
  2. Enumerates ALL threads in /proc/self/task/ via raw getdents syscall and checks TracerPid for each thread — catches debuggers that attach to individual threads rather than the main process.
  3. Scans /proc/self/maps for 13 debugger artifacts: liblldb.so, libgdbserver.so, android_server, android_server64, android_server32, libthread_db.so, libpython, JDWP, jdwp, libptrace, idaq, id64, x64dbg.
  4. Reads /proc/self/wchan for ptrace and n_tracerpid kernel wait channel strings.

Anti-debug trap (antidebug)

Arm the ptrace/seccomp trap that blocks debugger attachment. This check is not a detection — it is the defensive armament itself. If the anti-debug armament has been bypassed or stripped, this check fires as a detection signal.

Headlock abuse (headlock_abuse)

Quest-specific detection for head-lock hijacking used to bypass guardian boundaries or perform injection via the head-tracking subsystem:

  1. Reads the debug.oculus.headlock system property — values 1 (active) or 3 (hijacked) indicate headlock abuse is engaged.
  2. Reads debug.oculus.headlock.translation.z to measure the Z-axis displacement — values outside ±10.0 indicate the headset is being forcibly repositioned to bypass spatial restrictions.

Frida behavioral detection (frida_behavioral)

Memory-mapping anomaly detection that catches patched Frida builds:

  1. Counts anonymous r-xp (read-execute) memory mappings in /proc/self/maps. Normal Android apps use r--p for anonymous memory; r-xp indicates code injection.
  2. Triggers if the count exceeds 3 anonymous r-xp segments — catches Frida's Gum code allocator pages even when --strict-wx is active and library names are stripped.

This layer defeats frida-bin-patcher and similar tools that rename binary symbols and strip debug info but cannot eliminate the anonymous executable mappings required for code injection.

Frida patched-binary detection (frida_module)

Module name and command-line scanning for renamed/patched Frida builds:

  1. Scans /proc/self/maps for 5 patched-binary signatures: florida, oemcodec, sysmon, libflorida, liboemcodec.
  2. Scans /proc/[pid]/cmdline across all processes for com.android.systemui disguised Frida servers — catches MagiskHluda/Florida and phantom-frida that masquerade as system processes.

This layer defeats tools that rename frida-server to florida, oemcodec, or sysmon and launch it under a system UI process name.

Frida fd-leak detection (frida_memfd)

File descriptor symlink analysis that catches in-memory Frida injection:

  1. Enumerates /proc/self/fd via raw getdents64 syscall.
  2. Reads each symlink target via raw readlinkat syscall (bypasses userland hooks).
  3. Triggers if any fd symlink contains frida or linjector, or if pipe targets match the pipe-XXXXXX pattern used by Frida's inter-thread communication.

This layer defeats undetected-frida and other approaches that load Frida via memfd_create() — the injection leaves file descriptors that cannot be hidden from /proc/self/fd enumeration.

Widevine hook detection (widevine_hook)

Four-layer Widevine integrity detection:

  1. Format validation: verifies widevine_id is a 64-character hex string — rejects synthetic, replayed, or malformed IDs.
  2. Entropy check: counts unique hex characters — flags low-entropy IDs that indicate brute-forced or generated values.
  3. Consistency check: re-fetches Widevine ID every 5 scan cycles and compares with cached value — catches runtime hooks returning spoofed values.
  4. GOT/PLT hook detection: scans /proc/self/maps for RWX permissions in libmediadrmbase.so, libmedia.so, libutils.so, libmediautils.so, and libmediandk.so — detects inline hooks on MediaDrm JNI functions used to intercept or modify Widevine ID retrieval.

Default actions

Check Detects Default action
frida Frida instrumentation framework Alert
injection Code / library injection into the process Alert
root_hard Persistent / privileged root (e.g. Magisk full install) Ban
root_soft Soft or hidden root indicators, emulator environment Kick
debugger Attached debuggers (ptrace, JDWP, LLDB) Kick
antidebug Anti-debug bypass attempts Alert
headlock_abuse Quest headlock hijacking for spatial bypass Kick
frida_behavioral Anonymous executable memory (patched Frida builds) Kick
frida_module Renamed/patched Frida binaries (florida, oemcodec, sysmon) Kick
frida_memfd In-memory Frida injection via fd leaks Kick
widevine_hook MediaDrm function hooking, invalid/tampered Widevine IDs Ban

Available actions are alert, kick, and ban. The set of checks and their actions are configurable per project in the Kubian dashboard (Max tier).

Ban enforcement

Every 10 seconds the monitor thread queries the server for the device's ban status. The server validates the device certificate, checks the ban database, and returns the current state.

  • If the device was not banned when protection started and a ban is detected during the session, the player is kicked immediately and the server records a REALTIME_BANNED_WHILE_PLAYING audit event.
  • If the device was already banned when protection started, the ban poll does not kick — this prevents repeated kicks on rejoin. The developer's attestation backend is expected to handle the ban at login time instead.

Tier recheck

Every 60 seconds the library re-fetches the protection tier from the server. If the tier drops from Max to a lower tier, realtime detection checks are disabled and only the ban poll continues. The server signs the tier response with RSA; the client rejects any unsigned or tampered response and falls back to full Max-tier protection.

Server-side enforcement

Detection logic runs on the client, but the enforcement decision is made server-side and cannot be forged. When a detection fires, the library sends a telemetry report to the server (authenticated via the device certificate). The server evaluates the configured action for that check and responds with a directive: note (log only), kick (remove from session), or an executed ban (permanent device-level restriction).

Security note Realtime protection is a runtime environment monitor, not a game logic protector. As the developer, you remain responsible for implementing proper backend verification — such as strict nonce validation — to prevent cheating and value manipulation. The protection ensures the runtime environment is trustworthy; your backend ensures the data flowing through it is valid.
Getting Started · 04

Token Encryption — AXE

Every challenge token and integrity token issued by Kubian is wrapped in AXE (Abstract Xpress Encryption) — a lightweight, server-side encryption layer that prevents clients and intermediaries from reading or reverse-engineering token contents.

Why AXE exists

Without AXE, integrity tokens are signed JWTs — base64-encoded JSON that anyone can decode and read. Attackers who inspect token payloads can study field names, claim structures, and attestation details to find weaknesses or craft targeted bypasses.

AXE solves this by encrypting the entire token before it leaves the server. The result is an opaque blob that only the server can decrypt and verify. The client sees AXE.{encrypted}.{signature} — no payload fields, no structure, no attack surface.

How AXE works

AXE combines two NIST-standard primitives into a single operation:

  • AES-256-GCM — authenticated encryption that provides confidentiality and integrity in one pass. Each token gets a unique 12-byte nonce, making identical payloads produce different ciphertexts every time.
  • HMAC-SHA256 — a separate MAC key verifies the encrypted blob hasn't been tampered with before decryption is attempted. This prevents chosen-ciphertext attacks on the AES-GCM layer.

Both keys are derived from a single 256-bit server key using SHA-256 key derivation. The AES key and MAC key are never the same value, even though they originate from the same source.

Token format

AXE Token Structure
AXE.{base64url(nonce + AES-GCM-ciphertext + tag)}.{base64url(HMAC-SHA256-signature)}
Component Size Description
AXE. 4 bytes Prefix identifying the token as AXE-encrypted.
Encrypted blob Variable 12-byte nonce + AES-256-GCM ciphertext + 16-byte authentication tag, encoded in a custom base64 alphabet.
HMAC signature ~43 bytes SHA-256 MAC over the entire encrypted blob, encoded in URL-safe base64.

Seal (encrypt)

Server-side
axe_token = AXE.seal(payload_dict, server_key)

The payload dictionary is serialized to compact JSON, encrypted with AES-256-GCM using a random nonce, and signed with HMAC-SHA256. The result is the AXE.{blob}.{sig} string returned to the client.

Open (decrypt)

Server-side
payload_dict = AXE.open(axe_token, server_key)

The HMAC signature is verified first. If it fails, the token is rejected immediately without attempting decryption. If it passes, the encrypted blob is decrypted with AES-256-GCM and the resulting JSON is parsed back into a dictionary.

How AXE secures the integrity flow

The attestation pipeline uses AXE as an outer encryption shell around every token:

Endpoint What happens
get-challenge-token The server creates a signed JWT challenge token, then wraps it in AXE before returning it to the client. The client receives an opaque AXE.{...} string.
get-integrity-token The server decrypts the AXE-wrapped challenge token, extracts and verifies the inner JWT, then creates a new signed integrity JWT — and wraps that in AXE before returning it.
verify_token Your backend forwards the AXE token to Kubian. The server decrypts the AXE layer, extracts the inner JWT, verifies the PS256 signature, and evaluates the attestation claims.
Two layers of protection AXE provides encryption (confidentiality) while the inner JWT provides signing (authentication). Even if an attacker could somehow strip the AXE layer, they still cannot forge a valid PS256-signed JWT without the project's RSA private key.

What AXE prevents

  • Payload inspection — clients and intermediaries cannot read token fields, claim names, or attestation data. The only visible text is the opaque AXE. prefix.
  • Token replay with modification — any change to the encrypted blob invalidates both the AES-GCM authentication tag and the HMAC signature.
  • Cross-protocol analysis — different server keys produce completely different ciphertexts for identical payloads, making cross-project token analysis impossible.
  • Structural reverse-engineering — attackers cannot learn field names, data types, or claim structure from intercepted tokens.

Developer impact

None. The AXE encryption and decryption are handled entirely by the Kubian SDK and server infrastructure. Your client code receives and forwards opaque AXE.{...} strings exactly as it would any other token. Your backend calls verify_token with the AXE token in the request body — Kubian handles the decryption internally.

Attestation API · 05

Overview & Authentication

Technical reference for the attestation endpoints: base URL, authentication headers, request payloads, and the complete JSON response schemas produced by verify_token.

Base URL

API Base
https://api.kubian.app

All endpoints require JSON request bodies and expect the Content-Type: application/json header. Responses are always JSON.

Authentication — ACT (Access Content Token)

Every attestation and ban API call is gated by an ACTAccess Content Token for Applications and User Accounts. It is not a JWT; it is a structured credential string sent in the access-token header:

Header
access-token: KB|{project-app-id}|{project-app-secret}
  • Format validation: the header must begin with KB| and split into exactly three pipe-delimited segments.
  • Project lookup: the parsed app-id / app-secret pair is matched against the ACT database.
  • Standing check: suspended or payment-paused projects are rejected before any attestation logic runs.
Keep secrets server-side Never embed your app-secret in the Unity client or any on-device code. All API calls must originate from your secure backend.

ACT error reference

Error code HTTP Cause
ACT_Token_Missing 400 The access-token header was not present.
ACT_Token_Format_Invalid 400 The header did not split into exactly three segments or did not start with KB.
ACT_Token_Invalid_ID 404 The app-id / app-secret pair did not match any project.
Attestation API · 06

Verify Integrity Token

Verifies an AXE-encrypted attestation token generated on a device. This is the endpoint your backend calls to obtain a device verdict. Tokens are single-use and expire after 10 minutes.

Endpoint

POST  Verify Integrity Token
https://api.kubian.app/platform_integrity/verify_token

Required headers

Header Value Description
access-token KB|{app-id}|{app-secret} ACT credential for your project.
Content-Type application/json Must be set to JSON.

JSON body

Field Type Description
token string The AXE-encrypted attestation token passed from the client to your backend, then forwarded here. The server decrypts the AXE layer internally and verifies the inner JWT signature.
Request body
{
  "token": "AXE.tJZxIceqye1-DYr4vHuX221ZMiOOC2ngieUhZdc42Ke..."
}

Success response

On successful verification the response carries success: true with tee_integrity, strongbox_integrity, app_integrity, and a token field containing the AXE-decrypted inner JWT. See Success Responses for the full reference.

token field Every response — success or failure — includes a token field with the raw AXE-decrypted inner JWT (not decoded, not re-signed). This is useful for debugging, logging, or forwarding to downstream services.
Attestation API · 07

Verify Integrity Token — Success Responses

When verification passes all checks the response carries success: true with three integrity fields that indicate the device's hardware attestation tier and application verification state.

TEE-backed device

200 OK
{
  "success": true,
  "tee_integrity": "MEETS_TEE_INTEGRITY",
  "strongbox_integrity": "UNSUPPORTED",
  "app_integrity": "MEETS_APP_INTEGRITY",
  "nonce": "Your-Secure-Server-Nonce",
  "unique_id": "device-unique-id",
  "token": "eyJhbGciOiJQUzI1NiIs..."
}

StrongBox-backed device

200 OK
{
  "success": true,
  "tee_integrity": "MEETS_TEE_INTEGRITY",
  "strongbox_integrity": "MEETS_STRONGBOX_INTEGRITY",
  "app_integrity": "MEETS_APP_INTEGRITY",
  "nonce": "Your-Secure-Server-Nonce",
  "unique_id": "device-unique-id",
  "token": "eyJhbGciOiJQUzI1NiIs..."
}
Field Values Meaning
tee_integrity MEETS_TEE_INTEGRITY Bootloader locked, verified boot trusted, TEE-backed attestation passed.
strongbox_integrity MEETS_STRONGBOX_INTEGRITY | UNSUPPORTED MEETS_STRONGBOX_INTEGRITY when the device has a StrongBox security chip (Advanced security level). UNSUPPORTED when only TEE is available (Basic security level).
app_integrity MEETS_APP_INTEGRITY Application SHA-256 and package identifier match registered values.
token string The AXE-decrypted inner JWT. This is the raw signed JWT extracted from the AXE envelope — not decoded, not re-signed. Useful for debugging, logging, or passing to downstream services.
nonce echoes the nonce your server issued for the session. You must assert it exactly matches the nonce you generated; otherwise reject the client immediately.
Tiered enforcement Use strongbox_integrity to apply different policies per security tier. For example, MEETS_STRONGBOX_INTEGRITY may unlock higher-value in-game actions while UNSUPPORTED permits standard gameplay.
Attestation API · 08

Verify Integrity Token — Failure Responses

Every failure from verify_token returns a structured JSON body. Map the returned status to the appropriate client handling with the reference below.

Scenario HTTP Response body
Request Validation
Missing JSON body 400 {"success": false, "status": "BAD_REQUEST", "message": "Invalid request. Missing 'json' body."}
Missing token in body 400 {"success": false, "status": "BAD_REQUEST", "message": "Invalid request. Missing 'token' in json body."}
Device provided no application information 400 {"success": false, "status": "FAILURE_ERROR: DEVICE DID NOT PROVIDE APPLICATION INFORMATION", "unique_id": "device-unique-id", "token": "eyJhbGciOiJQUzI1NiIs..."}
Authentication (ACT)
Missing access-token header (ACT) 400 {"success": false, "status": "BAD_REQUEST", "db": "Access Content Token - Failed", "message": "Unable to verify your Kubian 'access-token'. Error: ACT_Token V2.0 - ACT_Token_Missing"}
Malformed ACT 400 {"success": false, "status": "BAD_REQUEST", "db": "Access Content Token - Failed", "message": "Unable to verify your Kubian 'access-token'. Error: ACT_Token V2.0 - ACT_Token_Format_Invalid"}
Unknown project 404 {"success": false, "status": "UNKNOWN_PROJECT", "db": "Access Content Token - Failed", "message": "Unable to verify your Kubian 'access-token'. Error: ACT_Token V2.0 - ACT_Token_Invalid_ID"}
Token Validation
Token expired 401 {"success": false, "status": "INTEGRITY_TOKEN_EXPIRED", "token": "eyJhbGciOiJQUzI1NiIs..."}
Token signature invalid 401 {"success": false, "status": "INTEGRITY_TOKEN_INVALID", "token": "eyJhbGciOiJQUzI1NiIs..."}
Token missing segments 500 {"success": false, "status": "INTEGRITY_TOKEN_STANDARDS_UNMET", "token": "eyJhbGciOiJQUzI1NiIs..."}
Integrity token replayed / consumed or malformed 401 {"success": false, "status": "INTEGRITY_TOKEN_STANDARDS_UNMET", "unique_id": "device-unique-id", "token": "eyJhbGciOiJQUzI1NiIs..."}
Challenge token replayed / consumed 401 {"success": false, "status": "CHALLENGE_TOKEN_ALREADY_CONSUMED", "unique_id": "device-unique-id"}
Unknown / unhandled decode error 400 {"success": false, "status": "UNKOWN_ERROR"}
Device Attestation
Device integrity compromised (rooted, hooked) 403 {"success": false, "status": "DEVICE_INTEGRITY_STANDARDS_UNMET", "unique_id": "device-unique-id", "token": "eyJhbGciOiJQUzI1NiIs..."}
Attested serial revoked (X.509 CRL) 403 {"success": false, "status": "DEVICE_ATTESTED_REVOKED", "unique_id": "device-unique-id", "token": "eyJhbGciOiJQUzI1NiIs..."}
Device issuer not allowed (Google / Meta gating) 403 {"success": false, "status": "DEVICE_ATTESTED_DISALLOWED", "unique_id": "device-unique-id", "token": "eyJhbGciOiJQUzI1NiIs..."}
Bootloader / verified boot integrity failed 403 {"success": false, "status": "DEVICE_INTEGRITY_STANDARDS_UNMET", "unique_id": "device-unique-id", "token": "eyJhbGciOiJQUzI1NiIs..."}
Integrity standard unmet (security level) 403 {"success": false, "status": "DEVICE_INTEGRITY_STANDARDS_UNMET", "unique_id": "device-unique-id", "token": "eyJhbGciOiJQUzI1NiIs..."}
Device OS outdated 403 {"success": false, "status": "DEVICE_INTEGRITY_UPDATE_UNMET", "unique_id": "device-unique-id", "token": "eyJhbGciOiJQUzI1NiIs..."}
Application SHA-256 or package mismatch 403 {"success": false, "status": "APP_INTEGRITY_STANDARDS_UNMET", "unique_id": "device-unique-id", "token": "eyJhbGciOiJQUzI1NiIs..."}
Application version below minimum 403 {"success": false, "status": "APP_VERSION_STANDARDS_UNMET", "unique_id": "device-unique-id", "token": "eyJhbGciOiJQUzI1NiIs..."}
Project Authorization
Device banned 403 {"success": false, "status": "DEVICE_AUTHORIZATION_FAILED", "unique_id": "device-unique-id", "token": "eyJhbGciOiJQUzI1NiIs..."}
Project suspended 403 {"success": false, "status": "PROJECT_SUSPENDED", "message": "Project has been suspended by administrator."}
Project payment past due 402 {"success": false, "status": "PROJECT_PAUSED", "message": "Project payment is past due. Please pay for your project for the services to be put back online."}
Anti-replay protection Every integrity_token is atomically consumed the first time it is verified. Submitting the same token again returns INTEGRITY_TOKEN_STANDARDS_UNMET with HTTP 401. Every response (success or failure) includes a token field containing the AXE-decrypted inner JWT for debugging and downstream use.
Attestation API · 09

Hardware Revoked List

Retrieves the current hardware revocation list, sourced from Google's publicly available Attestation CRL (Certificate Revocation List) and extended with any hardware Kubian has permanently suspended. Entries are keyed by intermediate hardware certificate serial numbers.

Data source — Google's public Attestation CRL

Kubian mirrors https://android.googleapis.com/attestation/status, the revocation list Google publishes for attested hardware. Certificates Google has listed as revoked are treated as permanently revoked hardware. The response keys are the serial numbers of the revoked intermediate hardware certificates, and each entry carries the status and reason Google publishes (for example REVOKED / KEY_COMPROMISE).

Endpoint

GET  Hardware Revoked List
https://api.kubian.app/platform_integrity/hardware-intermediate/revoked

Response schema — 200 OK

Response schema
{
  "entries": {
    "a1b2c3d4e5f6g7h8i9j0": {
      "status": "REVOKED",
      "reason": "KEY_COMPROMISE"
    }
  }
}
Integration · 10

The Flow at a Glance

How to start the attestation flow from the client / application side using the native Kubian libraries, and how to complete verification on your own backend.

The attestation pipeline requires 4 API calls total: 2 initiated by the SDK (Init and GetToken) and 2 internal calls the SDK performs automatically during the token request.

# API Call Initiated By Purpose
1 get-nonce SDK (during Init) Fetch a signed hardware nonce and create a device identity.
2 get-device-certificate SDK (during Init) Fetch the device attestation certificate, HW token, and entitlement status.
3 challenge SDK (automatic) Exchange the device cert for a signed challenge token (AXE-encrypted).
4 integrity SDK (automatic) Mint a signed integrity token from the challenge token and hardware proof (AXE-encrypted).
  1. Core Initialization — call KubianCore.Init(appId, callback). The SDK fetches the nonce, attestation certificate, and entitlement status in a single handshake. Always check the callback's error string before continuing.
  2. Request Integrity Token — call KubianIntegrity.getToken(appId, nonce) with a server-generated nonce. The SDK performs the challenge and integrity minting internally and returns a signed integrity_token.
  3. Submit to your backend — forward the token to your own server, which calls verify_token and asserts the nonce.
Internal endpoints are not for direct integration The underlying challenge / integrity token minting endpoints are internal Kubian infrastructure. The client SDKs (KubianIntegrity.getToken) handle this exchange natively — never call those endpoints from your own code.
Integration · 11

Core Initialization

Initializing the Kubian core from C# / Unity. A single KubianCore.Init call fetches the attestation certificate, HW token, nonce, and entitlement status in one handshake (2 internal API calls). Always handle the callback error string.

C# · Unity
using KubianCore;
using UnityEngine;

public class KubianBootstrapper : MonoBehaviour
{
    private const string AppId = "YOUR_KUBIAN_APP_ID";

    void Start()
    {
        InitializeCore();
    }

    private void InitializeCore()
    {
        Debug.Log("[Kubian] Initializing Core Platform...");

        KubianCore.Init(AppId, (error) =>
        {
            if (!string.IsNullOrEmpty(error))
            {
                Debug.LogError("[Kubian] Core Initialization Failed: " + error);
                HandleFailure("Failed to initialize security engine.");
                return;
            }

            Debug.Log("[Kubian] Core Initialized. Ready for Attestation.");
            ProceedToAttestation();
        });
    }

    private void HandleFailure(string reason)
    {
        // Kick the player to the main menu or prevent entering multiplayer matches.
    }

    private void ProceedToAttestation()
    {
        // Generate a challenge_nonce on your server, then call KubianIntegrity.getToken().
    }
}
Integration · 12

Requesting the Integrity Token

Generate a server-side challenge nonce and request the integrity token from the native Kubian attestation library. The SDK handles the challenge and integrity minting internally (2 additional API calls).

C# · Attestation
using KubianIntegrity;
using System.Security.Cryptography;

// The "challenge_nonce" must be a URL-safe Base64 string generated by your
// application server, between 22 and 172 characters long.
string GetChallengeNonceFromAppServer()
{
    byte[] randomBytes = new byte[16];
    using (RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider())
    {
        rng.GetBytes(randomBytes);
    }
    string base64Nonce = Convert.ToBase64String(randomBytes);
    return base64Nonce.Replace('+', '-').Replace('/', '_');
}

void RequestAttestation()
{
    string challenge_nonce = GetChallengeNonceFromAppServer();

    KubianIntegrity.getToken(AppId, challenge_nonce)
        .OnComplete((token, error) =>
        {
            if (!string.IsNullOrEmpty(token))
            {
                StartCoroutine(SubmitToBackend(token));
            }
            else
            {
                if (AuthenticationStatus != null) AuthenticationStatus.text = error;
                isAuthenticating = false;
            }
        });
}
Integration · 13

Backend Verification

Your backend receives the token from the client and forwards it to Kubian with your ACT header. The successful response returns a nonce.

Headers
POST https://api.kubian.app/platform_integrity/verify_token
Content-Type: application/json
access-token: KB|{project-app-id}|{project-app-secret}
JSON body
{
  "token": "{AXE-encrypted-integrity-token-from-client}"
}
Developer nonce validation is mandatory When Kubian verifies the device, the JSON response contains a nonce. Your backend must verify that this returned nonce matches the exact nonce you assigned to the client session. If it matches, the client is verified. If it does not match, reject the client immediately to prevent replay attacks. Never put your app-secret in the Unity client.
Integration · 14

Best Practices

Operational guidance for building a resilient, replay-safe attestation flow into your game or application.

  • Secure nonces: generate cryptographically secure, unpredictable nonces on your server — never sequential numbers.
  • Expiration tracking: track nonce and token expiration to prevent replays.
  • Re-verification: recheck attestation on high-stakes actions — joining/leaving rooms, trading, purchasing, entering competitive modes.
  • Don't block the main thread: callbacks are asynchronous; avoid Thread.Sleep or blocking loops.
  • Retry logic: on network timeouts during Init, back off and retry before disconnecting the user.
  • Obfuscation: run Unity assemblies (especially success callbacks) through an obfuscator such as IL2CPP with string encryption.
Device Ban API · 15

Device Ban API Reference

Manage and enforce hardware-level device bans for your project. Ban devices by their hardware-bound unique ID, query ban state, and revoke bans when necessary.

ACT (Access Content Token) required Every endpoint on this page is gated by an ACT sent via the access-token header in the form KB|{project-app-id}|{project-app-secret}. See the ACT error reference for failure responses.
Critical developer disclaimer Bans are a one-time receipt: developers are required to keep track of returned ban-id values on their own database if they wish to unban a device later. The ban-id is returned only once at ban execution and cannot be retrieved from Kubian later. See the Ban Requirements pages.
Device Ban API · 16

Ban Device

Bans a specific device from accessing your application. The device's unique_id must exist in your project's associated device list before a ban can be placed.

Endpoint

POST  Ban Device
https://api.kubian.app/platform_integrity/ban_device

Headers

Header Value
access-token KB|{project-app-id}|{project-app-secret}
Content-Type application/json

JSON body

Field Type Description
unique_id string The unique device identifier generated by the client and returned in attestation responses.
ban_time_in_hours int Optional. Duration of the ban in hours (default 168, capped at 8760 = 1 year). Set to 0 for a permanent ban that will never expire until manually revoked.
reason string Optional. Offending ban reason (max 500 characters). Omitted if not provided.

Responses

Status Scenario Response body
200 Success {"message": "Device Device-Unique-ID: has been banned", "ban-id": "ban_id_12345678abcdef"}
401 Missing / malformed ACT {"graph": "Access Content Token - Failed", "message": "Unable to verify your Kubian 'access-token'. Error: ACT_Token V2.0 - ACT_Token_Missing"}
404 ACT pair not found {"graph": "Access Content Token - Failed", "message": "Unable to verify your Kubian 'access-token'. Error: ACT_Token V2.0 - ACT_Token_Invalid_ID"}
400 Missing or invalid JSON body / unique_id {"message": "Missing unique_id"} or {"message": "Missing JSON body"}
400 Invalid ban_time_in_hours {"message": "ban_time_in_hours must be a non-negative integer"}
404 Project has no associated devices {"message": "Failed to ban Device-Unique-ID: Project has no associated devices"}
404 Unique ID not associated with the project {"message": "Failed to ban Device-Unique-ID: Unique ID not found in project list"}
409 Device already banned under an active policy {"message": "Device Device-Unique-ID is already banned"}
Device Ban API · 17

Unban Device

Revokes an active ban on a device using the device's unique_id, the unique ban_id receipt generated when the ban was created, or both. Providing either identifier alone is sufficient to unban. A stored ban_id lets you revoke a ban even after the device's unique_id has rotated or expired.

Endpoint

POST  Unban Device
https://api.kubian.app/platform_integrity/unban_device

Headers

Header Value
access-token KB|{project-app-id}|{project-app-secret}
Content-Type application/json

JSON body

Field Type Description
unique_id string The unique device identifier being unbanned. Optional if a valid ban_id is supplied. Works even if the unique_id has since rotated or expired.
ban_id string The one-time ban receipt ID (with or without the ban_id_ prefix). Optional if a matching unique_id is supplied.

Responses

Status Scenario Response body
200 Success {"message": "Device Device-Unique-ID: has been unbanned", "purged-ban-id": "ban_id_12345678abcdef"}
401 Missing / malformed ACT {"graph": "Access Content Token - Failed", "message": "Unable to verify your Kubian 'access-token'. Error: ACT_Token V2.0 - ACT_Token_Missing"}
404 ACT pair not found {"graph": "Access Content Token - Failed", "message": "Unable to verify your Kubian 'access-token'. Error: ACT_Token V2.0 - ACT_Token_Invalid_ID"}
400 Neither unique_id nor ban_id supplied {"message": "Missing unique_id or ban_id (at least one is required)"}
404 No ban record matches the supplied identifier {"message": "Failed to unban Device-Unique-ID: no ban found for this unique_id"} or {"message": "Failed to unban ban_id_12345678abcdef: ban record not found"}
Device Ban API · 18

List Banned Devices

Retrieves an object containing all currently active hardware bans linked to your project. Expired bans are omitted automatically, and ban-id values are masked to their first 5 hex characters to minimize data exposure.

Endpoint

POST  List Banned Devices
https://api.kubian.app/platform_integrity/user/list_bans

Headers

Header Value
access-token KB|{project-app-id}|{project-app-secret}
Content-Type application/json

JSON body

No request body parameters are necessary for this query.

Responses

Status Scenario Response body
200 Success {"entries": {"unique-id-example-1": {"ban-id": "ban_id_12345...", "ban_time_remaining": 167.95}}}
401 Missing / malformed ACT {"graph": "Access Content Token - Failed", "message": "Unable to verify your Kubian 'access-token'. Error: ACT_Token V2.0 - ACT_Token_Missing"}
404 ACT pair not found {"graph": "Access Content Token - Failed", "message": "Unable to verify your Kubian 'access-token'. Error: ACT_Token V2.0 - ACT_Token_Invalid_ID"}
Device Ban API · 19

Device Ban Status

Queries the operational ban state of a hardware unique ID within your project. If the device has an active restriction, deployment timestamps and remaining hour timelines are returned.

Endpoint

POST  Device Ban Status
https://api.kubian.app/platform_integrity/user/device_ban_status

Headers

Header Value
access-token KB|{project-app-id}|{project-app-secret}
Content-Type application/json

JSON body

Field Type Description
unique_id string The hardware unique device identifier to search against active policy registers.

Responses

Status Scenario Response body
200 Device cleanly authorized {"banned": false, "message": "Device Device-Unique-ID: No active bans found for this Unique ID.", "success": true}
200 Device matches active ban policy {"details": {"ban_id": "ban_id_12345", "ban_time_remaining_hours": 167.95, "banned_at": "2026-05-21T16:53:27", "expires_at": "2026-05-28T16:53:27", "unique_id": "Device-Unique-ID"}, "banned": true, "success": true}
401 Missing / malformed ACT {"graph": "Access Content Token - Failed", "message": "Unable to verify your Kubian 'access-token'. Error: ACT_Token V2.0 - ACT_Token_Missing"}
404 ACT pair not found {"graph": "Access Content Token - Failed", "message": "Unable to verify your Kubian 'access-token'. Error: ACT_Token V2.0 - ACT_Token_Invalid_ID"}
400 Missing unique_id {"message": "Missing unique_id"} or {"message": "Missing JSON body"}
404 Project has no associated devices {"message": "Failed to find ban status for Device-Unique-ID: No associated devices found for this project"}
404 Unique ID does not match any associated device {"message": "Failed to find ban status for Device-Unique-ID: Unique ID does not match any of the associated devices with this project"}
Device Ban API · 20

Get Ban Details

Queries granular tracking metrics and timestamps for a specific restriction record. This endpoint strictly requires the exact, full unmasked ban receipt ID. Masked or truncated entries are rejected to eliminate lookup guessing.

Endpoint

POST  Get Ban Details
https://api.kubian.app/platform_integrity/user/ban_details

Headers

Header Value
access-token KB|{project-app-id}|{project-app-secret}
Content-Type application/json

JSON body

Field Type Description
ban_id string The exact, full unmasked ban identifier received on creation (e.g. ban_id_12345678abcdef). Partial or masked tokens are rejected.

Responses

Status Scenario Response body
200 Ban profile found {"success": true, "details": {"unique_id": "Device-Unique-ID", "ban_id_masked": "ban_id_12345", "ban_id_full": "ban_id_12345678abcdef", "banned_at": "2026-05-21T16:53:27", "expires_at": "2026-05-28T16:53:27", "is_active": true, "ban_time_remaining_hours": 167.95}}
401 Missing / malformed ACT {"graph": "Access Content Token - Failed", "message": "Unable to verify your Kubian 'access-token'. Error: ACT_Token V2.0 - ACT_Token_Missing"}
404 ACT pair not found {"graph": "Access Content Token - Failed", "message": "Unable to verify your Kubian 'access-token'. Error: ACT_Token V2.0 - ACT_Token_Invalid_ID"}
400 Missing ban_id {"message": "Missing ban_id"} or {"message": "Missing JSON body"}
404 No matching ban record {"success": false, "message": "Failed to find ban details: Invalid or unrecognized ban_id for this project."}
Developer Responsibilities · 21

Device Ban Requirements & Compliance

What you, as a developer, are responsible for tracking when using the Device Ban API — including ban-id receipts, device identifiers, and the compliance terms that govern ban enforcement.

Tracking ban-id receipts

Bans are a one-time receipt. When ban_device succeeds, the response contains a ban-id value:

Ban response
{
  "message": "Device Device-Unique-ID: has been banned",
  "ban-id": "ban_id_12345678abcdef"
}
The ban-id is returned exactly once. It cannot be retrieved from Kubian's servers after execution. If you do not store it, you can still unban through unique_id while that identifier is unrotated, but after the unique_id rotates the ban-id becomes the only reliable way to revoke the ban. Store the ban-id immediately after a successful ban response.
  • Persist the ban-id immediately after a successful ban response.
  • Store it together with the device's unique_id and the ban timestamps.
  • Treat ban-id as a secret credential for the unban flow — it can revoke the ban.
  • Pass either the unique_id, the ban-id, or both to unban_device. Use the full unmasked value with ban_details; masked values are rejected.
Developer Responsibilities · 22

Device Identifiers

Every attested device is identified by a unique_id, a hardware-bound identifier generated by the client and returned in attestation responses. It is the key your project associates with a device, and the value you pass to every ban endpoint.

Identifier Source Notes
unique_id Attestation responses Required by ban_device, device_ban_status, and, along with ban_id, optional for unban_device (either identifier alone unbans). Rotates every 60 days.
ban_id Created at ban time One-time receipt. Required by ban_details and, along with unique_id, optional for unban_device (either identifier alone unbans).

Rotation & privacy: unique_id values rotate every 60 days by design. Active bans persist across rotation — Kubian links the ban to the device's hardware identity internally, so a rotated unique_id does not lift an active ban. Unbanning with a stored ban_id continues to work after rotation, so persist the ban-id receipt to retain the ability to revoke a ban indefinitely. The device's actual Android ID is never exposed to developers.

Developer Responsibilities · 23

What You Should Store

The minimum fields you should persist to manage bans effectively and honor unban / detail flows.

Field Type Required Purpose
unique_id string Yes Identifies the device for ban queries.
ban_id string Yes Enables future unban and detail lookups.
banned_at datetime Optional Audit trail for when the ban was issued.
expires_at datetime Optional Known ban expiry for your own enforcement.
Reason / context string Optional Which check or policy triggered the ban.
Developer Responsibilities · 24

Compliance Terms

The terms that govern ban enforcement, durations, expiry, and identifier rotation.

  • Default duration: bans default to 7 days (168 hours).
  • Maximum duration: ban_time_in_hours is capped at 1 year (365 days / 8760 hours).
  • Permanent bans: setting ban_time_in_hours to 0 creates a permanent ban that never expires. Permanent bans must be manually revoked by an administrator.
  • Automatic expiry: timed bans expire automatically; expired bans are omitted from list_bans and no longer enforced by verify_token.
  • Identifier rotation: unique_id values rotate every 60 days. Active bans persist across rotation — Kubian enforces bans by hardware identity, not just the unique_id, so banned devices remain blocked even after rotation.
  • Masking: ban-id values are masked to their first 5 hex characters in list responses (Administrative Isolation & Masking, Privacy Policy Section 3).
  • Single device assignment: a unique_id is bound to your project's device list; bans cannot be placed on unknown identifiers.
Enforcement responsibility The ban API records enforcement state, but it is your responsibility to honor it in your application: check device_ban_status or surface DEVICE_AUTHORIZATION_FAILED attestation failures, and gate gameplay accordingly.
Client API · 28

Client API Overview & Usage

The Client API lets your game interact with player identities on Kubian: log a device in, store profile data, and change profile names. It is designed for in-game use through KubianCore, and every endpoint can be toggled on or off per application from your dashboard.

Concepts

  • Device accounts — the first time a verified device logs in, Kubian automatically provisions an account (Player-xxxxxxxx) and links it to that device. No signup flow is required.
  • Profile data — a small string key/value store attached to each account (for example rank, region, loadout). Values are visible in your dashboard under the player's Profile Data tab and are searchable by key/value.
  • Profile name — a human-friendly display name. It defaults to the auto-generated username until you or the player changes it.
  • Sessions — login returns a session ticket used as the X-Authorization header on subsequent calls.

Endpoints

Method Endpoint In-game call Purpose
POST /client/create KubianCore.client.Create Create the device's account (auto-falls back to login).
POST /client/login KubianCore.client.Login Log the device in and get a session ticket.
POST /client/addProfileData KubianCore.client.addProfileData Merge key/value pairs into the account's profile data.
POST /client/updateProfileName KubianCore.client.updateProfileName Change the account's profile name.

In-game usage (KubianCore)

All of this is handled natively by the SDK once the platform is initialized:

Unity / C#
// Device login (auto-provisions the account)
KubianCore.client.Login((userId, error) =>
{
    if (error != null) { Debug.LogError(error); return; }
    Debug.Log("Logged in as " + userId);
});

// Or restore a session created by your own trusted server:
KubianCore.client.loginCreds(sessionTicket, userId, accessToken, (uid, err) => { ... });

// Profile data
KubianCore.client.addProfileData("rank", "diamond", (resultJson, error) => { ... });
KubianCore.client.updateProfileName("ProKiller99", (resultJson, error) => { ... });

// Permanent project-scoped device id (see Permanent Account IDs)
string deviceId = KubianCore.device.getProperty.accountId();

// Sign an AXE account probe (see Account Verification)
KubianCore.device.transactions.accountVerification((probeToken, err) => { ... });

// Current session & stored profile data
string ticket = KubianCore.client.account.getProperty.sessionTicket();
string profileJson = KubianCore.client.account.getProperty.profileData();

The native layer builds the request bodies, attaches the one-time device certificate and hardware token, signs requests, and stores the session so profile APIs work immediately after login.

Client API · 29

Client Login

POST /client/login exchanges a valid device certificate and hardware token for an account session. Devices are authenticated exactly like integrity verification: the certificate is one-time-use and consumed on success.

In-game call: KubianCore.client.Login

Request

Body
{
  "app_id": "your-app-id",
  "android_id": "device-android-id",
  "device_certificate": "<one-time certificate from init>",
  "access_token": "HW|...|..."
}

Success response

200 OK
{
  "success": true,
  "user_id": "...",
  "unique_id": "abcd1234",
  "username": "Player-4f7a21c9",
  "profile_name": "Player-4f7a21c9",
  "profile_data": { },
  "last_login": null,
  "session_ticket": "APPID.SESSIONID.ACTTOKEN.SIG",
  "access_token": "HW|...|...",
  "session_expires_in": 86400
}

The session ticket must be sent as the X-Authorization header on profile API calls. Sessions expire after 24 hours.

Any time after signing in, the SDK can hand you the current state: KubianCore.client.account.getProperty.sessionTicket() returns the active session ticket and KubianCore.client.account.getProperty.profileData() returns all stored profile data as a JSON object string (refreshed on every login and after each profile update). Both return null when not signed in.

Field meanings: username is the display name — it is what /client/updateProfileName and /server/updateClientProfileName change. profile_name is the stable profile identifier (Player-xxxxxxxx) and never changes. Both start out equal on fresh accounts.

Banned device response

If the device's unique ID has an active ban, login is rejected with HTTP 403:

403 Forbidden
{
  "success": false,
  "error": "Unable to login to requested user account",
  "message": "The requested account has been banned.",
  "ban_details": {
    "reason": "Cheating",          // only if ban reason visibility is enabled
    "duration": "23h 12m remaining", // only if duration visibility is enabled ("Permanent" for permanent bans)
    "issued_at": "2026-08-22T10:15:00"
  }
}

Reason and duration fields respect the dashboard's banning visibility settings; issued_at is always present.

Errors

  • 403 — certificate invalid or already used; access token mismatch; Client login API disabled for the app.
  • 404 — unknown app_id.

Create an account

In-game call: KubianCore.client.Create

/client/create takes the exact same body as /client/login and respects the same toggles and ban checks. It provisions the device's account and returns its identity without issuing a session:

POST /client/create
{
  "app_id": "your-app-id",
  "android_id": "40628456",
  "device_certificate": "-----BEGIN CERTIFICATE-----...",
  "access_token": "HW|...|..."
}
200 OK
{
  "success": true,
  "created": true,
  "user_id": "...",
  "unique_id": "abcd1234",
  "username": "Player-8ebb770f",
  "profile_name": "Player-8ebb770f"
}

If this device already owns an account, creation is rejected:

409 Conflict
{
  "success": false,
  "error": "Account already exist",
  "message": "This device is already connected to an account. Please log in instead."
}

You normally don't have to care: when KubianCore.client.Create receives the Account already exist error, the native layer automatically falls back to logging that account in — and after a fresh creation it signs in too. Either way your callback fires with the logged-in userId:

Unity / C#
KubianCore.client.Create((userId, err) =>
{
    if (err != null) { Debug.LogError(err); return; }
    // Signed in - account was created if missing, logged into otherwise.
});
Client API · 30

Profile APIs

Both endpoints require a valid session from /client/login. The session ticket travels in the X-Authorization header; the body carries the hardware access token that identifies the caller.

Add profile data

In-game call: KubianCore.client.addProfileData

POST /client/addProfileData
X-Authorization: APPID.SESSIONID.ACTTOKEN.SIG

{
  "access_token": "HW|...|...",
  "app_id": "your-app-id",
  "profile_data": {
    "rank": "diamond",
    "region": "us-west"
  }
}

Keys are merged into the existing profile data. Limits: 32 keys per account, keys up to 64 characters, values up to 512 characters.

200 OK
{
  "success": true,
  "user_id": "...",
  "added_keys": ["rank", "region"],
  "profile_data": { "rank": "diamond", "region": "us-west" }
}

Update profile name

In-game call: KubianCore.client.updateProfileName

POST /client/updateProfileName
X-Authorization: APPID.SESSIONID.ACTTOKEN.SIG

{
  "access_token": "HW|...|...",
  "app_id": "your-app-id",
  "username": "ProKiller99"
}

This sets the account's username — the display name shown in your dashboard. The stable profile_name identifier (for example Player-4f7a21c9) never changes. For compatibility, the legacy profile_name body key is still accepted. Names must be 3–32 characters and pass profanity filtering. The result appears in your dashboard's Players table as <unique_id> - <username>.

Common errors

  • 401 — missing/invalid session ticket or access token.
  • 403 — the corresponding Client API toggle is disabled for the app.
  • 400 — validation failure (bad key/value/name).
  • 409 — profile data limit reached.
Client API · 31

Permanent Account IDs

Alongside per-device accounts, Kubian issues every physical device a permanent user_account_id: a 16-character numeric identifier derived from the device's Widevine ID and scoped to your application. It never expires and never rotates.

Properties

  • Exactly 16 digits (0–9).
  • Tied to the Widevine DRM ID of the device.
  • Project-scoped — the same device receives a different account id in every application.
  • Unchanging within your project: it survives reinstalls, factory resets, and cache clears.
  • Issued lazily on first contact and stored permanently server-side.

Where you receive it

Source Field
Integrity init response user_account_id
Challenge token payload user_account_id
KubianCore (in-game) KubianCore.device.getProperty.accountId()

Usage

Unity / C#
string permanentId = KubianCore.device.getProperty.accountId();
if (permanentId != null)
{
    // Stable device identifier scoped to this application.
}

The value is available after platform initialization completes and is returned as null when the device could not be identified.

Access is gated. This identifier is only delivered once your project has an approved Account ID Collection request — open your project's Data Collection Request tab in the dashboard to apply. Until approval, accountId() returns null, no user_account_id is included in integrity responses or challenge tokens, and /server/verifyPermanentAccountId reports every id as unknown for your app.
Server-to-Kubian API · 32

Authentication & API Toggles

The Server-to-Kubian API lets your trusted backend manage player accounts directly — logging players in, writing profile data, and renaming profiles — without a device present.

Authentication

Every request must include your application credentials in the access-token header:

Header
access-token: KB|<app-id>|<app-secret>

You can find both values in your dashboard under application credentials. Requests with invalid credentials are rejected with 401 Invalid app credentials.

Endpoints

Method Endpoint Purpose
POST /server/create Create an account using your own user_id (max 32 characters).
POST /server/login Log an existing account in by user_id.
POST /server/addClientProfileData Merge profile data into any of your accounts.
POST /server/updateClientProfileName Rename any of your accounts.
POST /server/verifyPermanentAccountId Verify a device's permanent account id.
POST /verify/transaction/probe_transaction_token Verify an AXE account probe token and learn its account id.

API toggles

The dashboard's API Settings → Client APIs panel controls three switches that apply to both the client endpoints and their server equivalents:

  • Client login — gates /client/login, /client/create and /server/login.
  • Update profile name — gates /client/updateProfileName and its server variant.
  • Add profile data — gates /client/addProfileData and its server variant.

When a toggle is disabled, affected endpoints return:

403 Forbidden
{ "success": false, "error": "... disabled for this application." }
Server-to-Kubian API · 33

/server/create & /server/login

Create accounts from your backend, then log them in by user_id. Creation and login are separate calls: create once when your player signs up on your platform, then log in whenever they play.

Create an account

You supply the user_id — typically your own platform's stable player id, up to 32 characters. Kubian uses it as the account's permanent identifier, so /server/login then works with the exact same value.

POST /server/create
access-token: KB|your-app-id|your-app-secret

{
  "user_id": "7656119800000000",
  "username": "CoolPlayer"        // optional display name
}
200 OK
{
  "success": true,
  "user_id": "7656119800000000",
  "username": "CoolPlayer",
  "profile_name": "Player-4f7a21c9"
}

If the user_id is already taken by any account, creation is rejected:

409 Conflict
{
  "success": false,
  "error": "Unable to use this user_id.",
  "message": "The attempted use user_id is already connected to an account."
}

Log an account in

POST /server/login
access-token: KB|your-app-id|your-app-secret

{
  "user_id": "7656119800000000"
}

Login never creates accounts. If no account exists for the supplied user_id (or it belongs to a different application), you get:

404 Not Found
{
  "success": false,
  "error": "No account was found to login to with this user_id",
  "message": "Account not found. Please make sure the specified user_id is actually apart of an account."
}

Response

200 OK
{
  "success": true,
  "user_id": "...",
  "unique_id": "abcd1234",
  "username": "Player-9c21ba07",
  "profile_name": "Player-9c21ba07",
  "profile_data": { },
  "last_login": "2026-08-21T18:04:11",
  "session_ticket": "APPID.SESSIONID.ACTTOKEN.SIG",
  "access_token": "ACT|...",
  "session_expires_in": 86400
}

Handing the session to the game client

Deliver session_ticket, user_id and access_token to your game over your own secure channel, then install them natively:

Unity / C#
KubianCore.client.loginCreds(sessionTicket, userId, accessToken, (uid, err) =>
{
    if (err != null) { Debug.LogError(err); return; }
    // Profile APIs now work without a device-side login.
});

Bans

If any device linked to the account carries an active ban, login is rejected with the same banned-account payload documented on the Client Login page.

Verify a permanent account id

/server/verifyPermanentAccountId checks whether a permanent account id is valid for your application. Clients get this id in-game from KubianCore.device.getProperty.accountId() — a 16-digit number tied to the device's Widevine ID, scoped to your project (see Permanent Account IDs). If your project has no approved Account ID Collection request, every lookup returns valid: false:

POST /server/verifyPermanentAccountId
access-token: KB|your-app-id|your-app-secret

{
  "user_account_id": "4062845600424304"
}

A known id returns valid: true together with its creation date:

200 OK
{
  "success": true,
  "valid": true,
  "user_account_id": "4062845600424304",
  "created_at": "2026-08-01T12:00:00"
}

An unknown id is still a successful verification — it simply doesn't exist in your application:

200 OK
{
  "success": true,
  "valid": false,
  "user_account_id": "4062845600424304",
  "message": "No permanent account exists for this user_account_id in this application."
}

Ids that aren't exactly 16 digits are rejected with 400.

Server-to-Kubian API · 34

Server Profile APIs

Both endpoints target any account belonging to your application. They respect the same validation rules and limits as their client-side counterparts.

Add profile data

POST /server/addClientProfileData
access-token: KB|your-app-id|your-app-secret

{
  "user_id": "...",
  "profile_data": {
    "rank": "diamond",
    "matches_played": "142"
  }
}
200 OK
{
  "success": true,
  "user_id": "...",
  "added_keys": ["matches_played", "rank"],
  "profile_data": { "rank": "diamond", "matches_played": "142" }
}

Update profile name

POST /server/updateClientProfileName
access-token: KB|your-app-id|your-app-secret

{
  "user_id": "...",
  "username": "RenamedByServer"
}

Sets the account's username (display name). The stable profile_name identifier never changes. The legacy profile_name body key is still accepted.

Errors

  • 400 — missing identifier, oversized key/value, invalid name.
  • 401 — bad KB| credentials.
  • 403 — matching Client API toggle disabled.
  • 404 — unknown user_id.
  • 409 — profile data limit reached.
Account Protection · 35

ACT — Account Content Token

The credential that keeps you signed in between visits, without asking you to re-enter your password (and 2FA code, if enabled) every time.

What is ACT?

ACT stands for Account Content Token. It is the credential Kubian issues to an account after a successful sign-in with your password — plus your 2FA code, if you have two-factor enabled.

Think of it like a hotel key card: once you have checked in, you get a card that lets you back into your room without going to the front desk again. The card only works for your room, it expires after a set period, and if you ever change your password, every card that was ever issued is cancelled.

An ACT is tied to your account, not to any single device. Every device you sign into the same account with uses the same ACT — so seeing the same ACT on all of your browsers is normal and expected. The separate TAM check (next page) is what proves a request is actually coming from one of your trusted devices.

Elements of an ACT

  • Issued post-authentication — only after your password (and 2FA code, if enabled) has been validated.
  • Account-bound — it cannot be reused on another account, and it cannot be reverse-engineered to reveal your password.
  • Expiring — once the built-in window passes, the token stops working on its own and you are simply asked to sign in again.
  • Revocable — changing your password invalidates every previously issued ACT immediately.
  • Not self-sufficient — on any device with a registered key, a valid ACT alone is not enough; a fresh TAM device signature is required alongside it.

Kubian intentionally does not publish the internal mechanics of how ACTs are constructed or verified — that is part of what keeps them safe from being forged or tampered with.

Account Protection · 36

TAM & CAT

The Trusted Account Module ties your account to the specific device you sign in from, and the Challenge Account Token is the short-lived proof that the device still holds its key.

What is TAM?

TAM stands for Trusted Account Module. A key card can be copied — and so can an ACT if it were ever intercepted (through a compromised browser extension, a malicious script, or a leaked log). TAM closes that gap.

When you first sign in on a device, TAM has that device generate a cryptographic key pair of its own. The private half is created in a way that makes it non-exportable: your browser can use it to sign — proving "this is the same device that registered" — but nothing, not a script running on the page, not a browser extension, not even you looking through developer tools, can ever read the key's raw contents out of the browser. Only the matching public half is sent to us and stored on your account.

From that point on, signing in requires two things together: a valid ACT and proof from that specific device's TAM key. Stealing the ACT alone is no longer sufficient.

  • TAM keys are stored per device and each one is valid for 14 days. When a key expires, the device is asked to sign in again with your password, and a fresh key is issued.
  • If a new device is ever added to your account, we send a confirmation email to your account's address before that device is trusted.

What is CAT?

CAT stands for Challenge Account Token — the short-lived challenge the Trusted Account Module issues when a device needs to prove it still holds the key it registered.

Each CAT is generated fresh by us, is valid for only 120 seconds, and is cryptographically sealed so it cannot be forged, altered, or reused for a different account. Your browser never invents it — it only ever comes from us. To complete a sign-in, the device signs the CAT with its private TAM key and sends back only the signature. Because a CAT only works for the brief window it takes to verify a signature, a stolen or intercepted CAT on its own is worthless.

How TAM verifies a sign-in

  • 1. Token check — your browser presents its ACT; we confirm it is genuine and has not expired.
  • 2. CAT issued — we generate a short-lived CAT, valid for 120 seconds, and send it to your browser.
  • 3. Device proof — your browser signs that CAT with its private TAM key and sends back only the signature.
  • 4. Session granted — we verify both the CAT's authenticity and its signature against the TAM public key on file for your device. Only then is your session actually created. If the signature is missing, does not match, or the CAT has expired, sign-in is refused — even with a valid ACT.

The CAT is sealed with a server-held key using authenticated encryption, so it cannot be forged, decoded, or edited by anyone without that key, and it stops working entirely once its 120-second window closes.

Account Protection · 37

How Your Account Is Protected

What actually has to be true for a sign-in to be accepted — and what happens if a token is stolen, expires, or is invalidated.

The bar for a valid session

Only a token that was legitimately issued to your account, that has not expired, that has not been invalidated by a password change, and that is backed by a valid TAM device signature will ever work. All four conditions must hold at once.

  • Legitimately issued — generated only after you signed in with your password (and 2FA code, if enabled).
  • Not expired — an ACT carries a built-in expiration; once the window passes it stops working on its own.
  • Not invalidated — changing your password invalidates every previously issued token, so a token from before the change can no longer be used.
  • Device-backed — on a device with a registered key, a fresh TAM signature is required alongside the ACT.

Combined, these mean both your identity and your trusted device must be confirmed before a session is created.

Instant re-login

"Instant re-login" is the everyday benefit of holding a valid ACT plus a registered device key: when you come back to an app or site, your device presents both and you are back in immediately — no typing, no waiting on a fresh login screen.

  • Fast — your session resumes right away instead of requiring you to sign in again.
  • Seamless — there is no interruption between visits on any of your trusted devices.
  • Safe — the same device-key requirement that makes re-login instant is what makes a stolen ACT useless on its own.

If a token has expired, is malformed, or is missing its device signature, re-login is not granted; you simply go back through sign-in.

Password and token invalidation

Changing your password invalidates every previously issued token the moment the new password takes effect. Every device using a stored sign-in must sign in again with your password (and 2FA code, if enabled) before it can continue. This is the fastest way to lock out anyone who might hold an old sign-in.

Account Protection · 38

2FA, Trusted Devices & Recovery

Two-factor authentication, managing the devices trusted on your account, and what to do if you ever need to lock everything down.

Two-factor authentication (2FA)

Enabling 2FA adds a second check at sign-in, so your password alone is not enough to reach your account. With 2FA on, even a compromised password is not sufficient on its own to sign in.

  • TOTP authenticator — a 6-digit code from your authenticator app, generated from a per-account secret configured during setup.
  • Applied at the gate — the 2FA code is required at sign-in, at password reset, and on account-recovery paths before anything changes.
  • Preserved on lock — if your account is ever locked for protection, your existing 2FA settings are preserved so you can unlock with them.

Setup is done through a secure setup flow that displays a QR code (or a manual secret) and requires you to confirm a generated code before 2FA is enabled.

Managing trusted devices

The Account Administration page (under Account Settings) gives you a live view of every trusted device signed into your account. For each device you can see its sign-in status and when it was last used.

  • Sign out one device — removes that device's TAM key from your account. The device can no longer use its stored sign-in; it will be asked for your password (and 2FA code, if enabled) before it can access your account again.
  • Sign out all devices — removes every TAM key on the account at once. Every device, including the one you are using now, must sign in again with your password and 2FA before it can continue.

A confirmation email is sent to your account's security address when a device is signed out. If a sign-out wasn't yours, the email lets you secure your account immediately.

Account lock & recovery

If a sign-in attempt looks like it could be an attacker, Kubian may lock the account: all trusted device keys are invalidated and a recovery email is sent with instructions to reset your password, re-enable/confirm two-factor authentication, and unlock the account.

  1. Reset your password — choose a new, strong password. Every previously issued token is invalidated the moment the new password takes effect.
  2. Set up 2FA — configure a fresh authenticator (TOTP) key or confirm email verification before unlocking, so the account cannot be reached with a password alone.
  3. Unlock and re-register — unlock the account manually with your new password, then re-register your trusted devices.

Recommended account hygiene

  • Use a strong, unique password — your ACT is only as safe as the password that protects it; no server-side security makes up for a weak, reused, or shared password.
  • Enable 2FA — the single strongest step you can take; with 2FA on, even a compromised password is not enough on its own to sign in.
  • Sign out devices you don't use — remove a trusted-device key entirely from a machine you've stopped using; that device will need your password (and 2FA) before it can access your account again.
  • Change your password if anything looks unfamiliar — doing so immediately revokes every stored sign-in everywhere.
Account Verification · 25

Account Verification Flow

Account verification proves that a device owns a permanent, project-scoped user_account_id — with one call on the device and one call on your backend. The device receives an AXE-encrypted probe token it cannot read or forge; your server verifies that token and learns the account id.

  1. Device signs a probe. Your game calls KubianCore.device.transactions.accountVerification(). The SDK builds and sends a signed request; the server verifies the device, resolves the project-scoped account id, and returns an AXE-encrypted probe_transaction_token (valid 10 minutes, single-use).
  2. Device hands the token to your backend over your own secure channel (for example alongside your login request). The token is opaque to the client.
  3. Your server verifies the probe with POST /verify/transaction/probe_transaction_token using your KB| credentials. Kubian opens the AXE envelope, checks authenticity, expiry and single-use, verifies the bound nonce, and returns the user_account_id.
  4. Your server confirms the id with POST /server/verifyPermanentAccountId — the same check used for ids read in-game via KubianCore.device.getProperty.accountId().

In-game usage (KubianCore)

Unity / C#
KubianCore.device.transactions.accountVerification((probeToken, error) =>
{
    if (error != null) { Debug.LogError(error); return; }
    // Send probeToken to YOUR backend - never verify it on-device.
    SendToGameServer(probeToken);
});
Two requirements
  • Widevine identity — the probe requires the Widevine ID (plus security level), which is the stable account identity that survives factory resets. The SDK supplies both automatically.
  • Access is gated — like every account-id surface, probing requires an approved Account ID Collection request for your project (dashboard → Data Collection Request tab). Without it, signing returns 403.
Account Verification · 26

Verify Probe Token

POST /verify/transaction/probe_transaction_token verifies the authenticity of an account probe on your trusted backend and returns the project-scoped hardware id — the same value KubianCore.device.getProperty.accountId() reports in-game.

Request

POST /verify/transaction/probe_transaction_token
access-token: KB|your-app-id|your-app-secret

{
  "probe_transaction_token": "AXE...."
}

Authentication is your KB|app-id|app-secret server credential — the same header used across the Server API. The body also accepts probe_token or token as the key.

What verification checks

  • The AXE envelope opens with the server key (authenticity — forged or edited tokens fail).
  • The probe belongs to your application and has not expired.
  • The token has not been used before (single-use jti).
  • The nonce sealed inside is pulled out and verified: known, consumed by signing, and bound to the same probe and account.

Success response

200 OK
{
  "success": true,
  "valid": true,
  "user_account_id": "4062845600424304",
  "app_id": "your-app-id",
  "created_at": "2026-08-01T12:00:00"
}

An unknown id is still a successful verification — it simply doesn't exist in your application (valid: false with the same shape).

No second call needed. A valid: true response already confirms the id for your application — there is nothing further to prove. If you want to re-check the same id later (for example on subsequent sessions, without spending a fresh probe), POST /server/verifyPermanentAccountId accepts the bare id any time.

Direct link to this page: https://kubian.app/integrity/docs#account-verify-page-4

Errors

  • 400 — missing body, missing token, or not an AXE token.
  • 401 — bad KB| credentials, forged/expired/replayed token, or any nonce check failure.
  • 403 — token belongs to a different application.

Every exact error string is listed on the next page: Success & Failure Responses.

Account Verification · 27

Success & Failure Responses

The complete response reference for POST /verify/transaction/probe_transaction_token — what each field means and exactly what failure looks like.

Success — verified

Field Description
success Request processed. Always true here.
valid true — the probe is authentic and the account id exists in your application. Conclusive: no further check needed.
user_account_id The 16-digit project-scoped id. Same value as KubianCore.device.getProperty.accountId().
app_id Echo of your application id.
created_at When the permanent account record was created.
200 OK
{
  "success": true,
  "valid": true,
  "user_account_id": "4062845600424304",
  "app_id": "your-app-id",
  "created_at": "2026-08-01T12:00:00"
}

Success — unknown id

A well-formed, authentic probe for an id that does not exist in your application is still a successful verification — it simply reports valid: false:

200 OK
{
  "success": true,
  "valid": false,
  "user_account_id": "4062845600424304",
  "message": "No permanent account exists for this user_account_id in this application."
}

Failure responses

Every failure returns success: false, valid: false with an error string. Verification consumes the token, so a failed attempt cannot be retried with the same token — request a fresh probe.

400 Bad Request — the request itself is unusable

Error Meaning
Missing JSON body. No JSON body was sent at all.
Provide the 'probe_transaction_token' to verify. Body present but no token under probe_transaction_token, probe_token or token.
Invalid probe_transaction_token format. Token does not start with AXE. — not a probe token.
400 Bad Request
{
  "success": false,
  "valid": false,
  "error": "Provide the 'probe_transaction_token' to verify."
}

401 Unauthorized — credentials or token rejected

Error Meaning
Missing or invalid access-token header. Expected format: KB|app-id|app-secret No access-token header, or it does not start with KB|.
Malformed access-token header. Expected format: KB|app-id|app-secret Header is not exactly KB|app-id|app-secret.
Invalid app credentials. App id/secret pair does not match any project.
Invalid probe_transaction_token. AXE envelope failed to open (forged, corrupt, or wrong key), wrong payload type, missing replay id, or missing nonce/account inside.
This probe_transaction_token has expired. Past its 10-minute window — request a fresh probe.
This probe_transaction_token has already been used. Replay: single-use tokens are consumed on first verification, success or failure.
Invalid user_account_id in probe. Sealed id is not 16 digits.
Probe nonce is unknown. Sealed nonce was never issued for your application.
Probe nonce was never signed. Nonce exists but was never consumed by signing.
Probe nonce mismatch. Nonce is bound to a different probe than this token.
Probe account mismatch. Nonce is bound to a different account id than the sealed one.
401 Unauthorized
{
  "success": false,
  "valid": false,
  "error": "This probe_transaction_token has already been used."
}

403 Forbidden — right token, wrong application

403 Forbidden
{
  "success": false,
  "valid": false,
  "error": "This probe_transaction_token does not belong to this application."
}
Kubian Integrity AC · 39

What is Kubian Integrity AC

Kubian Integrity answers "can this device be trusted?" at session start. Kubian Integrity AC answers "is it still trustworthy?" — every two seconds, for the entire session. It is the always-on runtime defense layer: Real-Time Environment Protection (RTEP), device banning, and per-check enforcement, running natively inside your app.

Three native libraries, one shield

AC ships as compiled .so libraries with plain C entry points — no Java attack surface, symbols hidden, stack-protector hardened. Each library owns one job:

Runtime

libKubianIntegrityCore.so

The protection loop itself: StartProtection, ScanThreats, threat sweeps, realtime alerts, ban polling, tier gating, and session APIs.

Attestation

libKubianIntegrity.so

The two-step challenge exchange: fetches a single-use challenge, mints a fresh hardware-backed key bound to its nonce, and trades the attestation chain for an integrity token.

Identity

libKubianIntegrityCertificate.so

One-time device certificates from KeyStore attestation, with key material wiped from memory after use and keys rotated ephemerally.

Integrity vs AC at a glance

Aspect Kubian Integrity Kubian Integrity AC
Question answered Can this device be trusted right now? Is it still trustworthy, right now?
When it runs Once, at session start (point-in-time proof) Continuously, every 2 seconds for the whole session
Where it runs Server-side verification of presented proof Natively inside your app process + server-directed enforcement
Core mechanism Hardware attestation, X.509 chains, KeyMint data RTEP sweeps, realtime alerts, device bans
Outcome Integrity token: admit or refuse the session Alert, Kick, or Ban — per check, per your policy
Use them together Attestation without runtime protection trusts a device that can turn hostile a second later. Runtime protection without attestation watches a device that was never verified. Kubian Integrity proves the starting state; Integrity AC guards everything after it.
Kubian Integrity AC · 40

How RTEP Works

Real-Time Environment Protection is a native protection loop with a server-side brain. The client sweeps for threats every 2 seconds; the server decides what each detection means — Alert, Kick, or Ban — and the client enforces it. Here is the full loop.

  1. Start protection. Your integration calls StartProtection with a sweep interval (minimum 500 ms, typically 2000 ms) and an enforce flag. A detached monitor thread spins up — gameplay never waits on it.
  2. Sweep in slices. Each cycle runs the hard and soft scan sets, sleeping in 100 ms slices between work so the loop stays responsive and never hitches the game thread.
  3. Detect the change. Results are compared against the previous scan. On any change, the detection callback fires with full detail and a realtime alert is reported to the backend — app, device identity, device certificate, check, and detail.
  4. Server directs. The backend evaluates the studio's configured action for that check and answers with a directive: note (log only), kick (remove from session), or an executed device ban. In enforce mode, Kick and Ban terminate the process immediately.
  5. Re-verify forever. Ban status is re-polled every 10 seconds, the protection tier is re-fetched every 60 seconds, and device certificates renew themselves before expiry — the loop never goes stale.

One-shot scans

ScanThreats runs the same hard + soft scan set exactly once — same tier gating, same reporting — for integrations that want a point-in-time verdict (for example, right before a ranked match starts) without starting the continuous loop.

Ban polling

Every 10 seconds the monitor asks the server for the device's current ban state. If a ban lands mid-session, the player is kicked immediately and the server records a REALTIME_BANNED_WHILE_PLAYING audit event. Devices banned before protection started are not re-kicked — your login-time attestation path owns those.

Tier recheck

Every 60 seconds the library re-fetches the protection tier. If the project drops below Max, realtime checks stand down and only the ban poll continues. The tier response is RSA-signed: any unsigned or tampered reply is rejected and the client fails closed into full Max-tier protection.

RTEP is a runtime monitor, not a game-logic guard It guarantees the environment is trustworthy — your backend still validates the data flowing through it (strict nonce checks, server-side value authority). Trustworthy device, verified data: you need both.
Kubian Integrity AC · 41

Defenses Catalog

Every check RTEP runs, what it catches, and what happens by default. Checks and actions are configurable per project in the Kubian dashboard (Max tier) — this table is the starting policy. Per-check internals live in the Real-time Protection Reference.

Check Catches Default
frida Frida instrumentation framework Alert
injection Code / library injection into the process Alert
root_hard Persistent / privileged root (e.g. Magisk full install) Ban
root_soft Soft or hidden root indicators, emulator environment Kick
debugger Attached debuggers (ptrace, JDWP, LLDB) Kick
antidebug Anti-debug bypass attempts Alert
headlock_abuse Quest headlock hijacking for spatial bypass Kick
frida_behavioral Anonymous executable memory (patched Frida builds) Kick
frida_module Renamed / patched Frida binaries Kick
frida_memfd In-memory Frida injection via fd leaks Kick
widevine_hook MediaDrm hooking, invalid / tampered Widevine IDs Ban
Alert, Kick, Ban — your call Defaults above are starting points. Each check independently enables any combination of actions, so a detection can log quietly, remove the player, end the device permanently, or all three. Bans bind to hardware-backed identity and are enforced network-wide.