Skip to content
Documentation/Node.js SDK
On this page 1. Install and authenticate
BUILD WITH CTX

Node.js & TypeScript SDK

Build server-side memory with typed capture, recall, streaming uploads, cancellation, and durable replay.

@contexara-ai/sdk 0.1.0 · Node.js 20+Get an API key ↗

1. Install and authenticate

Use Node.js 20 or newer. The published package includes TypeScript declarations and has no runtime npm dependencies. These examples run as server-side ES modules; save executable examples with a .mjs extension. For TypeScript, use your application’s existing compiler configuration.

Terminal · Bash
npm install @contexara-ai/sdk@0.1.0
export CONTEXARA_API_KEY="ctx_live_replace_with_your_key"

On PowerShell, set the variable with the following command. Do not use a NEXT_PUBLIC_ environment variable for an API key.

PowerShell
$env:CONTEXARA_API_KEY = "ctx_live_replace_with_your_key"

Create an API key in the dashboard at /keys. Enable capture permission for writes and recall permission for searches. Keep the key on your server or in your secret manager; never expose it in browser JavaScript, a public repository, or logs. The key determines the authenticated account. A workspace ID selects an authorized destination; it does not grant access to another account.

2. Capture your first turn

Save as capture.mjs and run node capture.mjs. The constructor reads CONTEXARA_API_KEY. This is a real write; a successful response acknowledges submission, not immediate search availability.

JavaScript
import { Contexara } from "@contexara-ai/sdk";

const ctx = new Contexara();
if (!(await ctx.verify())) throw new Error("Invalid or inactive API key");
const receipt = await ctx.memory.add(
  "The deployment region is Mumbai.",
  "I will use Mumbai for deployment.",
);
console.log(receipt.eventId, receipt.turnId, receipt.status);

verify() returns false for an invalid/inactive key, but throws for network failures. A true result does not confirm capture/recall permissions or quota. AddResult contains eventId, sessionId, turnNumber, turnId, and status. Identity fields are prepared locally; status comes from the server.

3. Recall existing memories

Run after processing, or query memories already available in your account. The result is a typed RetrieveResult. Empty results are valid.

JavaScript
import { Contexara } from "@contexara-ai/sdk";

const ctx = new Contexara();
const result = await ctx.memory.search("What deployment region did we choose?", {
  limit: 5,
  scope: "general",
});
for (const memory of result.results) {
  console.log(memory.memoryId, memory.text, memory.sourceType);
}
if (!result.results.length) console.log("No matching memories yet.");

SDK models use camelCase; JSON on the wire uses snake_case. Memory fields include memoryId, text, rrfScore, entities, eventTime, recordTime, sourceType, sourceId, modality, contentHash, pageNumber, slideNumber, mimeType, and speaker. Optional provenance can be null. Entity name, text, and type are all optional. rrfScore is not a confidence percentage; retain server ordering. timingsMs is an extensible numeric map. Successful HTTP bodies are validated at runtime, not only by TypeScript.

Capture parameters and sessions

ArgumentMeaning
textFirst argument; non-blank user text, maximum 100,000 characters.
assistantMessageSecond argument; optional text or null, maximum 100,000 characters.
options.sessionIdStable conversation ID, maximum 512 characters.
options.turnNumberExplicit integer starting at 1, or local allocation if omitted.
options.projectId / workspaceIdAuthorized destination.
options.signalAbortSignal for the network operation.

A client creates one default session ID and increments turn numbers locally. For an application handling multiple conversations, pass one stable session ID per conversation. Counters are in memory, not shared between workers or persisted across restarts. For resumed or distributed sessions, allocate turn numbers atomically in your own durable storage and pass them explicitly. Never mix automatic and explicit numbering for the same session in one client. Start a new session when the intended project or workspace changes. Each distinct session consumes one of the client registry’s 100,000 slots; existing sessions continue working at capacity, but new sessions are rejected.

When passing options without an assistant reply, pass null as the second argument.

JavaScript
import { randomUUID } from "node:crypto";
import { Contexara } from "@contexara-ai/sdk";

const ctx = new Contexara();
const sessionId = randomUUID(); // Persist for subsequent turns.
await ctx.memory.add("Compare Rust and Go.", null, { sessionId });
await ctx.memory.add("Choose Rust for the parser.", null, { sessionId });

Project and workspace scope

Use general for unassigned memories in the selected workspace, or project with an active project ID. Omit the workspace ID for your personal workspace; pass an authorized workspace ID for a team workspace. A project must belong to that workspace. Do not pass a project ID with general scope. all_active is outside the supported stable contract. Capture and recall destinations are separate: explicitly use the same intended project and workspace in both calls.

