API reference

Obfuscation

Upload scripts to be obfuscated and protected, and ship new builds from CI without changing the loader.

On this page

Every protected script gets a permanent loader URL. Re-uploading a script keeps that URL, so a CI job can publish a new build and players get it on their next run — nobody re-copies the loader. Endpoints need obfuscation:read / obfuscation:write.

Script source travels base64-encoded in content (up to 10 MB decoded).

Builds run as background jobs

Creating and updating a script does not return the build. The request is accepted with 202 and a jobId; the build then runs on the server — on every engine — for up to 15 minutes, and you must poll the job until its status is succeeded or failed. Closing the connection, the tab or the CI step does not stop it.

  • Idempotency-Key header — required. A unique value (8–128 characters: letters, digits, _ . : -) per logical request, e.g. a UUID. If the response is lost, send the same request again with the same key: you get the job that was already accepted (header Idempotent-Replayed: true) — the script is not built or charged twice. The same key with a different body returns 409 IDEMPOTENCY_KEY_REUSED.
  • One build per script at a time. Updating a script that already has a queued or running job returns 409 OBFUSCATION_JOB_ACTIVE with that job's id in error.details.jobId — poll it instead.
  • Statuses: queued → running → succeeded | failed. A running job goes through the stages preparing, obfuscating and saving; there is no percentage. Jobs wait in the queue for at most 1 hour.
  • Polling: every 2 seconds is plenty; wait for each response before sending the next request. A network error while polling says nothing about the job — retry with a growing delay (up to 30 s).
  • On failure the previous build, source and loader URL stay live, and tokens or plan quota taken for the run are given back.
error.codeMeaning
ENGINE_ERRORThe engine rejected the script; message carries its reason.
ENGINE_RATE_LIMITED, ENGINE_UNAVAILABLEThe engine is over its limit or unreachable. Try again later.
DEADLINE_EXCEEDEDThe build did not finish within 15 minutes.
QUEUE_TIMEOUTThe job waited in the queue for over an hour.
WORKER_INTERRUPTEDThe worker stopped while a paid engine had the script and the result is unknown; nothing was charged.
PERMISSION_REVOKED, SCRIPT_DELETEDYou lost edit access, or the script was deleted, before the build was published.
BYOK_KEY_MISSING, INSUFFICIENT_TOKENS, LIMIT_EXCEEDEDThe engine's API key, tokens or plan limit ran out before the run.
STORAGE_ERROR, INTERNAL_ERRORThe build could not be saved, or something else broke. Try again.

Obfuscate a script

POST/api/obfuscation/obfuscateobfuscation:write

Starts obfuscating a new script and answers 202 with the job. Poll the job; when it succeeds, result.uuid is the script's UUID — keep it to update the script later.

curl -X POST "https://api.santiauth.com/api/obfuscation/obfuscate" \
  -H "x-api-key: $SANTIAUTH_API_KEY" \
  -H "Idempotency-Key: 5f0c2a1e-8d4b-4c7a-9e21-3b6f0d9a7c11" \
  -H "Content-Type: application/json" \
  -d '{
  "serviceId": 42,
  "type": "SANTIOBF",
  "fileName": "main.lua",
  "content": "cHJpbnQoIkhlbGxvIGZyb20gU2FudGlBdXRoIik=",
  "library": "v4",
  "options": {
    "preset": "Strong"
  }
}'
202 Accepted
{
  "success": true,
  "data": {
    "jobId": "0b6c7f0e-3a51-4d0e-9a8c-2f1e4d5b6a70",
    "status": "queued"
  }
}
FieldTypeNotes
serviceIdinteger, requiredService the script belongs to.
typeenum, requiredObfuscator: SANTIOBF, LURAPH, IRONBREW or WYNFUSCATOR.
fileNamestring, requiredUp to 64 chars.
contentstring, requiredScript source, base64-encoded.
libraryenumKey library to embed: v4, v1 or none (default).
optionsobject, requiredDepends on type. SANTIOBF: { "preset": "Medium" | "Strong" }. LURAPH: its full option set (TARGET_VERSION, OPTIMIZATION_LEVEL, …). Others: free-form flags.

Update a script

PUT/api/obfuscation/{uuid}obfuscation:write

Starts re-obfuscating new source under the same UUID and loader URL, and answers 202 with the job. Takes the same body as Obfuscate a script; serviceId can be left out. Players keep getting the current build until the new one is published.

curl -X PUT "https://api.santiauth.com/api/obfuscation/3f2b8c1e-4d5a-4e6f-9a7b-8c9d0e1f2a3b" \
  -H "x-api-key: $SANTIAUTH_API_KEY" \
  -H "Idempotency-Key: 9d3e1b7a-4f2c-4e8d-a6b5-1c0f7e2d3a94" \
  -H "Content-Type: application/json" \
  -d '{
  "type": "SANTIOBF",
  "fileName": "main.lua",
  "content": "cHJpbnQoIkhlbGxvIGZyb20gU2FudGlBdXRoIHYyIik=",
  "library": "v4",
  "options": {
    "preset": "Strong"
  }
}'
202 Accepted
{
  "success": true,
  "data": {
    "jobId": "7a2d9c4e-1b3f-4e6a-8d0c-5f9e2b1a3c48",
    "status": "queued"
  }
}

Get a job

GET/api/obfuscation/jobs/{jobId}obfuscation:read

