Documentation

Guides for protecting production JavaScript

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://www.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://www.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://www.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://www.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://www.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://www.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://www.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://www.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://www.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://www.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.

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