# Node SDK

> The statejar package on npm, a typed client for StateJar's memory API in Node 20.3 or later. It stores what a message says and returns only the memory a question needs, as the system message for your own model, and it never sends a request you did not make.

## What it does

- **Store.** `ingest({ sessionTag, text })` sends one message. StateJar extracts typed facts from it, refuses a value that fails its type, and stores a new version named by its handle (`shm_…`). No transcript is stored.
- **Recall.** `recall({ sessionTag, query, audit })` returns `memory_context`, the system message for your model: the whole state while it is small, only the fields the question needs once it is large. `audit: true` writes the disclosure to your audit log as an audited turn.
- **Read and restore.** `state(handle)`, `versions(sessionTag)`, `restore({ handle, sessionTag })`, `auditLog({ limit, offset, sessionTag })`, `auditReplay(requestId)`, `usage()` and `health()`.
- **Careful by design.** `baseUrl` is required, so nothing reaches a server you did not name. Redirects are refused. Every call has a timeout and takes an `AbortSignal`. Nothing is retried on its own, since a repeated write could store a message twice. A developer key is refused in a browser unless you opt in. A key never appears in an error message.

The package is ESM with its types included, and it has no dependencies. StateJar never calls a model for you: your model key stays in your code.

## Install

```bash
npm install statejar
```

You need Node 20.3 or later. npm serves version 0.2.1 (checked on 27 Oct 2026).

## Set it up

Make a developer key on the [API keys page](/api-keys) and put it in `STATEJAR_API_KEY`. In Node the client reads it when you pass no `apiKey`; in a browser it never does. There is no `npx statejar login` yet, but the key the Python package's [`statejar login`](/docs/cli.html) stores works here too.

| Option | Default | What it does |
|---|---|---|
| `baseUrl` | none, required | The API root, such as `https://api.statejar.com/api/v1`. An origin alone gets `/api/v1`. |
| `apiKey` | `STATEJAR_API_KEY` in Node | The developer key, sent as the `X-API-Key` header. |
| `timeoutMs` | `30000` | Milliseconds a call may take. Each call also takes `{ signal, timeoutMs }`. |
| `dangerouslyAllowBrowserApiKey` | `false` | Lets a developer key be used in a browser, where anyone who loads the page can read it. Call StateJar from your server instead. |

A `sessionTag` names the conversation (1 to 100 characters). A new tag's first message builds on your newest memory in any tag.

## Check it works

The first-turn example from [getting started](/docs/getting-started.html#6-remember-and-recall), saved as `first-turn.mjs`:

```js
import { StateJarClient } from "statejar";

const sj = new StateJarClient({
  baseUrl: process.env.STATEJAR_BASE_URL ?? "https://api.statejar.com/api/v1",
  // apiKey: STATEJAR_API_KEY is read automatically in Node (never in a browser)
});

const tag = "user-42";                     // one tag per conversation
await sj.ingest({ sessionTag: tag, text: "I'm Meera from Pune and I prefer email." });
await sj.ingest({ sessionTag: tag, text: "My budget is ₹3,500." });

const query = "What is my budget?";
const hit = await sj.recall({ sessionTag: tag, query, audit: true });
console.log(hit.handle_used);       // shm_…
console.log(hit.memory_context);    // the system message for your model
```

`node first-turn.mjs` prints the handle, then the facts your model would get:

![npm install of the package, then node first-turn.mjs: a handle, then memory_context with the name, city, contact preference and budget](/media/casts/node-first-turn.svg)

## What it sends

- **To the API you name:** the text of each message you `ingest` and each question you `recall` with, with your key in the `X-API-Key` header. Nothing else, and nothing you did not call.
- **Never:** your model key or your model's replies. The package does not call a model.
- **On your machine:** nothing. The package stores no state and keeps no log.
- **What StateJar stores:** typed facts, never the messages. Every recall is in your [audit log](/audit), and deleting your account deletes all of it.

## Troubleshooting

Every error is a `StateJarError` with a `kind`, the HTTP `status` when a response arrived, and the server's `detail`.

- **`config`:** `baseUrl` is missing, not an absolute `http(s)` URL, or carries a query string.
- **`auth` (401):** the key is missing, wrong, revoked or expired, or it belongs to another server (staging and production keys differ).
- **`rate_limited` (429):** a limit of your plan ([what each limit counts](/docs/pricing-and-payments.html)). `detail` carries the server's answer.
- **`unavailable` or `timeout`:** the request never completed: DNS, a proxy, a wrong `baseUrl`, or a server slower than `timeoutMs`.
- **`redirect`:** the server answered with a redirect, which the client refuses so your key is never sent on to another host. Use the address the redirect pointed to, such as `https://` for `http://`.
- **"refusing to use a developer API key in a browser":** call StateJar from your server, where the key stays private.
