Skip to main content
Everything here assumes the package is installed and your API key is set. If it is not, start with Installation.
The examples are labelled ts but use no type annotations, so they are valid JavaScript exactly as written. Swap the import for a require() if your project is CommonJS; see module format. The one TypeScript-only section is exported types.

Quick start

Every method returns a Promise. Call initialize() once before your first operation and shutdown() when your process exits. shutdown() is safe to call more than once.
The samples on this page are B2B: they pass customer_id. On a B2C instance (user_context_isolation: "equals_customer") customer_id is not accepted, and any call carrying it is rejected with HTTP 400. Drop the key entirely and send user_id alone. customer.context.fetch() is B2B-only for the same reason. If you are not sure which shape your instance is, GET /api/v1/auth/whoami returns its user_context_isolation: equals_customer means B2C, strict means B2B.

API surface: namespaced and flat

The client exposes two call styles against the same instance. Namespaced methods mirror the Python SDK one to one, so the same call shapes work in both languages. They return the raw snake_case response.
Flat methods are the JavaScript-idiomatic surface. They return the normalised camelCase shape.
customer_id depends on your instance. On a B2B instance (user_context_isolation = strict) it is required. On a B2C instance (equals_customer) it is not accepted: the user_id is the whole identity, and a call carrying a customer_id is rejected. The SDK checks this at the call site once initialize() has learned which mode you are on, so the mistake surfaces immediately rather than as a silently empty fetch. See B2C vs B2B.
The two surfaces return different shapes on purpose. user.context.fetch() gives you the raw snake_case response; fetchUserContext() gives you the normalised camelCase one. Pick one style per codebase rather than mixing them.

Cross-scope fetch

fetch() queries every scope you supply an identifier for, in parallel, deduplicates by item id, and returns a formatted_context string ready for prompt injection.
A scope that fails is dropped with a warning rather than failing the whole call, so partial context is still returned. An InvalidInputError is the exception: a malformed request surfaces instead of degrading to an empty result.

As an LLM tool

as_tool() returns a tool definition with the scope identifiers closed over, so the model chooses the query but never whose memory to read.

Client options

Pass these to new SynapClient({ ... }):
string
Your Synap API key. Falls back to SYNAP_API_KEY when omitted.
string
API base URL. Falls back to SYNAP_BASE_URL, then the Synap Cloud default. Set it when you run a self-hosted deployment.
string
Optional. Resolved from the API key during initialize() when omitted. Falls back to SYNAP_INSTANCE_ID.
object
{ connect, read, write } in seconds. Defaults to 5s connect and 30s read.
object
{ maxAttempts, backoffBase, backoffMax, backoffJitter }. Defaults to 3 attempts with jittered exponential backoff.
boolean
default:"false"
Keep the connection warm with a periodic health ping. Worth it for a long-lived process, pointless in serverless where the container is frozen between requests.
function
Supply your own fetch. Useful for testing or for routing through a proxy.

The anticipation stream

The stream lets the server push context bundles ahead of time, which the SDK then serves locally instead of making a billed retrieval. It is opt-in.
Without the stream, every retrieval is a cloud fetch. With it, turns the server anticipated correctly are answered from a local bundle. Whether that is worth a persistent connection depends on your traffic shape.

How it differs from the Python SDK

The two SDKs share one behaviour contract and the same method set, so a Python example translates call for call. Seven things still differ, and every one of them has bitten someone.
wait_for_completion, record_message, stop_listening, batch_create, get_context_for_prompt, create_from_file. All of them keep Python’s spelling so a snippet ports without renaming.memories.waitForCompletion is undefined, not an alias, so the mistake surfaces as is not a function at the call site rather than at import.
Arguments are the opposite: both spellings work everywhere, so { user_id } and { userId } are equally valid.
Python returns a Pydantic model whose facts, preferences, episodes, emotions and temporal_events always exist, empty at worst. The namespaced JavaScript surface returns the raw JSON, where a collection the server omitted is undefined.
Reading context.facts.length on a response with no facts throws.
Python caches to SQLite (cache_backend defaults to "sqlite"), so a restarted process keeps its cache. JavaScript caches in memory.This is a billing difference, not only a latency one: a cache miss is a metered retrieval. Long-lived servers are barely affected. Short-lived processes and serverless functions will see more cloud fetches than the equivalent Python deployment.
Every error carries a stable .code. With a dual ESM/CJS dependency graph a consumer can end up holding two copies of the same error class, and instanceof then fails against the copy it was not built from.
instanceof is made to work across copies as well, but .code is the documented contract and the one to rely on at a package boundary.
storage_path, cache_backend, session_timeout_minutes, log_level and logger exist in ConfigureOptions so a config object can be shared between the two SDKs without a type error. They do nothing here: there is no SQLite backend and no global logger to replace.Timeouts and the retry policy are real, and their defaults match Python exactly: connect 5s, read 30s, write 10s, 3 attempts, backoff base 1s capped at 10s.
Each memory type names its text differently: facts, preferences and temporal events use content, episodes use summary, emotions use context. Concatenating the collections and reading .content across them is wrong in both languages.JavaScript ships a helper that flattens every collection and normalises the text into .memory:
Python has no free-function equivalent. Its format_for_prompt is a method on the cross-scope sdk.fetch() result, not on a scoped fetch.
Every HTTP path (ingestion, retrieval, profiles, credits) runs on Edge, Workers and in the browser. listen() is gRPC, so it needs Node and the two optional peers. Importing the SDK in an Edge route stays safe; only the stream is unavailable there. See Where it runs.

Exported types

Option and response types are exported for annotating your own functions:

Typed error handling

Error classes are real classes, so instanceof narrowing works, and each carries a stable .code and a .transient flag:
Transient errors are retried automatically. Ingestion is deliberately not retried when a failure leaves the outcome unknown, because a retry would store and bill twice.