Documentation
Everything you need to obfuscate Luau: the HTTP API and the in-script macros.
Overview
The MoonVeil API obfuscates, prettifies, and minifies Luau over HTTP. The base URL is https://moonveil.cc/api, and every endpoint is versioned under /v2.
A successful response returns the transformed script as text/plain. Errors return JSON shaped like { "error": "..." }.
Authentication
Send your API key as a bearer token on every request. The endpoint examples below reuse this client:
const KEY = process.env.MOONVEIL_KEY!;
async function moonveil(op: string, body: object) {
const res = await fetch(`https://moonveil.cc/api/v2/${op}`, {
method: "POST",
headers: {
Authorization: `Bearer ${KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify(body),
});
if (!res.ok) throw new Error((await res.json()).error);
return res.text();
}Browser sessions authenticate with the token cookie instead, which the dashboard handles automatically. API keys can be generated in the Dashboard in the 'API Keys' section.
Account
Returns the authenticated account, its plan and limits, and today's usage.
| Field | Type | Description |
|---|---|---|
id | string | Unique account id. |
email | string | Account email. |
username | string | Discord display name. |
plan | object | Active plan: name, maxScriptChars, dailyQuota, allowedOptions. |
usage | object | Today's usage: used, quota, obfuscationCount, resetsAt. |
const res = await fetch("https://moonveil.cc/api/account", {
headers: { Authorization: `Bearer ${KEY}` },
});
const account = await res.json();Example response
{
"id": "217039...",
"email": "[email protected]",
"username": "cpnk",
"avatar": null,
"opLevel": 0,
"hasBilling": true,
"plan": {
"name": "Basic",
"maxScriptChars": 5000000,
"dailyQuota": 30,
"allowedOptions": {
"vms": ["fox", "skid"],
"compileTypes": ["cff", "vm", "safeEnv"],
}
},
"usage": { "used": 8, "quota": 30, "obfuscationCount": 142, "resetsAt": "2026-06-18T03:21:00Z" }
}Obfuscate
Runs the full obfuscation pipeline over a script. The body takes the source and an optional options object; any option you omit falls back to its default.
| Field | Type | Default | Description |
|---|---|---|---|
compileType | "cff" | "vm" | "safeEnv" | "cff" | Top-level compile mode for the whole script. |
vmType | "fox" | "skid" | "skid" | VM implementation, used when the mode is vm or safeEnv or for any MV_VM blocks for cff. |
safeEnvLock | "luau" | "rbx" | "luau" | Used when the mode is safeEnv, locks to the specified environment. |
cffDecompose | boolean | false | Decompose control flow into a flattened state machine. |
cffMangleNext | boolean | false | Mangle successor / dispatch indices. |
cffMangleStrings | boolean | false | Mangle string constants. |
cffMangleGlobals | boolean | false | Mangle global accesses. |
cffMangleCfPercent | number | 0 | Insert fake branches based on the provided chance. Higher = slower. |
const obfuscated = await moonveil("obf", {
script: 'print"meow"',
options: { compileType: "safeEnv", vmType: "fox", safeEnvLock: "rbx" },
});Returns the obfuscated Luau as text/plain (sent as a obfuscated.lua attachment). Requesting an option your plan does not allow returns 403.
Example response
-- This script was generated using MoonVeil 2.0.15-beta
-- (obfuscated bundle, tens of KB, truncated here)
return({...Prettify
Reformats a script into clean, readable source. The body is just { "script": "..." }. Returns the formatted source as text/plain.
const pretty = await moonveil("prettify", { script });Example response
local function verify(pw)
return pw == "s3cret"
endMinify
Folds constants, strips dead code, and shrinks a script. It runs the optimizer, not just whitespace removal. Body { "script": "..." }, returns text/plain.
const minified = await moonveil("minify", { script });Example response
local function verify(pw)return pw=="s3cret"end
Errors
Errors return a JSON envelope with a single error message and a matching status code.
400 | Malformed JSON, an unknown field, or the script exceeds your character limit. |
401 | Missing or invalid API key / session. |
403 | A requested option is not allowed on your plan. |
429 | Daily quota reached, or you are being rate limited. |
500 | Something failed on our side. |
Rate limits
Each plan sets a daily obfuscation quota and a short-window rate limit. Obfuscation counts against both; prettify and minify are only rate limited. When you hit a limit you get 429, with a Retry-After header on the rate-limit case.