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.

  1. Create a project in the dashboard.
  2. Upload one or more scripts (each tied to a Game ID).
  3. Generate keys (users) for your buyers.
  4. 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.

VariableTypeDescription
BST_PremiumbooleanWhether the user is whitelisted. Useful for freemium / FFA scripts.
BST_DiscordstringThe user's linked Discord ID (empty string if none).
BST_ScriptstringName of the current script.
BST_ExecutionsnumberTotal executions for this user. 0 by default.
BST_ExpirynumberSeconds until the key expires. math.huge if the key never expires.
BST_NotestringThe note attached to the user's key ('Not specified' by default).
BST_VersionstringCurrent 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")
end

Macros

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()
end

BST_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-Remaining and a 429 with Retry-After when exceeded.
  • IP allowlist: optionally restrict your key to specific IPs or CIDR ranges in Account. Requests from anywhere else are rejected.
GET/api/v1/me
Account tier, usage, and limits.
GET/api/v1/projects
List your projects.
GET/api/v1/scripts
List your scripts.
GET/api/v1/keys
List keys. Filters: projectId, scriptId, limit, cursor.
POST/api/v1/keys
Create one or more keys. Returns the raw key values once.
POST/api/v1/keys/lookup
Resolve a key's status by its value ({ key }).
GET/api/v1/keys/:id
Fetch a single key.
PATCH/api/v1/keys/:id
Lock/unlock, reset HWID, kick, set note/expiry/discordId.
DELETE/api/v1/keys/:id
Delete a key.

Membership Tiers

Limits scale with your plan. Upgrade any time by redeeming an invite code in your account.

TierObfuscationsUsersProjectsScripts
Basic $5/mo5025018
Pro $10/mo1001000316
Master $25/mo20010000830

Ready to start? Buy an invite code.

Purchase the tier you want, then redeem your code in your account.

Buy

Need higher limits? Open a ticket in our Discord for enterprise pricing.