> **Project: Roblox Script Key System** > > Build a full-stack key licensing system for Roblox scripts. Tech stack: Node.js backend, MySQL or SQLite database, plain HTML/CSS/JS frontend. I own `saiops.cc`, `api.saiops.cc`, and `key.saiops.cc`. > > --- > > **DATABASE SCHEMA — create tables for:** > > `keys` table: > - `key` (unique string) > - `created_at` (timestamp) > - `expires_at` (timestamp) > - `hwid_limit` (int — 0 = unlimited, 1 = single HWID lock, N = N unique HWIDs) > - `first_used_at` (timestamp, nullable) > - `duration_days` (int) > - `is_active` (boolean) > - `note` (optional label, e.g. "given to user X") > > `sessions` table (one row per script execution): > - `id` (auto increment) > - `key` (foreign key) > - `hwid` (string) > - `username` (Roblox username) > - `executor` (string, e.g. "Synapse X", "Fluxus") > - `game_id` (Roblox PlaceId) > - `instance_id` (Roblox JobId for server join) > - `started_at` (timestamp) > - `last_ping_at` (timestamp — updated every 30s to track session duration) > > `hwid_bindings` table: > - `key` (foreign key) > - `hwid` (string) > - `first_used_at` (timestamp) > > --- > > **API ENDPOINTS at `api.saiops.cc`:** > > `POST /validate` > - Body: `{ key, hwid, username, executor, game_id, job_id }` > - Logic: > 1. Check key exists and is not expired > 2. Check HWID limit — if `hwid_limit = 0`, allow anyone. If `hwid_limit = N`, check how many unique HWIDs are in `hwid_bindings` for this key. If under limit, add HWID if new. If over limit, reject. > 3. Log session row > 4. Return: `{ valid: true, message: "OK" }` or `{ valid: false, reason: "..." }` > > `POST /ping` > - Body: `{ key, hwid, job_id }` > - Updates `last_ping_at` for the matching active session > - Used to track how long user is running the script > > `GET /keyinfo?key=XXXX` > - Returns: created_at, expires_at, hwid_limit, hwids bound, first_used_at, is_active, remaining days > > `POST /admin/generate` (protected by secret admin token in header) > - Body: `{ duration_days, hwid_limit, note }` > - Generates a random key (e.g. `SAI-XXXX-XXXX-XXXX`), inserts into DB, returns key > > All endpoints should use CORS headers allowing only `saiops.cc` and `key.saiops.cc` as origins. Admin endpoint must require `Authorization: Bearer ADMIN_SECRET` header. > > --- > > > Admin page at `saiops.cc/admin` (hidden from nav) — password protected via localStorage token: > - Form to generate a key: input for duration (days), HWID limit, optional note > - Calls `api.saiops.cc/admin/generate` > - Shows generated key with copy button > - Table showing all keys from DB with columns: key, created, expires, hwid_limit, HWIDs used, first used, active/expired status > > --- > > **FRONTEND — `key.saiops.cc`:** > > Key info lookup page: > - box to show the key > - Calls `api.saiops.cc/keyinfo` > - Displays: status (active/expired), expiry date, time remaining, HWID slots used/total, when key was first used > > ---