unreadable.gg

agents

Agents can use the same API as any other program. There's also an MCP server, and every page has a markdown version.

pages for agents

llms.txt lists these docs. llms-full.txt has every page in one file.

Every page is also markdown. Add .md to its address, like /docs/api.md, or send Accept: text/markdown.

If your agent can run commands, use curl, like in the quickstart. The file goes straight from disk and never enters the model's context. Add -o result.json and only read the fields you need, like verdict and notes.

The full schema is at https://api.unreadable.gg/v1/openapi.json, with every option, note code and error code.

mcp

POSThttps://api.unreadable.gg/mcp

The MCP server is stateless and only speaks revision 2026-07-28. There's no initialize and no session. A client that starts with initialize (the older handshake) gets an "unsupported protocol version" error, so your client has to support the stateless revision.

Each request carries the protocol version and client capabilities in _meta. Its MCP-Protocol-Version, Mcp-Method and Mcp-Name headers must match the body.

It answers server/discover, tools/list, tools/call and subscriptions/listen.

Without a key, calls use your connection's free decompiles. To use a member's account, send Authorization: Bearer ugk_... with a key from the account page, same as the API.

toolwhat it does
decompileDecompiles one module from base64 bytecode. Returns a handle, the verdict, line and function counts, and notes by code. Includes the source if it's 120 lines or less. Takes the same options as the API. Uses one free decompile.
decompile_manyDecompiles up to 16 named modules with one set of options. Returns a handle and summary for each, or its error. The same bytes under two names are decompiled once. Each unique module uses one free decompile. For more, or a whole .rbxl/.rbxm, use the site or the stream.
list_functionsEvery function in source order: id, name, first and last line, parent, and note count.
get_functionOne function's source and notes, by id or name, with the functions it calls and is called by.
get_sourceThe whole source, or a range of lines. To change the code, patch and recompile the whole source.
search_sourceThe lines that contain a text or match a pattern, each with the function it's in.
list_notesThe notes, or only the ones with one code.
compareWhat changed between two handles: functions added, removed and changed, matched by name, with a diff of each changed one. Free.
explain_switchWhat one switch or preset does. No handle needed. Free.

Each note says which function it's about. get_function only lists calls to local functions by name, since those are the ones the decompiler can prove. Calls through tables or globals aren't listed.

Pick the preset by what the source is for:

presetfor
faithfulAuditing, patching and recompiling, or matching a crash's line numbers.
balancedUnderstanding the code.
readableSkimming.

handles

To keep the file out of the model's context, send it from disk to /v1/handles, then read the result with the tools:

copy
curl https://api.unreadable.gg/v1/handles \
  -H "Content-Type: application/octet-stream" \
  --data-binary @module.luac

It takes the same request and key as /v1/decompile, and answers like the decompile tool.

A handle works for one hour. The result is encrypted with a key that only exists in the handle, so nobody can read it without the handle, us included (privacy).

If a tool returns handle-not-ready in the first two minutes, call it again after retryAfter seconds.

To send up to 16 modules at once, use multipart/form-data. Make each module a file part, named by its filename. Add an optional options part with the options as JSON. The whole body is at most 8 MiB, and it answers like decompile_many:

copy
curl https://api.unreadable.gg/v1/handles \
  -F file=@a.luac -F file=@b.luac \
  -F 'options={"preset":"balanced"}'

Each file is held to your file size limit: 2 MiB free, 8 MiB for members.