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, not placeholder pages.

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

Current-month quota counters (this page's worked example).

Worked example: poll the quota endpoint

The shortest possible call — POST {APIKey, APIPwd} to /v1/ai/usage.ashx, parse JSON, print tier and actionsUsed / actionsCap. 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)
# ...

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

# 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`);

// 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: HTTP 200 always (except 405 for non-POST), ok: false signals an error with error code and human-readable message. Documented error codes: input_invalid, method_not_allowed, quota_exhausted, auth_failed, upstream_unavailable, rate_limited. 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.