> ## Documentation Index
> Fetch the complete documentation index at: https://docs.niadra.com/llms.txt
> Use this file to discover all available pages before exploring further.

# TypeScript SDK

> Every method of the @niadra/sdk package, with parameters, return value and an example.

`@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](/en/api).

## Install

```sh theme={null}
npm install @niadra/sdk
```

## The client

```typescript theme={null}
import { Niadra, handles } from "@niadra/sdk";

const niadra = new Niadra(); // reads NIADRA_API_KEY
```

```typescript theme={null}
new Niadra(options?: ClientOptions)
```

| Option           | Type                               | Default                                      | Description                                                                                      |
| ---------------- | ---------------------------------- | -------------------------------------------- | ------------------------------------------------------------------------------------------------ |
| `apiKey`         | `string`                           | `NIADRA_API_KEY`                             | A source key, `nia_sk_<live\|test>_<region>_<space>_<key_id>_<secret>`.                          |
| `baseURL`        | `string`                           | `NIADRA_BASE_URL`, then derived from the key | The key names its region and space, so the address is `https://<space>.<region>.api.niadra.com`. |
| `timeouts`       | `Partial<Timeouts>`                | see below                                    | Per-method time budgets, in milliseconds.                                                        |
| `cache`          | `Partial<CacheOptions>` or `false` | see below                                    | The per-conversation context cache; `false` turns it off.                                        |
| `queue`          | `Partial<QueueOptions>`            | see below                                    | Batching of `track()` and `action()`.                                                            |
| `strict`         | `boolean`                          | `false`                                      | Throw instead of logging and resolving with an empty result. Use it in tests.                    |
| `flushOnExit`    | `boolean`                          | `true`                                       | Flush queued events when a Node process runs out of work.                                        |
| `fetch`          | `typeof fetch`                     | global `fetch`                               | Your own implementation.                                                                         |
| `logger`         | `Logger`                           | `consoleLogger`                              | Anything with `debug`, `warn` and `error`. `silentLogger` is exported.                           |
| `defaultHeaders` | `Record<string, string>`           | `{}`                                         | Extra headers on every API request.                                                              |

`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

```typescript theme={null}
const DEFAULT_TIMEOUTS = {
  context: 300,         // context(), every view except voice
  contextVoice: 150,    // context() with view: "voice"
  navigation: 600,      // search(), timeline(), open(), objectState(), objectTimeline()
  navigationVoice: 300, // navigation through voice conversations and voice-bound tools
  write: 5_000,         // each attempt of a batch, feedback() or an upload reservation
  token: 2_000,         // subjectToken()
  upload: 60_000,       // each attempt of an uploadMedia() transfer
};
```

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.

```typescript theme={null}
new Niadra({ cache: { ttlMs: 10_000, staleWhileRevalidateMs: 600_000, maxStaleMs: 1_800_000, maxEntries: 1000 } });
```

### 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.

```typescript theme={null}
new Niadra({ queue: { flushAt: 15, flushIntervalMs: 1000, maxBatchSize: 100, maxQueueSize: 10_000, maxAttempts: 3 } });
```

## Handles

A handle identifies a subject in a channel or system. The builders set the type and scope; the server normalizes the value.

| Builder                                                               | Handle                                           |
| --------------------------------------------------------------------- | ------------------------------------------------ |
| `handles.phone(e164)`                                                 | `phone_e164`                                     |
| `handles.email(address)`                                              | `email`                                          |
| `handles.waId(value)`, `handles.waJid(value)`, `handles.waLid(value)` | WhatsApp ids                                     |
| `handles.waBsuid(value, businessAccount)`                             | `wa_bsuid`, scoped to the business account       |
| `handles.appUserId(value)`                                            | `app_user_id`                                    |
| `handles.systemId(value, system)`                                     | `system_id`, scoped to the system that issued it |
| `handles.govIdHmac(value, country)`                                   | `gov_id_hmac`                                    |
| `handles.orgRegistryHmac(value, country)`                             | `org_registry_hmac`, an organization             |
| `handles.emailDomain(domain)`                                         | `email_domain`, an organization                  |
| `handles.anonId(value)`                                               | `anon_id`                                        |

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`](/en/api/context).

```typescript theme={null}
niadra.context(params: ContextParams, options?: ContextOptions): Promise<ContextResult>
```

