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

# JavaScript SDK

> Use @getnexorai/sdk v0.1.44 from a Node.js server, or mount its advanced browser widget with a public web-chat key.

`@getnexorai/sdk` provides two separate surfaces:

* a server-side JavaScript client for 16 public REST operations;
* an advanced browser widget for Web Chat.

This reference is pinned to `@getnexorai/sdk` v0.1.44. It does not claim to wrap the complete REST API.

<Warning>
  Keep the two key types separate. Server code uses a secret REST key loaded from an environment variable. Browser code may use only the browser-visible, non-secret Web Chat key that starts with `nxr_pub_`.
</Warning>

## Install for server use

The SDK requires Node.js 18 or later.

```bash theme={null}
npm install @getnexorai/sdk@0.1.44
```

The package exposes the default singleton and named exports from its root. It also exposes chat types and helpers through `@getnexorai/sdk/chat`.

## Server quick start

Set `NEXOR_API_KEY` in your server's secret store. Do not commit it.

```js theme={null}
import nexor from "@getnexorai/sdk";

if (!process.env.NEXOR_API_KEY) {
  throw new Error("NEXOR_API_KEY is required");
}

nexor.init({ apiKey: process.env.NEXOR_API_KEY });

const result = await nexor.createLead({
  first_name: "Ada",
  last_name: "Lovelace",
  email: "ada@example.com",
  workflow_id: "your-workflow-uuid",
});

console.log(result.lead.id);
```

`createLead` and `createLeadsBulk` always send `skip_first_message: true`. Creating a lead through these helpers does not, by itself, mean the Agent immediately sends its ordinary first cadence message.

For dependency injection or multi-tenant server code, create independent clients instead of changing the singleton:

```js theme={null}
import { NexorClient } from "@getnexorai/sdk";

const client = new NexorClient({ apiKey: process.env.NEXOR_API_KEY });
const { workflows } = await client.listWorkflows();
```

## REST methods

All 16 methods below are available on the initialized default export and on `NexorClient`.

| Method                                        | Purpose                                                           |
| --------------------------------------------- | ----------------------------------------------------------------- |
| `createLead(input, options?)`                 | Create one lead and optionally assign it to an Agent              |
| `createLeadsBulk(inputs, options?)`           | Create up to 1,000 leads in one request                           |
| `updateLead(leadId, updates, options?)`       | Update a lead; `metadata` is shallow-merged by the API            |
| `getLead(leadId, options?)`                   | Read a lead, captured variables, engagement, and active Agent run |
| `getLeadHistory(leadId, params?, options?)`   | Page through messages, transcripts, and activity                  |
| `isLeadPaused(leadId, params?, options?)`     | Check whether human takeover paused automation                    |
| `stopAutomation(leadId, input, options?)`     | Pause automation for a specific Agent run                         |
| `resumeAutomation(leadId, input, options?)`   | Return control to the Agent and optionally resume cadence         |
| `syncLeadTags(input, options?)`               | Replace a lead's complete tag set, addressed by ID or email       |
| `listLeadMeetings(leadId, params?, options?)` | List a lead's meetings, optionally filtered by status             |
| `listWorkflows(options?)`                     | List active Agents in the account                                 |
| `listCampaigns(options?)`                     | List campaigns in the account                                     |
| `listTemplates(params?, options?)`            | List approved WhatsApp templates with optional filters            |
| `sendMessage(input, options?)`                | Send a WhatsApp or email message, or trigger a call               |
| `createMeeting(input, options?)`              | Record a booked meeting for an existing lead matched by email     |
| `createMeetingNotes(input, options?)`         | Attach a transcript, summary, and action items to a meeting       |

Use the [REST API reference](/docs/en/api/introduction) for endpoints the SDK does not wrap.

## HTTP behavior

