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