| Parameter         | Type                    | Default  | Description                                                                        |
| ----------------- | ----------------------- | -------- | ---------------------------------------------------------------------------------- |
| `subject`         | `Handle`                | none     | Who the pack is about. Pass `subject` or `object`, exactly one.                    |
| `object`          | `ObjectRef` or `string` | none     | Center the pack on a business object, such as `"invoice:erp:0823"`.                |
| `about`           | `Handle`                | none     | The account or partner the person acts for. Needs an active link.                  |
| `view`            | `View`                  | `"chat"` | `voice`, `chat`, `brief`, `full`, `custom`, `account`, `partner` or `task:<name>`. |
| `verification`    | `Verification`          | `"V0"`   | What the conversation proved, `V0` to `V4`, or `no_customer`.                      |
| `conversation_id` | `string`                | none     | Pins the pack and enables the cache.                                               |
| `task_id`         | `string`                | none     | The same, for an internal agent's task.                                            |
| `query`           | `string`                | none     | What the turn is about, up to 2,000 characters.                                    |
| `delta`           | `boolean`               | `false`  | Ask only for what changed since this source last read the subject.                 |
| `target`          | `TargetModel`           | none     | `{ provider, model }` of the model that will read the pack.                        |

`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:

| Field       | Description                                                                                                                               |
| ----------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
| `text`      | The pack, for the system prompt. Empty when there is nothing to inject.                                                                   |
| `suffix`    | What changes turn by turn: the delta and the live turns from other channels. It belongs at the end of the prompt.                         |
| `variables` | Named values from the pack, for templates.                                                                                                |
| `source`    | `network`, `cache`, `stale`, `fallback` or `none`.                                                                                        |
| `response`  | The API response the result was built from, with `withheld`, `verification`, `etag`, `path` and the rest; `null` when `source` is `none`. |
| `error`     | What went wrong, when `source` is `fallback` or `none`.                                                                                   |

```typescript theme={null}
const ctx = await niadra.context({
  subject: handles.phone("+14155550123"),
  view: "voice",
  verification: "V1",
  conversation_id: "call-4471",
});

const system = `${AGENT_INSTRUCTIONS}\n\n${ctx.text}`;
const messages = [...history, { role: "user", content: `${ctx.suffix}\n\n${userTurn}` }];
```

`renderSuffix(response)` and `renderLive(response)` build the same suffix from a raw response.

## search()

Searches the whole history of one subject by keyword and meaning. Maps to [`POST /v1/history/search`](/en/api/history-search).

```typescript theme={null}
niadra.search(params: SearchRequest, options?: RequestOptions): Promise<Result<SearchResponse>>
```

| Parameter                    | Type             | Default  | Description                                                                    |
| ---------------------------- | ---------------- | -------- | ------------------------------------------------------------------------------ |
| `subject`                    | `Handle`         | required | Whose history.                                                                 |
| `query`                      | `string`         | required | Natural language or keywords, up to 2,000 characters.                          |
| `about`                      | `Handle`         | none     | The organization the person acts for.                                          |
| `filters`                    | `HistoryFilters` | none     | `since`, `until`, `channels`, `categories`, `item_kinds`, `outcome`, `object`. |
| `max_tokens`                 | `number`         | `800`    | Budget of the answer, 50 to 4,000. Use 300 for voice.                          |
| `verification`               | `Verification`   | `"V0"`   | The same level the context uses.                                               |
| `conversation_id`, `task_id` | `string`         | none     | Who is asking.                                                                 |

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`.

```typescript theme={null}
const { data, error } = await niadra.search({
  subject: handles.phone("+14155550123"),
  query: "credit for missed technician visit",
  max_tokens: 300,
});
if (data?.recurrence) console.log(data.recurrence.occurrences, data.recurrence.last_resolution);
```

## timeline()

One page of the subject's history, most recent first. Maps to [`POST /v1/history/timeline`](/en/api/history-timeline).

```typescript theme={null}
niadra.timeline(params: TimelineRequest, options?: RequestOptions): Promise<Result<TimelineResponse>>
```

Takes `subject`, `about`, `filters`, `cursor`, `limit` (1 to 100, default 20), `verification` and `conversation_id`. `data` carries `items`, `next_cursor`, `withheld` and `as_of`.

```typescript theme={null}
const { data: page } = await niadra.timeline({ subject: handles.phone("+14155550123"), limit: 20 });
```

## open()

Opens one history item from `search()` or `timeline()`. Maps to [`GET /v1/history/items/{item_id}`](/en/api/history-item).