| Setting             | Default or behavior                                              |
| ------------------- | ---------------------------------------------------------------- |
| Base URL            | `https://api.getnexor.ai`                                        |
| Authentication      | `X-API-Key`                                                      |
| Timeout             | 30 seconds                                                       |
| Automatic retries   | 2 retries for HTTP 429, HTTP 5xx, network failures, and timeouts |
| Per-request control | `signal`, `timeoutMs`, `maxRetries`, and `idempotencyKey`        |

Set `maxRetries: 0` for a non-idempotent operation when repeating it could duplicate an effect. Passing `idempotencyKey` adds an `Idempotency-Key` header; it does not, on its own, guarantee that every endpoint deduplicates the request.

```js theme={null}
await nexor.sendMessage(
  {
    lead_id: "lead-uuid",
    workflow_id: "workflow-uuid",
    channel: "email",
    subject: "Welcome",
    content: "Thanks for contacting us.",
  },
  {
    maxRetries: 0,
    timeoutMs: 15_000,
    idempotencyKey: crypto.randomUUID(),
  },
);
```

## Handle errors

The SDK exports `NexorError`, `NexorAPIError`, `NexorAuthError`, `NexorValidationError`, and `NexorNetworkError`.

```js theme={null}
import {
  NexorAPIError,
  NexorAuthError,
  NexorNetworkError,
  NexorValidationError,
} from "@getnexorai/sdk";

try {
  await nexor.getLead("lead-uuid");
} catch (error) {
  if (error instanceof NexorAuthError) {
    // The key is missing, invalid, or forbidden for this request.
  } else if (error instanceof NexorValidationError) {
    // Review the request fields before retrying.
  } else if (error instanceof NexorNetworkError) {
    // The request exhausted its network or timeout retries.
  } else if (error instanceof NexorAPIError) {
    console.error(error.status, error.code, error.requestId);
  } else {
    throw error;
  }
}
```

The v0.1.44 package currently sends `nexor-sdk-js/0.1.0` in its Node.js `User-Agent`. Do not use that header value to determine the installed package version.

## Browser widget

For most websites, use the smaller [hosted Web Chat loader](/docs/en/guides/channels/web-chat). It does not require npm and its snippet comes directly from the Agent's **Install** section.

Use the SDK widget when you need programmatic control through `initChat`, callbacks, or runtime configuration. The IIFE build exposes a global named `Nexor`.

```html theme={null}
<script src="https://unpkg.com/@getnexorai/sdk@0.1.44/dist/nexor.iife.js"></script>
<script>
  Nexor.init({ apiKey: "nxr_pub_YOUR_PUBLIC_KEY" });

  const chat = Nexor.initChat({
    workflowId: "your-workflow-uuid",
    capture: { mode: "skip" },
  });

  chat.open();
</script>
```

<Warning>
  Never place an `nxr_live_` REST key in HTML, a browser bundle, a screenshot, or a public repository. A browser key must start with `nxr_pub_`. Configure the intended domains in the Agent's Web Chat settings; that allowlist applies to the normal widget transport, not as an authorization guarantee for every optional SDK flow.
</Warning>

### Callbacks and widget handle

Pass callbacks to `initChat` when the host page needs lifecycle or conversation events.

| Callback                    | Runs when                                                |
| --------------------------- | -------------------------------------------------------- |
| `onOpen()`                  | The widget panel opens                                   |
| `onClose()`                 | The widget panel closes                                  |
| `onMessage({ role, text })` | A `user` or `bot` message is added                       |
| `onLeadCaptured(leadId)`    | The widget receives the persisted lead ID                |
| `onError(error)`            | Widget setup, capture, or turn handling reports an error |

`initChat` returns a handle with these methods:

| Method           | Effect                                                              |
| ---------------- | ------------------------------------------------------------------- |
| `open()`         | Open the panel                                                      |
| `close()`        | Close the panel                                                     |
| `toggle()`       | Switch the current open state                                       |
| `send(text)`     | Queue a user message through the same debounce path as the composer |
| `update(patch)`  | Change `clientPrompt` or `openingMessage` without remounting        |
| `destroy()`      | Stop timers, remove listeners, and remove the widget DOM            |
| `getSessionId()` | Read the current generated session ID                               |

