Skip to main content
@niadra/sdk is the TypeScript SDK. It runs on Node 18 and later and on edge runtimes, needs only fetch, and ships ESM and CommonJS builds with full type definitions. It is open source under Apache 2.0. Every method on this page maps to a route of the API reference.

Install

The client

enabled is false when the client was built without a usable key; it then sends nothing. Create one client per process and share it: it owns the queue, the cache and the connection pool.

Safe by default, strict on request

By default every method is fail-open. Reads resolve with an empty result, track() returns null for an item it could not accept, and the reason is logged. Logs carry status codes, error codes and request ids, never handles, message text or the key. With strict: true the constructor throws configuration errors, track() throws validation errors, reads throw request errors and flush() throws lost batches.

Time budgets

Override them per client with timeouts, or per call with { timeout }. Pass { signal } to cancel a call. Reads are retried only on 421 (the space moved to another cell), at once, up to three attempts. Writes are retried on 408, 421, 429 and 5xx with exponential backoff and full jitter; other 4xx answers are never retried.

The context cache

Inside a conversation or task, packs are cached in memory:
  • younger than 10 s: returned without a request;
  • up to 10 minutes older: returned at once while one background request refreshes it;
  • when a request fails: the last good pack, if it is less than 30 minutes old;
  • at most 1,000 packs, the least recently used evicted first.
Refreshes send the cached ETag, so an unchanged pack costs a not_modified answer instead of the full text, and only one refresh per pack runs at a time. A 401 or 403 is not an outage: the cached packs go (all of them on 401, the one requested on 403), so cutting a vendor’s access also cuts what it had cached. A plain read and a delta read of one conversation share one entry, and each delta is handed out once.

The write queue

track() and action() return at once. Events leave in batches: when 15 are waiting or every second, up to 100 per request, three attempts each. When 10,000 events are waiting, new ones are dropped and logged.

Handles

A handle identifies a subject in a channel or system. The builders set the type and scope; the server normalizes the value. Builders for people take an optional { subjectKind } to mark an organization. toObjectRef("invoice:erp:0823") turns the shorthand into an ObjectRef; the id may itself contain colons.

context()

The context pack for a person, an organization or a business object. Maps to POST /v1/context.
options takes timeout, signal, headers (such as a traceparent) and cache: false to skip the cache for this call. Resolves with a ContextResult, always usable:
renderSuffix(response) and renderLive(response) build the same suffix from a raw response. Searches the whole history of one subject by keyword and meaning. Maps to POST /v1/history/search.
Every navigation call resolves to a Result: { data, error }, with exactly one of the two set. data carries items, recurrence, withheld, as_of, tokens_used and degraded.

timeline()

One page of the subject’s history, most recent first. Maps to POST /v1/history/timeline.
Takes subject, about, filters, cursor, limit (1 to 100, default 20), verification and conversation_id. data carries items, next_cursor, withheld and as_of.

open()

Opens one history item from search() or timeline(). Maps to GET /v1/history/items/{item_id}.
params takes verification, conversation_id and task_id. The literal excerpt only comes back to keys with an elevated scope.

objectState() and objectTimeline()

Reads of a business object. They map to GET /v1/objects/{object_type}/{namespace}/{external_id} and its timeline.
The state comes only from what the systems of record reported; an agent’s action counts once a system confirms it. The timeline lists system events and agent actions, newest first, never what anyone said. Object ids are record ids, not personal data, so they go in the URL; an id with a slash cannot be addressed that way. limit goes from 1 to 100, default 20.

track()

Records a message, a system event or an action. Returns at once with the item’s idempotency key, or null when the item was dropped. Maps to POST /v1/batch.

action()

Records what an agent did in a system of record. Queued, like track(); recording actions needs the act scope.
ActionEvent takes the same fields as track() plus operation (required, such as credit), result (up to 2,000 characters), purpose, closes (the open item the action fulfils, by item_id or by object and operation) and corrects_action_id. speaker defaults to ai_agent. The action stays declared until the system of record confirms it.

identify(), verify() and handoff()

These are sent at once rather than queued, and resolve to a WriteResult: { ok: true, idempotency_key, error: null } or { ok: false, idempotency_key, error }. A context() call made after identify() or verify() resolves already reflects it.
A verify() level above the ceiling of the source fails with verification_not_allowed.