```typescript theme={null}
niadra.open(id: string, params?: OpenParams, options?: RequestOptions): Promise<Result<OpenedItem>>
```

`params` takes `verification`, `conversation_id` and `task_id`. The literal `excerpt` only comes back to keys with an elevated scope.

```typescript theme={null}
const { data: item } = await niadra.open("ep_01J2", { conversation_id: "call-4471" });
```

## objectState() and objectTimeline()

Reads of a business object. They map to [`GET /v1/objects/{object_type}/{namespace}/{external_id}`](/en/api/object) and its [timeline](/en/api/object-timeline).

```typescript theme={null}
niadra.objectState(object: ObjectRef | string, options?: RequestOptions): Promise<Result<ObjectState>>
niadra.objectTimeline(
  object: ObjectRef | string,
  params?: { cursor?: string; limit?: number },
  options?: RequestOptions,
): Promise<Result<ObjectTimeline>>
```

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.

```typescript theme={null}
const { data: invoice } = await niadra.objectState("invoice:erp:0823");
const { data: page } = await niadra.objectTimeline("invoice:erp:0823", { limit: 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`](/en/api/batch).

```typescript theme={null}
niadra.track(event: TrackEvent): string | null
```

| Field                                | Type                      | Description                                                                                                   |
| ------------------------------------ | ------------------------- | ------------------------------------------------------------------------------------------------------------- |
| `channel`                            | `string`                  | Where it happened: `whatsapp`, `voice`, `app`, `erp`. Required.                                               |
| `speaker`                            | `Speaker` or `SpeakerRef` | `customer`, `ai_agent`, `human_agent` or `system`; a bare role is shorthand for `{ role }`. Required.         |
| `kind`                               | `EventKind`               | `message` (default), `system_event` or `action`.                                                              |
| `text`                               | `string`                  | Shorthand for `content: { type: "text", text }`.                                                              |
| `content`                            | `Content`                 | Text, a transcript or a media reference.                                                                      |
| `idempotency_key`                    | `string`                  | The provider message id. A UUIDv7 is minted when you leave it out.                                            |
| `conversation_id`, `task_id`         | `string`                  | The conversation or task.                                                                                     |
| `handles`, `subjects`, `object_refs` | arrays                    | Who and what the event is about. At least one is required. `object_refs` accepts `type:namespace:id` strings. |
| `canonical_type`, `fields`           | `string`, object          | Required for a system event, such as `invoice.credited`.                                                      |
| `action`                             | `ActionInfo`              | Required for `kind: "action"`, and only valid there.                                                          |
| `occurred_at`                        | `string` or `Date`        | Defaults to now.                                                                                              |
| `context_stamp`                      | `ContextStamp`            | Which context the agent's prompt carried. Conversations and tasks set it after `markInjected()`.              |

```typescript theme={null}
niadra.track({
  channel: "whatsapp",
  conversation_id: "wa-8812",
  idempotency_key: "wamid.HBgLMTQxNTU1NTAxMjMVAgASGBQzQUQ",
  handles: [handles.phone("+14155550123")],
  speaker: "customer",
  text: "The technician never showed up. I am calling you.",
});
```

## action()

Records what an agent did in a system of record. Queued, like `track()`; recording actions needs the `act` scope.

```typescript theme={null}
niadra.action(event: ActionEvent): string | null
```

`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.

```typescript theme={null}
niadra.action({
  channel: "erp",
  task_id: "billing-7741",
  handles: [handles.systemId("48213", "crm")],
  object_refs: ["invoice:erp:0823"],
  operation: "credit",
  result: "$40 credit on the August bill",
  closes: { object: { type: "invoice", namespace: "erp", id: "0823" }, operation: "dispute" },
});
```

## 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.

```typescript theme={null}
niadra.identify(params: IdentifyParams): Promise<WriteResult>
niadra.verify(params: VerifyParams): Promise<WriteResult>
niadra.handoff(params: HandoffParams): Promise<WriteResult>
```

| Method     | Parameters                                                                                                                                                    |
| ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `identify` | `handles` (2 to 16), `method` (default `explicit_identify`), `subject_kind` (default `person`), `conversation_id`.                                            |
| `verify`   | `handle`, `method` (`otp_whatsapp`, `otp_sms`, `login`, `kba`, `network_attestation`, `human_agent`), `level`, `conversation_id` or `task_id`, `valid_until`. |
| `handoff`  | `conversation_id`, `target` (`human` or `agent`), `target_source`, `reason`, `mode` (`warm`, the default, or `cold`).                                         |

A `verify()` level above the ceiling of the source fails with `verification_not_allowed`.

```typescript theme={null}
await niadra.verify({
  handle: handles.phone("+14155550123"),
  method: "otp_whatsapp",
  level: "V3",
  conversation_id: "wa-8812",
});
```

## 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`](/en/api/feedback).

