API Reference

LunaUX Decompiler API

Core endpoints for Luau bytecode decompilation, disassembly, and control-flow graph extraction. All endpoints accept POST requests with a JSON body and require a valid API key in the Authorization header.

/api/decompile

Converts Luau bytecode back into human-readable Lua/Luau source code. The output is a plain-text string on success.

Decompile bytecode

POST/api/decompile

Submits base64-encoded Luau bytecode for decompilation. Returns a plain-text Lua source string on HTTP 200. Optionally accepts a filename for debug labeling in the output.

Request body

FieldTypeRequiredDescription
bytecode string required Luau bytecode encoded as a base64 string (plain text, no data URI prefix).
filename string optional Hint name for the script. Used in debug comments in the decompiled output.
options object optional Decompile option overrides. See Decompile Options for all available keys.
json
{
  "bytecode": "TFVBVQEAAAAAAAAAo...",
  "filename": "my_script.luau",
  "options": {
    "EmitTypeAnnotations": false,
    "CompoundAssignments": true,
    "UpvalueComments": true
  }
}
On HTTP 200 the response body is plain text — the decompiled Lua source directly. On any other status code, the response is a JSON object: {"error": "...", "detail": "..."}.
text — 200 OK
-- Decompiled by LunaUX · my_script.luau
local function greet(name) -- line: 1
    -- upvalues: none
    print("Hello, " .. name)
end

greet("world")

/api/disassemble

Produces a low-level bytecode listing — one instruction per line — without attempting full source reconstruction. Useful for analyzing individual opcodes and register usage.

Disassemble bytecode

POST/api/disassemble

Identical request schema to /api/decompile. Returns a plain-text disassembly listing on HTTP 200.

FieldTypeRequiredDescription
bytecode string required Luau bytecode encoded as a base64 string.
filename string optional Label name shown at the top of the disassembly output.
json
{
  "bytecode": "TFVBVQEAAAAAAAAAo...",
  "filename": "my_script.luau"
}
On HTTP 200 the response is a plain-text disassembly listing. On any other status code, the response is JSON: {"error": "...", "detail": "..."}.
text — 200 OK
; proto #0  my_script.luau  line 1
  [0]  LOADK     R0, K0        ; "world"
  [1]  GETGLOBAL R1, K1        ; greet
  [2]  CALL      R1, 1, 1
  [3]  RETURN    R0, 0

/api/cfg

Returns the full control-flow graph (CFG) for each proto (function closure) in the bytecode, as structured JSON. Nodes represent basic blocks; edges represent control-flow transitions.

Extract control-flow graph

POST/api/cfg

Requires only the bytecode field — filename is not used since the response is always JSON. Returns a structured graph object on HTTP 200.

FieldTypeRequiredDescription
bytecode string required Luau bytecode encoded as a base64 string. No filename field is accepted.
json
{
  "bytecode": "TFVBVQEAAAAAAAAAo..."
}

Response schema

Node

idstringunique block ID
labelstringdisplay label
codestring[]list of instructions

Edge

fromstringsource node ID
tostringtarget node ID
labelstring?"T" or "F" (optional)
kindstring?"true" | "false"
json — 200 OK
{
  "maindebug_id": 0,
  "protos": {
    "0": {
      "debug_name": null,
      "line_defined": 0,
      "code": {
        "entry": "entry",
        "nodes": [
          { "id": "entry",   "label": "entry",  "code": ["LOADK R0, K0", "GETGLOBAL R1, K1", "CALL R1, 1, 1"] },
          { "id": "branch",  "label": "branch", "code": ["TEST R0", "JMP cond"] },
          { "id": "then_bb", "label": "then",   "code": ["ADD R2, R0, R1", "SETGLOBAL R2"] },
          { "id": "else_bb", "label": "else",   "code": ["LOADK R3, K2", "RETURN R3, 1"] },
          { "id": "merge",   "label": "merge",  "code": ["GETGLOBAL R4, K3", "RETURN R4, 1"] }
        ],
        "edges": [
          { "from": "entry",   "to": "branch"  },
          { "from": "branch",  "to": "then_bb", "label": "T", "kind": "true"  },
          { "from": "branch",  "to": "else_bb", "label": "F", "kind": "false" },
          { "from": "then_bb", "to": "merge"   },
          { "from": "else_bb", "to": "merge"   }
        ]
      }
    }
  }
}

Status Codes

All three endpoints share the same error response format. On success (200) the body varies by endpoint; on any error the body is always JSON.

Error response format: {"error": "short message", "detail": "extended description"}
CodeMeaningBody
200 OK — request succeeded Plain text (decompile / disassemble) or JSON (cfg)
400 Bad Request — invalid option or malformed body {"error": ..., "detail": ...}
401 Unauthorized — API key is missing or invalid {"error": ..., "detail": ...}
429 Too Many Requests — rate limit exceeded (500 / min) {"error": ..., "detail": ...}
498 Token Expired — API key in Authorization header has expired {"error": ..., "detail": ...}
500 Internal Server Error — unexpected failure in the decompiler pipeline {"error": ..., "detail": ...}
503 Service Unavailable — the decompiler service is not responding {"error": ..., "detail": ...}

Decompile Options

Pass any of these boolean flags in the options object of your /api/decompile request body to control output style. Toggle each option below to compare the two variants.

Loading options…