Replace this project UUID with an active project you own. The fragment assumes an existing ctx client.

JavaScript
const result = await ctx.memory.search("deployment region", {
  scope: "project",
  projectId: "11111111-1111-4111-8111-111111111111",
  limit: 10,
});
// Add workspaceId for an authorized team workspace.
SearchOptions fieldDefault / constraint
limit10; integer 1–50.
scopegeneral or project.
projectIdRequired for project; omit for general.
workspaceIdOmitted means personal workspace.
deep / forceFastfalse; cannot both be true.
signalOptional AbortSignal.

Build context for your model

Format an already-fetched result locally to avoid a second retrieval call. The formatter preserves memory IDs and source labels.

JavaScript
import { formatContextBlock } from "@contexara-ai/sdk";

// result is the RetrieveResult returned by memory.search().
const context = formatContextBlock(result, 500);
console.log(context);

ctx.contextBlock(query, { tokenBudget: 500 }) performs its own general search. It does not accept project/workspace scope or a signal. For scoped or cancellable retrieval, call memory.search() first, then formatContextBlock(). The default count is a character-based approximation. Supply { tokenCounter: text => tokenizer.encode(text).length } as the third formatter argument for model-specific counting. Empty results produce an empty string. Keep retrieved content separate from trusted model instructions.

Deep Recall

Deep Recall is opt-in and can fall back to Fast Recall when plan or quota conditions do not allow synthesis. A completed Deep Recall run may still have a null synthesis. Check whether Deep Recall ran separately from whether an answer was produced. Its final result count is independently budgeted: limit constrains the initial Fast Recall pass, not the final synthesized result set. Do not enable deep and force-fast together.

JavaScript
import { deepRecallRan } from "@contexara-ai/sdk";

const result = await ctx.memory.search("Why did we choose Mumbai?", { deep: true });
if (deepRecallRan(result) && result.synthesis != null) {
  console.log(result.synthesis);
} else {
  for (const memory of result.results) console.log(memory.text);
}

Durable capture and replay

prepareAdd() is synchronous and does not send a request. toPersistable() serializes the prepared event; fromPersistable() validates and rehydrates it. This single-event file example uses exclusive creation. A multi-worker service should use a transactional outbox and atomically allocate turnNumber in its own durable store.

JavaScript
import { readFile, writeFile } from "node:fs/promises";
import { Contexara, toPersistable, fromPersistable } from "@contexara-ai/sdk";

const ctx = new Contexara();
const path = "pending-capture.json";
let saved;
try {
  saved = await readFile(path, "utf8");
} catch (error) {
  if (error.code !== "ENOENT") throw error;
  const event = ctx.memory.prepareAdd("The deployment region is Mumbai.");
  saved = JSON.stringify(toPersistable(event));
  await writeFile(path, saved, { flag: "wx" });
}
const receipt = await ctx.memory.send(fromPersistable(JSON.parse(saved)));
console.log(receipt.eventId, receipt.status);
// Record delivery in your outbox after acknowledgement.
// Re-running this example replays the same logical event.

Upload documents and images

Create notes.md before running this example. Pass a file path, or a binary buffer with filename and mimeType options. upload() returns an async generator; consume it with for await.

JavaScript
import { Contexara, StreamError } from "@contexara-ai/sdk";

const ctx = new Contexara();
try {
  for await (const event of ctx.upload("notes.md", {
    caption: "Roadmap notes", explain: false,
  })) {
    if (event.attachmentId) console.log("Stored:", event.attachmentId);
    if (event.chunk) process.stdout.write(event.chunk);
  }
} catch (error) {
  if (error instanceof StreamError) {
    console.error("Partial attachment:", error.attachmentId);
  }
  throw error;
}

UploadOptions includes caption, explain, sessionId, projectId, workspaceId, groupId, filename, mimeType, and signal. workspaceId maps to explicit_workspace_id on the wire. groupId is meaningful for image batches.

File typeAccepted MIME typeMaximum bytes
PNG / JPEG / WebPimage/png, image/jpeg, image/webp20,000,000
PDFapplication/pdf50,000,000
DOCXapplication/vnd.openxmlformats-officedocument.wordprocessingml.document20,000,000
PPTXapplication/vnd.openxmlformats-officedocument.presentationml.presentation50,000,000
TXT / Markdowntext/plain, text/markdown5,000,000

Timeouts, retries, and cancellation

Default policy: 5,000 ms timeout, one retry, 200 ms base backoff, 4,000 ms maximum backoff, and no overall deadline. Backoff uses jitter. Verify and Fast Recall retry transient failures within the configured budget; Deep Recall retries at most once because generation may already have incurred cost. Capture retries reuse the unchanged prepared event. Uploads never retry automatically. A usable Retry-After header is honored for 429 responses; a 429 without it is not automatically retried. An overall deadline includes attempts and backoff for ordinary requests; upload has separate connection/read-idle behavior rather than one total stream deadline.