```typescript theme={null}
niadra.feedback(params: FeedbackParams): Promise<WriteResult>
```

| `action`               | Requires                                |
| ---------------------- | --------------------------------------- |
| `retract_fact`         | `fact_id`. The fact leaves every pack.  |
| `correct_fact`         | `fact_id` and `value`, the right value. |
| `resolve_open_item`    | `open_item_id`.                         |
| `conversation_outcome` | `conversation_id` and `value`.          |

`subject` is always required; `value` goes up to 2,000 characters and `reason` up to 500.

```typescript theme={null}
await niadra.feedback({
  subject: handles.phone("+14155550123"),
  action: "resolve_open_item",
  open_item_id: "oi_01J8ZK",
  reason: "Visit rescheduled",
});
```

## 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`](/en/api/media-uploads).

```typescript theme={null}
niadra.uploadMedia(
  params: { data: Uint8Array | ArrayBuffer | Blob; content_type: string; subject?: Handle },
  options?: { signal?: AbortSignal },
): Promise<Result<MediaUpload>>
```

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`.

```typescript theme={null}
const { data: upload } = await niadra.uploadMedia({
  data: recording,
  content_type: "audio/wav",
  subject: handles.phone("+14155550123"),
});
if (upload) {
  niadra.track({
    channel: "voice",
    conversation_id: "call-4471",
    handles: [handles.phone("+14155550123")],
    speaker: "customer",
    content: { type: "audio", media_ref: upload.media_ref, media_sha256: upload.media_sha256, transcript },
  });
}
```

## conversation()

A helper for one customer thread: it reads the pack the server pins, keeps the deltas, captures turns and ends the conversation.

```typescript theme={null}
niadra.conversation(params: ConversationParams): Conversation
```

| Parameter         | Type                    | Default                                        | Description                    |
| ----------------- | ----------------------- | ---------------------------------------------- | ------------------------------ |
| `subject`         | `Handle`                | required                                       | The customer.                  |
| `channel`         | `string`                | required                                       | Such as `whatsapp` or `voice`. |
| `conversation_id` | `string`                | a UUIDv7                                       | Your id for the thread.        |
| `view`            | `View`                  | `voice` on the voice channel, `chat` otherwise | The view of the pack.          |
| `verification`    | `Verification`          | `"V0"`                                         | The level already proven.      |
| `about`, `target` | `Handle`, `TargetModel` | none                                           | As in `context()`.             |

| Member                                                                       | Description                                                                                                 |
| ---------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- |
| `context(options?)`                                                          | The pinned `text`, plus a `suffix` with every delta received since the pin and the current live turns.      |
| `markInjected(context?, at?)`                                                | Records that the pack went into the prompt; the agent's next turns and actions carry it as `context_stamp`. |
| `customer(text, options?)`, `agent(text, options?)`, `human(text, options?)` | Record a turn. `agent()` carries the stamp.                                                                 |
| `track(event)`, `action(event)`                                              | As on the client, with the conversation bound.                                                              |
| `verify({ method, level, handle? })`                                         | Raises the level and reads at the new level from then on.                                                   |
| `handoff({ target, target_source?, reason?, mode? })`                        | Records a transfer.                                                                                         |
| `tools()`                                                                    | The history kit bound to this customer and conversation.                                                    |
| `end()`                                                                      | Emits `conversation.ended`.                                                                                 |
| `verification`, `timings`, `contextStamp`, `lastContext`                     | Current level, first injection and first agent turn, the last stamp, the last result.                       |

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.

```typescript theme={null}
const conv = niadra.conversation({
  subject: handles.phone("+14155550123"),
  channel: "whatsapp",
  conversation_id: "wa-8812",
});

conv.customer("The technician never showed up. I am calling you.", { idempotency_key: "wamid.001" });
const ctx = await conv.context();
conv.markInjected(ctx);
conv.agent(await callModel(ctx.text, history, ctx.suffix));
await conv.verify({ method: "otp_whatsapp", level: "V3" });
await conv.handoff({ target: "human", reason: "asked for a person" });
await conv.end();
```

## 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`.

