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-Keyheader — 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 (headerIdempotent-Replayed: true) — the script is not built or charged twice. The same key with a different body returns409 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_ACTIVEwith that job's id inerror.details.jobId— poll it instead. - Statuses:
queued→running→succeeded|failed. A running job goes through the stagespreparing,obfuscatingandsaving; 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.code | Meaning |
|---|---|
ENGINE_ERROR | The engine rejected the script; message carries its reason. |
ENGINE_RATE_LIMITED, ENGINE_UNAVAILABLE | The engine is over its limit or unreachable. Try again later. |
DEADLINE_EXCEEDED | The build did not finish within 15 minutes. |
QUEUE_TIMEOUT | The job waited in the queue for over an hour. |
WORKER_INTERRUPTED | The worker stopped while a paid engine had the script and the result is unknown; nothing was charged. |
PERMISSION_REVOKED, SCRIPT_DELETED | You lost edit access, or the script was deleted, before the build was published. |
BYOK_KEY_MISSING, INSUFFICIENT_TOKENS, LIMIT_EXCEEDED | The engine's API key, tokens or plan limit ran out before the run. |
STORAGE_ERROR, INTERNAL_ERROR | The build could not be saved, or something else broke. Try again. |
Obfuscate a script
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"
}
}'{
"success": true,
"data": {
"jobId": "0b6c7f0e-3a51-4d0e-9a8c-2f1e4d5b6a70",
"status": "queued"
}
}| Field | Type | Notes |
|---|---|---|
serviceId | integer, required | Service the script belongs to. |
type | enum, required | Obfuscator: SANTIOBF, LURAPH, IRONBREW or WYNFUSCATOR. |
fileName | string, required | Up to 64 chars. |
content | string, required | Script source, base64-encoded. |
library | enum | Key library to embed: v4, v1 or none (default). |
options | object, required | Depends on type. SANTIOBF: { "preset": "Medium" | "Strong" }. LURAPH: its full option set (TARGET_VERSION, OPTIMIZATION_LEVEL, …). Others: free-form flags. |
Update a script
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"
}
}'{
"success": true,
"data": {
"jobId": "7a2d9c4e-1b3f-4e6a-8d0c-5f9e2b1a3c48",
"status": "queued"
}
}Get a job
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"{
"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
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
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
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
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
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