Documentation

Call JSO AI from your client — JavaScript Obfuscator Docs

Reference guides for release workflows, command-line usage, cross-file protections, and the desktop app.

Inside the Docs

Practical guides for real release work.

How-to guides Start with release sequencing and command-line usage, then move into feature-specific references.
Advanced protection Browse cross-file controls like Replace Globals and Protect Members when a build spans multiple scripts.

Calling JSO AI from your client

All four /v1/ai/* endpoints use the same APIKey + APIPwd auth as the obfuscation API. If you already have one of our language clients wired up, you can call AI with no new credentials, just a different URL path. The wire envelope is documented in AIApi.aspx and formally specified in ai-wire-format.schema.json.

Shortcut: if you already use jso-protector from npm for obfuscation, the AI surface is built in. Use the jso CLI tab below for one-line shell scripts, or the Node (library) tab for programmatic access with full TypeScript types. The 8 hand-rolled HTTP examples that follow stay accurate for any language you don't have a JSO client for yet.

preset-suggest

Describe app → jso.config.json.

compat-check

JS source → compatibility findings.

explain-error

Runtime error → diagnosed transform + fix.

usage

Quota counters, AI key health, and safe monitoring fields.

Worked example: poll the quota endpoint

The shortest possible call — POST {APIKey, APIPwd} to /v1/ai/usage.ashx, parse JSON, print tier, actionsUsed / actionsCap, and providerKey.status. The providerKey object intentionally omits the provider secret and key suffix. Same shape works for the other three endpoints; only the URL path and the request body fields change.

# Install once.
npm install jso-protector

# Then any CI step:
export JSO_API_KEY='<base64-from-dashboard>'
export JSO_API_PASSWORD='<base64-from-dashboard>'

jso ai usage --pretty
# tier:               FreeTrial (preview mode)
# actions:            0 / 10   (10 remaining)
# tokens:             0 / 0   (0 remaining)
# AI key health:      Key ready (openai) [ready]
# next key test:      2026-06-25T00:00:00Z
# rotation review:    2026-08-24T00:00:00Z
# ...

# JSON output for monitoring tools (pipe to jq):
jso ai usage | jq -r '"\(.tier): \(.actionsRemaining) of \(.actionsCap), key=\(.providerKey.status)"'

# Pre-obfuscation gate (NEW). AI scans every input file in your jso.config
# before the obfuscation API is even called. Aborts the build on error-level
# findings — eval, Function constructor, framework reflection traps, etc.
jso ai compat-scan --config jso.config.json --fail-on error

# Or fold the same check INTO your existing obfuscation command — single
# round-trip, no second CLI to wire in:
jso-protector --config jso.config.json --ai-precheck --ai-precheck-fail-on error

Also: jso ai preset-suggest "<description>", jso ai compat-check src/app.js --framework react, jso ai explain-error "<error>". Exit codes are CI-friendly: 0 = ok, 1 = business-logic / HTTP error, 2 = argument error.

// Node 18+ — Same npm package, programmatic access. Full TypeScript types.
const { ai } = require("jso-protector");

const u = await ai.usage();
console.log(`${u.tier}: ${u.actionsRemaining} of ${u.actionsCap} actions remaining`);
if (u.providerKey) {
    console.log(`AI key: ${u.providerKey.label} (${u.providerKey.status})`);
}

// Auth resolves from JSO_API_KEY / JSO_API_PASSWORD env vars by default;
// override per-call with { apiKey, apiPassword }.

// Other endpoints:
const { suggestion } = await ai.presetSuggest({
    description: "React SaaS, balanced, lock to example.com",
});
fs.writeFileSync("jso.config.json", JSON.stringify(suggestion.config, null, 2));

const { report } = await ai.compatCheck({
    source: fs.readFileSync("src/app.js", "utf8"),
    framework: "react",
});
if (report.summary.errors > 0) process.exit(1);

const { explanation } = await ai.explainError({
    error: "Uncaught TypeError: api.charge is not a function",
});
console.log(explanation.transform, "→", explanation.fix);
curl -fsS -X POST https://javascriptobfuscator.com/v1/ai/usage.ashx \
    -H "Content-Type: application/json" \
    -d "{\"APIKey\":\"$JSO_API_KEY\",\"APIPwd\":\"$JSO_API_PASSWORD\"}" \
    | jq -r '"\(.tier): \(.actionsUsed)/\(.actionsCap) actions, \(.tokensUsed)/\(.tokensCap) tokens"'
import os, json, urllib.request

req = urllib.request.Request(
    "https://javascriptobfuscator.com/v1/ai/usage.ashx",
    data=json.dumps({
        "APIKey": os.environ["JSO_API_KEY"],
        "APIPwd": os.environ["JSO_API_PASSWORD"],
    }).encode("utf-8"),
    headers={"Content-Type": "application/json"},
    method="POST",
)
with urllib.request.urlopen(req) as r:
    body = json.loads(r.read())

if not body["ok"]:
    raise SystemExit(f"AI usage error: {body.get('error')}: {body.get('message')}")

print(f"{body['tier']}: {body['actionsUsed']}/{body['actionsCap']} actions, "
      f"{body['tokensUsed']}/{body['tokensCap']} tokens")
// Node 18+ — global fetch, no deps.
const body = await (await fetch("https://javascriptobfuscator.com/v1/ai/usage.ashx", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({
        APIKey: process.env.JSO_API_KEY,
        APIPwd: process.env.JSO_API_PASSWORD,
    }),
})).json();

if (!body.ok) throw new Error(`AI usage error: ${body.error}: ${body.message}`);
console.log(`${body.tier}: ${body.actionsUsed}/${body.actionsCap} actions, ${body.tokensUsed}/${body.tokensCap} tokens`);
package main

import (
    "bytes"
    "encoding/json"
    "fmt"
    "net/http"
    "os"
)

type usageResp struct {
    OK          bool   `json:"ok"`
    Tier        string `json:"tier"`
    ActionsUsed int    `json:"actionsUsed"`
    ActionsCap  int    `json:"actionsCap"`
    TokensUsed  int64  `json:"tokensUsed"`
    TokensCap   int64  `json:"tokensCap"`
    Error       string `json:"error"`
    Message     string `json:"message"`
}

func main() {
    body, _ := json.Marshal(map[string]string{
        "APIKey": os.Getenv("JSO_API_KEY"),
        "APIPwd": os.Getenv("JSO_API_PASSWORD"),
    })
    res, err := http.Post("https://javascriptobfuscator.com/v1/ai/usage.ashx",
        "application/json", bytes.NewReader(body))
    if err != nil { panic(err) }
    defer res.Body.Close()
    var u usageResp
    json.NewDecoder(res.Body).Decode(&u)
    if !u.OK { panic(fmt.Sprintf("AI usage: %s: %s", u.Error, u.Message)) }
    fmt.Printf("%s: %d/%d actions, %d/%d tokens\n", u.Tier, u.ActionsUsed, u.ActionsCap, u.TokensUsed, u.TokensCap)
}
using System;
using System.Net.Http;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;

class Program {
    static async Task Main() {
        var http = new HttpClient();
        var body = JsonSerializer.Serialize(new {
            APIKey = Environment.GetEnvironmentVariable("JSO_API_KEY"),
            APIPwd = Environment.GetEnvironmentVariable("JSO_API_PASSWORD"),
        });
        var res = await http.PostAsync(
            "https://javascriptobfuscator.com/v1/ai/usage.ashx",
            new StringContent(body, Encoding.UTF8, "application/json"));
        var doc = JsonDocument.Parse(await res.Content.ReadAsStringAsync());
        var root = doc.RootElement;
        if (!root.GetProperty("ok").GetBoolean())
            throw new Exception($"AI usage: {root.GetProperty("error")}: {root.GetProperty("message")}");
        Console.WriteLine($"{root.GetProperty("tier").GetString()}: " +
            $"{root.GetProperty("actionsUsed").GetInt32()}/{root.GetProperty("actionsCap").GetInt32()} actions, " +
            $"{root.GetProperty("tokensUsed").GetInt64()}/{root.GetProperty("tokensCap").GetInt64()} tokens");
    }
}
import java.net.URI;
import java.net.http.*;
import com.fasterxml.jackson.databind.*; // jackson-databind

var http = HttpClient.newHttpClient();
var body = "{\"APIKey\":\"" + System.getenv("JSO_API_KEY") +
           "\",\"APIPwd\":\"" + System.getenv("JSO_API_PASSWORD") + "\"}";
var req = HttpRequest.newBuilder(URI.create("https://javascriptobfuscator.com/v1/ai/usage.ashx"))
    .header("Content-Type", "application/json")
    .POST(HttpRequest.BodyPublishers.ofString(body)).build();
var res = http.send(req, HttpResponse.BodyHandlers.ofString());
var json = new ObjectMapper().readTree(res.body());
if (!json.get("ok").asBoolean())
    throw new RuntimeException("AI usage: " + json.get("error").asText() + ": " + json.get("message").asText());
System.out.printf("%s: %d/%d actions, %d/%d tokens%n",
    json.get("tier").asText(),
    json.get("actionsUsed").asInt(),  json.get("actionsCap").asInt(),
    json.get("tokensUsed").asLong(),  json.get("tokensCap").asLong());
require "net/http"
require "json"

uri = URI("https://javascriptobfuscator.com/v1/ai/usage.ashx")
res = Net::HTTP.post(uri,
    { APIKey: ENV["JSO_API_KEY"], APIPwd: ENV["JSO_API_PASSWORD"] }.to_json,
    "Content-Type" => "application/json")

body = JSON.parse(res.body)
abort "AI usage: #{body["error"]}: #{body["message"]}" unless body["ok"]

puts "#{body["tier"]}: #{body["actionsUsed"]}/#{body["actionsCap"]} actions, " \
     "#{body["tokensUsed"]}/#{body["tokensCap"]} tokens"
<?php
$body = json_encode([
    "APIKey" => getenv("JSO_API_KEY"),
    "APIPwd" => getenv("JSO_API_PASSWORD"),
]);
$ctx = stream_context_create([
    "http" => [
        "method"  => "POST",
        "header"  => "Content-Type: application/json\r\n",
        "content" => $body,
        "ignore_errors" => true,
    ],
]);
$res = file_get_contents("https://javascriptobfuscator.com/v1/ai/usage.ashx", false, $ctx);
$j = json_decode($res, true);
if (empty($j["ok"])) {
    fwrite(STDERR, "AI usage: {$j["error"]}: {$j["message"]}\n"); exit(1);
}
printf("%s: %d/%d actions, %d/%d tokens\n",
    $j["tier"], $j["actionsUsed"], $j["actionsCap"], $j["tokensUsed"], $j["tokensCap"]);
// Cargo.toml: reqwest = { version = "0.12", features = ["json", "blocking"] }, serde_json = "1"
use serde_json::{json, Value};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let body: Value = reqwest::blocking::Client::new()
        .post("https://javascriptobfuscator.com/v1/ai/usage.ashx")
        .json(&json!({
            "APIKey": std::env::var("JSO_API_KEY")?,
            "APIPwd": std::env::var("JSO_API_PASSWORD")?,
        }))
        .send()?
        .json()?;

    if !body["ok"].as_bool().unwrap_or(false) {
        return Err(format!("AI usage: {}: {}", body["error"], body["message"]).into());
    }
    println!("{}: {}/{} actions, {}/{} tokens",
        body["tier"].as_str().unwrap_or(""),
        body["actionsUsed"], body["actionsCap"],
        body["tokensUsed"],  body["tokensCap"]);
    Ok(())
}
import java.net.URI
import java.net.http.HttpClient
import java.net.http.HttpRequest
import java.net.http.HttpResponse
import com.fasterxml.jackson.module.kotlin.* // jackson-module-kotlin

fun main() {
    val body = """{"APIKey":"${System.getenv("JSO_API_KEY")}","APIPwd":"${System.getenv("JSO_API_PASSWORD")}"}"""
    val res = HttpClient.newHttpClient().send(
        HttpRequest.newBuilder(URI("https://javascriptobfuscator.com/v1/ai/usage.ashx"))
            .header("Content-Type", "application/json")
            .POST(HttpRequest.BodyPublishers.ofString(body)).build(),
        HttpResponse.BodyHandlers.ofString())
    val j = jacksonObjectMapper().readTree(res.body())
    if (!j["ok"].asBoolean()) error("AI usage: ${j["error"]}: ${j["message"]}")
    println("${j["tier"].asText()}: ${j["actionsUsed"]}/${j["actionsCap"]} actions, " +
            "${j["tokensUsed"]}/${j["tokensCap"]} tokens")
}

Adapting to the other three endpoints

Same auth, same JSON-envelope shape. Change only:

  • preset-suggest — URL path /v1/ai/preset-suggest.ashx, add "description": "..." to the body. Response field: suggestion.config.
  • compat-check — URL path /v1/ai/compat-check.ashx, add "source": "<the JS>" and optional "framework": "react". Response field: report.findings.
  • explain-error — URL path /v1/ai/explain-error.ashx, add "error": "..." and optional "config": "...". Response field: explanation.cause / fix / docsUrl.

Error handling is uniform: ok: false signals an error with an error code and human-readable message. Documented error codes include input_invalid, method_not_allowed, quota_exhausted, auth_failed, rate_limited, provider_error, managed-checkout states, and portal states. Validate parsed responses against ai-wire-format.schema.json in CI to catch drift early.

Quick sanity check: the Prometheus exporter is a 100-line Node reference implementation calling exactly this endpoint and turning the response into metrics. Skim it as a complete working example before adapting to your stack.

Frequently asked questions

What credentials do the AI endpoints use?

The same ones as the obfuscation API. All four of the AI endpoints authenticate with the same API key and password pair, so if you already have one of the language clients wired up you can call the AI surface with no new credentials and only a different URL path. The wire envelope is documented on the AI API page and specified formally in the published schema file.

Do we need to hand-roll HTTP requests to use the AI endpoints?

Not if you already use the npm package. The AI surface is built into it, so the CLI covers one-line shell scripts and the Node library gives programmatic access with full TypeScript types. The hand-written HTTP examples exist for languages that do not yet have a shipped client, and they stay accurate as a protocol reference regardless.

Which languages have worked examples on this page?

Python, Node, Go, .NET, Java, Ruby, PHP, Rust and Kotlin, plus plain curl for anything not listed. Each example calls the same endpoint with the same envelope, so the differences between them are language plumbing rather than protocol differences. Adapting an example to one of the other endpoints is a matter of changing the path and the request body.

How do we adapt an example to a different AI endpoint?

Change the path and the request payload; the authentication, transport and error handling stay the same across all four endpoints. That uniformity is the reason the page works through one endpoint in detail rather than repeating the full example four times.

Is there a complete working reference implementation to look at?

Yes. The Prometheus quota exporter is a roughly hundred-line Node reference implementation that calls the quota endpoint and turns the response into metrics. It is worth skimming as a complete, runnable example before adapting anything to your own stack, because it shows the request, the response handling and the error paths together.

What should a client do with response fields it does not recognise?

Ignore them, which is what every shipped client does. The API is additive: new response fields can appear over time, and existing fields are not removed or renamed. A client that rejects unknown fields will break on a change that was specifically designed to be safe, so parse what you need and pass over the rest.

Try this in the online obfuscator

Paste your own code and see this option applied, or compare plans for larger projects and the desktop app.

Try It Free See Pricing