```typescript theme={null}
niadra.task(params: TaskParams): Task
```

`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()`.

```typescript theme={null}
const task = niadra.task({
  channel: "erp",
  task_id: "billing-7741",
  object: "invoice:erp:0823",
  view: "task:billing",
  verification: "no_customer",
});
const ctx = await task.context();
task.markInjected(ctx);
task.action({
  operation: "credit",
  result: "$40 credit on the August bill",
  closes: { object: { type: "invoice", namespace: "erp", id: "0823" }, operation: "dispute" },
});
await task.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.

```typescript theme={null}
niadra.tools(subject: Handle, binding?: ToolBinding): BoundTools
```

`binding` takes `about`, `verification`, `conversation_id`, `task_id` and `voice` (use the voice time budget).

| Member             | Description                                                                                                                                                                                             |
| ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `definitions`      | `search_customer_history`, `get_customer_timeline` and `open_history_item`, as `{ type: "function", function: { name, description, parameters } }`.                                                     |
| `has(name)`        | Whether `name` is one of these tools.                                                                                                                                                                   |
| `call(name, args)` | Runs one tool call and resolves with the text for the model. `args` may be the JSON string most model APIs return. Failures come back as a short JSON error the model can read, or throw with `strict`. |

`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`](/en/api/history-tools).

```typescript theme={null}
const kit = niadra.tools(handles.phone("+14155550123"), { conversation_id: "call-4471" });
const response = await openai.chat.completions.create({ model, messages, tools: kit.definitions });
for (const call of response.choices[0].message.tool_calls ?? []) {
  if (kit.has(call.function.name)) {
    messages.push({ role: "tool", tool_call_id: call.id, content: await kit.call(call.function.name, call.function.arguments) });
  }
}
```

## 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`](/en/api/subject-tokens).

```typescript theme={null}
niadra.subjectToken(params: SubjectTokenRequest, options?: RequestOptions): Promise<Result<SubjectToken>>
```

`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](/en/guides/mcp).

```typescript theme={null}
const { data: token } = await niadra.subjectToken({
  subject: handles.phone("+14155550123"),
  conversation_id: "call-4471",
  verification: "V1",
});
```

## wrap()

Wraps an OpenAI-compatible client so every call gets the context and records the answer.

```typescript theme={null}
wrap<C extends object>(client: C, session: WrapSession | (() => WrapSession | null | undefined)): C
```

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.

```typescript theme={null}
import OpenAI from "openai";
import { wrap } from "@niadra/sdk";

const openai = wrap(new OpenAI(), conv);
const completion = await openai.chat.completions.create({ model: "gpt-4.1", messages });
```

## flush() and shutdown()

```typescript theme={null}
niadra.flush(): Promise<void>
niadra.shutdown(): Promise<void>
```

`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`.

| Class                       | When                                                                      |
| --------------------------- | ------------------------------------------------------------------------- |
| `NiadraConfigError`         | No usable key, or an option the SDK cannot honour.                        |
| `NiadraValidationError`     | A request failed validation before it left the process. Nothing was sent. |
| `NiadraTimeoutError`        | The call ran out of its time budget; `timeoutMs` says which.              |
| `NiadraConnectionError`     | The network call failed before an HTTP answer: DNS, TLS, reset.           |
| `NiadraAbortError`          | Cancelled through your `AbortSignal`.                                     |
| `NiadraAPIError`            | An error status, with `status`, `code`, `requestId` and `problem`.        |
| `NiadraAuthenticationError` | 401.                                                                      |
| `NiadraPermissionError`     | 403.                                                                      |
| `NiadraRateLimitError`      | 429, with `retryAfterMs`.                                                 |

The full code catalog is in [Errors](/en/errors). Include `requestId` when you contact support.

## Next steps

<CardGroup cols={2}>
  <Card title="Python SDK" href="/en/sdk/python">
    The same surface, synchronous and asynchronous.
  </Card>

  <Card title="Quickstart" href="/en/quickstart">
    From a key to the first delivered context.
  </Card>

  <Card title="MCP with any LLM" href="/en/guides/mcp">
    Seven tools with the customer bound by a subject\_token.
  </Card>

  <Card title="Errors" href="/en/errors">
    The code catalog and what to do with each code.
  </Card>
</CardGroup>
