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月25日T00:00:00Z
# rotation review: 2026年08月24日T00: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.