Reference
Documentation
Everything you need to protect and ship your scripts with Bastion.
Getting Started
Bastion protects your Lua scripts with Luraph and gates them behind a key system. Create a project, upload a script, generate keys for your users, then hand them a one-line loader.
- Create a project in the dashboard.
- Upload one or more scripts (each tied to a Game ID).
- Generate keys (users) for your buyers.
- Share your project's loader URL — see below.
The Loader
Each project has a single loader URL. The buyer pastes their key and the loader, and Bastion automatically serves the right script for whatever game they're in — no Game ID in the link.
script_key = "YOUR_KEY_HERE"
loadstring(game:HttpGet("https://bastiondev.org/api/loader/project/<publicId>"))()The loader is obfuscated and cached on our edge. Replace<publicId>with your project's public ID (shown on the project's custom-loader screen).
Runtime Variables
Once authenticated, these globals are available inside your script. They mirror common whitelist variables, prefixed with BST_. For compatibility, the equivalent Luarmor LRM_ names (e.g. LRM_IsUserPremium, LRM_SecondsLeft) are also set, so scripts written for Luarmor run unmodified.
| Variable | Type | Description |
|---|---|---|
| BST_Premium | boolean | Whether the user is whitelisted. Useful for freemium / FFA scripts. |
| BST_Discord | string | The user's linked Discord ID (empty string if none). |
| BST_Script | string | Name of the current script. |
| BST_Executions | number | Total executions for this user. 0 by default. |
| BST_Expiry | number | Seconds until the key expires. math.huge if the key never expires. |
| BST_Note | string | The note attached to the user's key ('Not specified' by default). |
| BST_Version | string | Current script version, e.g. "0.0.0.3". |
if BST_Premium then
print("Welcome back, premium user!")
print("Your key expires in " .. BST_Expiry .. "s")
endMacros
Bastion runs your scripts through Luraph, so Luraph's LPH_ macros work out of the box. Write them in your source and they're processed automatically at upload — no extra configuration.
On the v15 node you can also tune protection per function with the attributes system. Make LPH_ATTRIBUTES the first statement of a function body to set its VM, preset, optimization or encryption — for example LPH_ATTRIBUTES(VM(NONE)) to keep a hot path un-virtualized, or PRESET(SECURE) to push security higher. Child functions inherit a parent's attributes unless they set their own.
LPH_OBFUSCATED
Evaluates to true only in the protected build, false in your raw source. Use it to detect tampering.
LPH_ENCSTR(str)
Encrypts a string literal so it never appears in plaintext in the output.
LPH_ENCBUF(str)
Like LPH_ENCSTR but returns an encrypted buffer (Luau only).
LPH_ENCNUM(num)
Encrypts a number literal, preserving int/float type.
LPH_REWRITE(expr)
Rewrites integer/word arithmetic into an equivalent, harder-to-read expression.
LPH_PRECHECK(fn, key)
Inlines a whitelist/anti-tamper check into the loader; the wrong result blocks decryption.
LPH_CRASH()
Immediately crashes execution — handy inside anti-tamper checks.
local secret = LPH_ENCSTR("my-secret-token")
if not LPH_OBFUSCATED then
-- only runs in your raw source, never in the protected build
LPH_CRASH()
endBST_INIT — run code before authentication
Wrap a function in BST_INIT to run it before the protected code is authenticated — for a "enter your key" GUI, or an anti-cheat bypass that has to land before anything else. The function is pulled out at upload and served raw, ahead of the obfuscated loader, so treat it as public: put no secrets in it. For compatibility, LRM_INIT_SCRIPT works as an alias.
To show an async key prompt, block at the end of the function until the key is set — otherwise the loader continues and fails authentication. Assign the entered key to the global script_key. Code that doesn't need to wait (a bypass) can just run inline.
BST_INIT(function()
local UI = loadstring(game:HttpGet("https://your-key-ui"))()
local done, entered = false, nil
UI:AddInput({ Title = "Enter a key", OnSubmit = function(key)
entered = key
done = true
end })
UI:Show()
-- Block until the key is entered, then hand it to the loader.
while not done do task.wait() end
script_key = entered
end)REST API
Automate keys and projects from your own bot or backend. Generate an API key in Account and send it as a bearer token:
curl https://bastiondev.org/api/v1/keys \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "projectId": "...", "count": 1, "durationDays": 30 }'- Auth: every request needs
Authorization: Bearer <key>. The key is shown once at generation — store it safely. - Rate limits: per-minute, by tier (Basic 60, Pro 120, Master 300). Responses carry
X-RateLimit-Remainingand a429withRetry-Afterwhen exceeded. - IP allowlist: optionally restrict your key to specific IPs or CIDR ranges in Account. Requests from anywhere else are rejected.
/api/v1/me/api/v1/projects/api/v1/scripts/api/v1/keys/api/v1/keys/api/v1/keys/lookup/api/v1/keys/:id/api/v1/keys/:id/api/v1/keys/:idMembership Tiers
Limits scale with your plan. Upgrade any time by redeeming an invite code in your account.
| Tier | Obfuscations | Users | Projects | Scripts |
|---|---|---|---|---|
| Basic $5/mo | 50 | 250 | 1 | 8 |
| Pro $10/mo | 100 | 1000 | 3 | 16 |
| Master $25/mo | 200 | 10000 | 8 | 30 |
Ready to start? Buy an invite code.
Purchase the tier you want, then redeem your code in your account.
Need higher limits? Open a ticket in our Discord for enterprise pricing.