JavaScript
import { Contexara, ResiliencePolicy, CancellationError } from "@contexara-ai/sdk";

const ctx = new Contexara({
  resilience: new ResiliencePolicy({
    timeoutMs: 5000, maxRetries: 1, deadlineMs: 8000,
  }),
});
const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), 3000);
try {
  await ctx.memory.search("deployment region", { signal: controller.signal });
} catch (error) {
  if (error instanceof CancellationError) console.error("Caller cancelled");
  else throw error;
} finally {
  clearTimeout(timer);
}

Caller cancellation produces CancellationError and is not retried. An SDK timeout produces ContexaraTimeoutError. Cancellation cannot undo a write the server already accepted. Signals apply to verify, add/send, search, and upload network calls; local prepareAdd and formatting do not need them.

Handle failures explicitly

ConditionSDK exceptionWhat to do
401AuthenticationErrorReplace an invalid or inactive key. verify returns false for this status.
402QuotaExceededErrorCheck account or team usage and allowance.
403PermissionDeniedErrorCheck key scope, capture/recall preferences, and workspace membership.
400 / 422 or local validationValidationErrorCorrect the request; do not retry unchanged.
409ConflictErrorInvestigate turn identity in synchronous capture deployments.
429RateLimitErrorInspect retry-after; distinguish rate limits from upload allowance limits.
Malformed successful responseResponseValidationErrorPreserve diagnostic details and investigate schema drift.
TimeoutContexaraTimeoutErrorOutcome may be uncertain; apply operation-specific replay rules.
SSE failureStreamErrorInspect partial attachment metadata before deciding next steps.
Other non-2xxContexaraAPIErrorInspect the status and error detail; avoid unlimited retries.

Network failures use ContexaraConnectionError. All SDK operation exceptions extend ContexaraError. RateLimitError.retryAfterSeconds may be null. Constructor configuration errors should be handled during application startup.

JavaScript
import { Contexara, ContexaraError, RateLimitError } from "@contexara-ai/sdk";

const ctx = new Contexara();
try {
  await ctx.memory.search("deployment region");
} catch (error) {
  if (error instanceof RateLimitError) {
    console.error("Retry-after seconds:", error.retryAfterSeconds);
  } else if (error instanceof ContexaraError) {
    console.error(error.name, error.message);
  }
  throw error;
}

Test without API calls

The included mock records captures and returns seeded data regardless of query text. It does not perform extraction or semantic search. This TypeScript fixture uses the real Memory interface without any casts.

TypeScript
import { MockContexaraClient, type Memory } from "@contexara-ai/sdk";

const memory: Memory = {
  memoryId: "m1", text: "Use Mumbai.", rrfScore: 0.9, entities: [],
  eventTime: null, recordTime: null, confidenceBucket: null,
  speaker: null, sourceType: "sdk", modality: null, sourceId: null,
  contentHash: null, pageNumber: null, slideNumber: null, mimeType: null,
};
const ctx = new MockContexaraClient();
ctx.seed([memory]);
const result = await ctx.memory.search("deployment region");
if (result.results[0].text !== "Use Mumbai.") throw new Error("Unexpected result");
await ctx.memory.add("Use Mumbai.");
if (ctx.recordedCaptures.length !== 1) throw new Error("Capture not recorded");

For HTTP-level tests, inject a fetch implementation through new Contexara({ apiKey: "test-key", fetch: mockFetch }). Test cancellation during retries, malformed responses, and upload streams that end without [DONE].

Hosts and supported surface

ConfigurationDefault
captureBaseUrl / CONTEXARA_CAPTURE_BASE_URLhttps://api.contexara.com
retrievalBaseUrl / CONTEXARA_RETRIEVAL_BASE_URLhttps://retrieval.contexara.com
apiKey / CONTEXARA_API_KEYRequired credential.
timeoutMs / maxRetries5000 / 1; overridden by a resilience policy.

baseUrl and CONTEXARA_BASE_URL are legacy retrieval aliases. Conflicting explicit baseUrl and retrievalBaseUrl values throw. The Node client uses fetch and has no close() method. CommonJS applications can use const { Contexara } = require("@contexara-ai/sdk").

The 0.1.0 SDK exposes verify, capture, search, context formatting, and uploads. It does not expose list, delete, chat, or stats methods. Public API keys cannot call Loop’s internal chat endpoints. Capture acknowledgements cannot report processing errors that occur later in the asynchronous worker.

← All documentation
SUGGESTED STARTING POINTS