feedback()

Corrects what Niadra derived about a subject. Sent at once and recorded as an event, so it is audited like any other. Maps to POST /v1/feedback.
subject is always required; value goes up to 2,000 characters and reason up to 500.

uploadMedia()

Hands a file to Niadra, such as a call recording, and resolves with the reference for its event. Media never travels inside an event. Maps to POST /v1/media/uploads.
The method hashes the bytes with Web Crypto, reserves an upload and sends the bytes straight to storage over the signed URL. It sends exactly the upload_headers the API returned, which are the headers the signature covers, and nothing else: never your key or defaultHeaders. The URL must be HTTPS, except against a local endpoint served over HTTP. Storage checks the body against the declared size and digest. With subject, the file is stored under that person, so erasing them erases it even if no event ever references it. On Node 18, Web Crypto is only exposed behind a flag. data carries media_ref, media_sha256, content_type, size_bytes and expires_at.

conversation()

A helper for one customer thread: it reads the pack the server pins, keeps the deltas, captures turns and ends the conversation.
After the first pack, each read also asks for the delta, and the conversation keeps every delta it receives, in order, in suffix, ahead of the live turns. When the server pins a new pack, after verify() for instance, the kept deltas are dropped: the new pack already has them. A read with query is a one-off and leaves them alone. Call markInjected() each time you put the pack in a prompt: it is how Niadra tells a context that arrived after the agent spoke from one the agent had and did not use.

task()

The same for an internal agent (billing, collections, triage). A task centers its pack on its object, keeps deltas and stamps like a conversation, scopes the cache and ends with task.ended.
TaskParams takes channel (required, the internal agent or system, such as billing-agent), task_id (a UUIDv7 when omitted), subject, object, about, view (default brief; use a task view such as task:billing), verification and target. A task has context(), markInjected(), agent(), track(), action(), verify(), tools() (null without a subject) and end().

tools()

The navigation kit as function-calling tools, with the customer bound in the SDK rather than in the tool arguments. The model chooses what to look for, never whom it is about, so a prompt injection has no argument to switch customers with.
binding takes about, verification, conversation_id, task_id and voice (use the voice time budget). TOOL_DEFINITIONS and TOOL_NAMES are exported. For APIs that expect { name, description, input_schema }, map function.parameters to input_schema. The definitions are also served by GET /v1/history/tools.

subjectToken()

Mints a signed token, valid for 15 minutes, that binds one customer, conversation and verification level. Call it from your backend and pass it to the MCP connection. Maps to POST /v1/subject-tokens.
params takes subject, about, conversation_id, task_id and verification. data carries token and expires_at. Send the token in the Niadra-Subject-Token header, next to the source key. See MCP with any LLM.

wrap()

Wraps an OpenAI-compatible client so every call gets the context and records the answer.
Every chat.completions.create and chat.completions.parse call through the wrapper, streaming or not, gets the pack after your leading system messages and the suffix at the end. The injection is stamped, and the model’s answer (its first choice) is recorded as the agent’s turn: at once, or when a stream ends or you stop reading it. .withResponse() keeps working and records too; .asResponse() returns the raw HTTP response, so nothing is recorded then. Pass a function instead of a session to pick one per call; when it returns null, the call passes through untouched. Nothing the wrapper does can fail your call: a context it cannot fetch is left out, and a failure to record the answer is logged, without content. injectContext(context, messages) does the placement alone, without wrapping.

flush() and shutdown()

flush() sends every queued event. Call it before a serverless function returns, or pass it to the platform on edge runtimes (ctx.waitUntil(niadra.flush())). shutdown() flushes, stops the background timer and releases the exit hook; call it from your SIGTERM handler in long-running services, because beforeExit does not fire on signals or process.exit().

Errors

Every error extends NiadraError. The full code catalog is in Errors. Include requestId when you contact support.

Next steps

Python SDK

The same surface, synchronous and asynchronous.

Quickstart

From a key to the first delivered context.

MCP with any LLM

Seven tools with the customer bound by a subject_token.

Errors

The code catalog and what to do with each code.