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.