Status of a build. queuePosition is the number of jobs ahead of it on its engine while it is queued. On succeeded, result.uuid is the script; on failed, error has a code from the table above and a message safe to show.

curl "https://api.santiauth.com/api/obfuscation/jobs/3f2b8c1e-4d5a-4e6f-9a7b-8c9d0e1f2a3b" \
  -H "x-api-key: $SANTIAUTH_API_KEY"
200 OK
{
  "success": true,
  "data": {
    "jobId": "7a2d9c4e-1b3f-4e6a-8d0c-5f9e2b1a3c48",
    "kind": "update",
    "status": "running",
    "stage": "obfuscating",
    "stages": [
      {
        "name": "preparing",
        "status": "done",
        "startedAt": "2026-10-22T10:00:02.000Z",
        "finishedAt": "2026-10-22T10:00:03.000Z"
      },
      {
        "name": "obfuscating",
        "status": "running",
        "startedAt": "2026-10-22T10:00:03.000Z",
        "finishedAt": null
      },
      {
        "name": "saving",
        "status": "pending",
        "startedAt": null,
        "finishedAt": null
      }
    ],
    "engine": "SANTIOBF",
    "serviceId": 42,
    "scriptId": "9b1f6a3e-2c4d-4e5f-8a9b-0c1d2e3f4a5b",
    "fileName": "main.lua",
    "queuePosition": null,
    "createdAt": "2026-10-22T10:00:00.000Z",
    "startedAt": "2026-10-22T10:00:02.000Z",
    "finishedAt": null,
    "deadlineAt": "2026-10-22T10:15:02.000Z",
    "result": null,
    "error": null
  }
}

List jobs

GET/api/obfuscation/jobsobfuscation:read

A service's queued and running jobs plus those finished in the last 24 hours, newest first (up to 20). Finished jobs are kept for 7 days.

curl "https://api.santiauth.com/api/obfuscation/jobs?service_id=42" \
  -H "x-api-key: $SANTIAUTH_API_KEY"

List scripts

GET/api/obfuscationobfuscation:read

Protected scripts of a service, paginated with limit (default 20) and offset.

curl "https://api.santiauth.com/api/obfuscation?service_id=660e8400-e29b-41d4-a716-446655440001" \
  -H "x-api-key: $SANTIAUTH_API_KEY"

Get the original source

GET/api/obfuscation/{uuid}/sourceobfuscation:read

The source you uploaded, before obfuscation, in data.source.

curl "https://api.santiauth.com/api/obfuscation/3f2b8c1e-4d5a-4e6f-9a7b-8c9d0e1f2a3b/source" \
  -H "x-api-key: $SANTIAUTH_API_KEY"

Get execution stats

GET/api/obfuscation/{uuid}/statsobfuscation:read

How often the script ran over the last range = 7, 14 (default), 30 or 90 days; 90 needs a plan with 90-day analytics.

curl "https://api.santiauth.com/api/obfuscation/3f2b8c1e-4d5a-4e6f-9a7b-8c9d0e1f2a3b/stats?range=30" \
  -H "x-api-key: $SANTIAUTH_API_KEY"

Delete a script

DELETE/api/obfuscation/{uuid}obfuscation:write

Deletes the script. Its loader URL stops working immediately for every player.

curl -X DELETE "https://api.santiauth.com/api/obfuscation/3f2b8c1e-4d5a-4e6f-9a7b-8c9d0e1f2a3b" \
  -H "x-api-key: $SANTIAUTH_API_KEY"

Deploy from CI

A GitHub Actions step that ships src/main.lua on every push to main. It starts the build, then polls the job until it finishes (up to 1 hour in the queue plus 15 minutes of build, so the samples wait 80 minutes); a retry after a lost response reuses the same Idempotency-Key.

# SANTIAUTH_API_KEY and SCRIPT_UUID are repository secrets; the key needs obfuscation:write and obfuscation:read.
set -euo pipefail
API="https://api.santiauth.com/api/obfuscation"
KEY="ci-${GITHUB_RUN_ID:-local}-${GITHUB_RUN_ATTEMPT:-1}"
jq -n --arg content "$(base64 < src/main.lua | tr -d '\n')" \
  '{type: "SANTIOBF", fileName: "main.lua", content: $content, library: "v4", options: {preset: "Strong"}}' > body.json
JOB=$(curl -fsS --retry 3 --retry-all-errors -X PUT "$API/$SCRIPT_UUID" \
  -H "x-api-key: $SANTIAUTH_API_KEY" \
  -H "Idempotency-Key: $KEY" \
  -H "Content-Type: application/json" \
  --data-binary @body.json | jq -r .data.jobId)
END=$((SECONDS + 80 * 60))  # 1 h of queueing + a 15-minute run
while [ "$SECONDS" -lt "$END" ]; do
  JOB_JSON=$(curl -fsS --retry 5 --retry-all-errors --max-time 10 "$API/jobs/$JOB" -H "x-api-key: $SANTIAUTH_API_KEY")
  STATUS=$(jq -r .data.status <<< "$JOB_JSON")
  if [ "$STATUS" = succeeded ]; then echo "Deployed"; exit 0; fi
  if [ "$STATUS" = failed ]; then jq -r '.data.error | "\(.code): \(.message)"' <<< "$JOB_JSON"; exit 1; fi
  sleep 2
done
echo "Timed out waiting for job $JOB"; exit 1