JavaScript and TypeScript
sdk/typescript is a TypeScript client with no dependencies: it uses the fetch built into Node, Deno, Bun and browsers, and Node 22.18+ runs its source directly. Keep the key on your server; never ship it to a browser.
Install it from a checkout of this repository (npm links the folder, and Node runs the TypeScript there):
npm install /path/to/checkout/sdk/typescript # then: import { JersClient } from "jers-sdk"
Node does not strip types from files copied into node_modules, so a packed copy (npm pack, or --install-links) fails with ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING; import from the linked folder, or from the source path as below.
import { JersClient, choice, noul, score } from "./sdk/typescript/src/index.ts";
const jers = new JersClient(); // JERS_API_KEY, JERS_BASE_URL
const r = await jers.systemOne(
{ message: "We were charged twice for September again.", account: "ACME Logistics" },
{
route: choice("Which team should handle this?", { billing: "Charges and refunds", support: "Product problems", other: "None of these" }),
refund: noul("Is the customer asking for money back?"),
urgency: score("How urgent is this?", ["Can wait a week", "Should be handled today", "Blocking the customer now"]),
},
{ subject: "acme", memory: { compare: true } },
);
r.answers.route.choice; // typed: "billing" | "support" | "other"
r.answers.refund.noul; // probability of yes
r.memory?.without_memory; // the same call without the memory
r.warnings; // known traps in this request
r.usage.answers_billed;
new JersClient({ apiKey, baseUrl, timeoutMs, maxRetries, backoffMs }): the key falls back to JERS_API_KEY, the gateway to JERS_BASE_URL and then http://127.0.0.1:8797; by default 120 000 ms per call, 2 retries, 500 ms backoff.
Every route has a method: remember, forget, delete, memory, rulesAdd, rules, rulesDelete, feedback, quality, calibrationFit, calibration, goldenAdd, golden, goldenRun, goldenDelete, decisionsDelete, batchCreate, batchUpload, batch, batches, batchResults, batchWait, models, usage, and signup(email, { inviteCode }), which needs no key. request(method, path, body) calls any route.
await jers.memory("acme", true); // lines and rules, word for word
await jers.rulesAdd("mia", "session_result <= -30", "Mia's stop-loss is reached: she stops playing now.", { ttlSeconds: 3600 });
await jers.goldenDelete(["refund-1"]); // [] deletes nothing; no argument deletes every case
await jers.request("GET", "/v1/usage");
Options on systemOne: model, subject, memory (use, top_k, min_share, compare, placebo), robust (true or { orders: 1-5 }, choices of up to 20 options), windows (true or { combine: { question_id: "max" | "mean" | "min" } }), derive, values, cache.
Errors are classes per status (AuthenticationError, PaymentRequiredError, PermissionDeniedError, NotFoundError, ConflictError, BadRequestError, RateLimitError with retryAfter, EngineError, ServerError, ConnectionFailed). The client retries 429, 502 and 503 with backoff, and a connection that failed before the request left; a decision that may have arrived is never sent again, so it cannot be billed twice.
The source is not type-checked in this repository (tsc is not installed here); tsconfig.json has the settings for checking it. Its tests run against the real engine: python tests/real_steps/test_jers_typescript_sdk.py.
Without the SDK
const res = await fetch(`${process.env.JERS_BASE_URL}/v1/systemone`, {
method: "POST",
headers: { Authorization: `Bearer ${process.env.JERS_API_KEY}`, "Content-Type": "application/json" },
body: JSON.stringify({ state, questions, subject }),
});
const data = await res.json();
if (!res.ok) throw new Error(`${res.status} ${data.error.type}: ${data.error.message}`);