<Note>
  `send(text)` resolves after the message is queued, not after Nexor returns or paints the reply. Use `onMessage` to observe the later bot message and `onError` to observe a failed turn.
</Note>

```js theme={null}
const chat = Nexor.initChat({
  workflowId: "your-workflow-uuid",
  onOpen: () => console.log("opened"),
  onClose: () => console.log("closed"),
  onMessage: ({ role, text }) => console.log(role, text),
  onLeadCaptured: (leadId) => console.log("lead", leadId),
  onError: (error) => console.error(error),
});

chat.open();
await chat.send("Hello"); // Queued; the reply can still be in flight.
```

### Widget request flow

The widget's current network flow is not the older `/api/public/chat` path documented in previous SDK notes.

| When                                                                                 | Route                              |
| ------------------------------------------------------------------------------------ | ---------------------------------- |
| Initial configuration, top-level                                                     | `GET /api/public/chat/config`      |
| Initial configuration, embedded alternative                                          | `GET /api/widget/v1/config`        |
| Proof-of-work attempt before visitor persistence or a chat turn                      | `GET /api/widget/v1/pow-challenge` |
| Every chat turn                                                                      | `POST /api/widget/v1/turn`         |
| Reply-recovery polling after chat activity                                           | `GET /api/widget/v1/pending`       |
| Capture-form persistence when `capture.createLeadOnSubmit` or SMS consent is enabled | `POST /api/widget/v1/visitor`      |
| Contact-request attempt in SDK v0.1.44 when `requestContact` is configured           | `POST /api/public/leads`           |
| Best-effort diagnostics                                                              | `POST /api/widget/v1/telemetry`    |

<Warning>
  In SDK v0.1.44, `requestContact` attempts `POST /api/public/leads`. Do not enable this optional flow on an untrusted public site until Nexor completes backend hardening for contact requests. Allowed domains cover the normal widget transport; they are not an authorization guarantee for `requestContact`. Use the capture form, which persists visitor details through `POST /api/widget/v1/visitor`, or create the lead from a trusted server integration.
</Warning>

The two configuration routes are alternatives: `initChat` chooses the public route at top level and the widget-scoped route in an iframe. Before visitor persistence or a chat turn, it requests a fresh proof-of-work challenge and, when one is available and solved, attaches the result. The configuration, proof-of-work, turn, pending, visitor, and telemetry routes are the normal widget transport and have route-specific request and session controls. Use the widget instead of calling them directly. The `requestContact` row is excluded from that assurance.

## Reproduce the offline browser smoke

The repository smoke rebuilds `dist/nexor.iife.js` from the pinned SDK checkout, records its SHA-256 digest, and evaluates it in an empty JSDOM page. It replaces `fetch` with a closed mock that rejects every unrecognized origin or route, then verifies the global export, mounted DOM, top-level configuration request, proof of work when available, `POST /api/widget/v1/turn`, telemetry, and rendered reply. It also checks, without exercising the conditional flows, that both language pages list the embedded configuration, pending mailbox, visitor persistence, and public contact-request routes, and that the unsupported `requestContact` path carries the required warning.

Prepare the pinned SDK worktree once, then run the smoke without Nexor credentials or network calls:

```bash theme={null}
git -C /absolute/path/to/nexor-sdk checkout e11035d723e2b86f74b8a15f05289c6ebc36187d
npm --prefix /absolute/path/to/nexor-sdk ci --ignore-scripts
npm run sdk:smoke -- /absolute/path/to/nexor-sdk
```

The final command rebuilds the pinned bundle and fails if the checkout, bundle digest, browser key prefix, routes, proof of work, reply, or documented snippets drift.
