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

# Node.js SDK

> The official Nixflex client for JavaScript and TypeScript

The official SDK wraps every Nixflex endpoint in typed methods, with retries, pagination, and typed errors built in. It has no runtime dependencies and works on Node 18 or newer.

<CardGroup cols={2}>
  <Card title="npm" icon="npm" href="https://www.npmjs.com/package/nixflex">
    `nixflex` on the npm registry
  </Card>

  <Card title="Source" icon="github" href="https://github.com/nixflex/nixflex-node">
    Read the code, open an issue
  </Card>
</CardGroup>

## Install

```bash theme={null}
npm install nixflex
```

## Authenticate

Pass your full credential - both halves joined by a colon, exactly as the dashboard shows it. The SDK sets the header for you.

```javascript theme={null}
import Nixflex from 'nixflex';

const client = new Nixflex({ apiKey: process.env.NIXFLEX_API_KEY });   // "nxf_xxx:nxfs_xxx"
```

CommonJS works too:

```javascript theme={null}
const { Nixflex } = require('nixflex');
```

| Option       | Default                   | Notes                                          |
| ------------ | ------------------------- | ---------------------------------------------- |
| `apiKey`     | required                  | `KEY_ID:KEY_SECRET`. Keep it server-side.      |
| `baseUrl`    | `https://api.nixflex.com` | Override for testing against a mock server.    |
| `timeout`    | `30000`                   | Per-request timeout in milliseconds.           |
| `maxRetries` | `1`                       | Retries after a rate limit or network failure. |

<Warning>
  Never use the SDK in browser or mobile client code - it needs your API secret. Call it from your own backend and expose only what your app needs.
</Warning>

## Agents

```javascript theme={null}
const agent = await client.agents.create({
  name: 'Acme Dental Receptionist',
  system_prompt: 'You are the front-desk assistant at Acme Dental...',
  welcome_message: 'Hello, Acme Dental, how can I help?',
});

await client.agents.list();
await client.agents.get(agent.agent_id);
await client.agents.update(agent.agent_id, { voice_id: 'some-voice' });
await client.agents.delete(agent.agent_id);
```

## Calls

```javascript theme={null}
// Outbound - returns as soon as the call is queued
const call = await client.calls.create({
  agent_id: agent.agent_id,
  to_number: '+447386172392',
  prompt: 'Remind {name} about their appointment at {time}.',
  dynamic_vars: { name: 'Sarah', time: 'Tuesday 2pm' },
});

// History
const recent = await client.calls.list({ limit: 20 });
const one = await client.calls.get(call.call_id);

// GDPR erasure - irreversible
await client.calls.delete(call.call_id);
await client.calls.deleteAll();
```

<Note>
  Outbound calls use `to_number`. A single SMS uses `to`. The SDK's types enforce the difference, so an editor with TypeScript will catch it before you run the code.
</Note>

## Phone numbers

```javascript theme={null}
await client.phoneNumbers.import({
  phone_number: '+447446466847',
  agent_id: agent.agent_id,
  twilio_sid: process.env.TWILIO_SID,
  twilio_token: process.env.TWILIO_TOKEN,
});

const { phone_numbers, count } = await client.phoneNumbers.list();

await client.phoneNumbers.update('+447446466847', {
  custom_prompt: 'You are the receptionist for the Camden branch...',
  sms_reply_enabled: true,
});

await client.phoneNumbers.setMonitor('+447446466847', true);
await client.phoneNumbers.setWebCalls('+447446466847', true);
await client.phoneNumbers.delete('+447446466847');
```

Telnyx numbers use `telnyx_api_key` and `telnyx_connection_id` instead of the Twilio pair - the carrier is inferred from which credentials you send. `null` on an update field means *inherit the agent's setting*; see [Update phone number](/api-reference/phone-numbers/update).

## SMS

```javascript theme={null}
await client.sms.send({
  agent_id: agent.agent_id,
  to: '+447386172392',
  message: 'Your appointment is confirmed for Tuesday at 2pm.',
});

const campaign = await client.sms.campaigns.create({
  agent_id: agent.agent_id,
  from_number: '+447446466847',        // Twilio number
  name: 'March reminders',
  message_template: 'Hi {{name}}, your check-up is due.',
  recipients: [{ phone: '+447386172392', variables: { name: 'Sarah' } }],
  schedule_type: 'now',
});

await client.sms.campaigns.launch(campaign.campaign_id);
await client.sms.campaigns.get(campaign.campaign_id);
await client.sms.campaigns.list({ status: 'done' });
await client.sms.campaigns.delete(campaign.campaign_id);
```

## Campaigns (voice)

```javascript theme={null}
const batch = await client.campaigns.create({ /* see Batch create */ });
await client.campaigns.launch(batch.campaign_id);
```

## Usage and keys

```javascript theme={null}
const usage = await client.usage.get();
console.log(usage.total_calls, usage.total_minutes, usage.total_cost);
console.log(usage.peak_concurrent_this_month, 'of', usage.max_concurrent);

const rotated = await client.keys.rotate();   // new secret shown once
```

## Webhooks

Configure a number's post-call webhook, and verify deliveries:

```javascript theme={null}
await client.webhooks.set('+447446466847', 'https://api.yourapp.com/nixflex');
await client.webhooks.get('+447446466847');
await client.webhooks.delete('+447446466847');
```

Verification works standalone or from the client. Pass the **raw** body - a re-serialized object will not match the signature:

```javascript theme={null}
import express from 'express';
import { verifyWebhookSignature } from 'nixflex';

app.post('/nixflex', express.raw({ type: 'application/json' }), (req, res) => {
  const ok = verifyWebhookSignature(
    req.body,
    req.get('x-nixflex-signature'),
    process.env.NIXFLEX_KEY_SECRET,     // the nxfs_ half of your key
  );
  if (!ok) return res.sendStatus(400);

  const event = JSON.parse(req.body.toString('utf8'));
  res.sendStatus(200);
});
```

It returns `false` for a tampered body, wrong secret, malformed header, or a signature older than 300 seconds (override with `{ toleranceSeconds }`). It never throws. Full details: [Webhooks](/advanced/webhooks#signing-and-verification).

## Pagination

Every list endpoint has an iterator that fetches pages as you consume them:

```javascript theme={null}
for await (const call of client.calls.iter({ limit: 100 })) {
  console.log(call.call_id, call.duration_ms);
}
```

`client.agents.iter()` works the same way. Or page manually with `limit` and `offset`.

## Errors

Failed requests throw a typed error you can branch on. Every one carries `status`, `code`, `type`, `message`, `docUrl`, `details`, and `requestId` for support.

```javascript theme={null}
import { NixflexRateLimitError, NixflexPaymentRequiredError, NixflexError } from 'nixflex';

try {
  await client.calls.create({ /* ... */ });
} catch (err) {
  if (err instanceof NixflexRateLimitError) {
    console.log(`Slow down - retry in ${err.retryAfterSeconds}s`);
  } else if (err instanceof NixflexPaymentRequiredError) {
    console.log('Top up your balance');
  } else if (err instanceof NixflexError) {
    console.error(err.status, err.code, err.message, err.requestId);
  }
}
```

| Class                         | Status | Meaning                                          |
| ----------------------------- | ------ | ------------------------------------------------ |
| `NixflexInvalidRequestError`  | 400    | Missing or invalid parameters.                   |
| `NixflexAuthenticationError`  | 401    | Bad or missing credential.                       |
| `NixflexPaymentRequiredError` | 402    | Out of credit.                                   |
| `NixflexNotFoundError`        | 404    | No such resource on your account.                |
| `NixflexRateLimitError`       | 429    | Too many requests. Carries `retryAfterSeconds`.  |
| `NixflexServerError`          | 5xx    | Something failed on our side.                    |
| `NixflexConnectionError`      | -      | Network failure or timeout before a response.    |
| `NixflexError`                | -      | Base class - catch this to handle any SDK error. |

## Retries

The SDK retries carefully, never blindly:

* **429** - retried once, honouring the `Retry-After` header.
* **Network failure or timeout** - retried, because no request reached us.
* **5xx** - retried only for `GET` and `DELETE`.
* **`POST` after a 5xx** - never retried. A create request that may have succeeded is not repeated, so a call is never dialled twice.

## TypeScript

Types ship with the package - no `@types` install. Request and response shapes are typed for every endpoint, and each method carries the field-level notes from this reference as editor tooltips.

```typescript theme={null}
import Nixflex, { type Call, type PhoneNumber } from 'nixflex';
```

## Versioning

The SDK follows [semantic versioning](https://semver.org). Published methods are never removed or renamed within a major version, so upgrades within a major are safe. See the [changelog](https://github.com/nixflex/nixflex-node/blob/main/CHANGELOG.md) before upgrading.

<Note>
  Building in another language? Every endpoint is a plain HTTPS request with a Bearer header - see the [API reference](/api-reference/introduction). The SDK is a convenience, never a requirement.
</Note>
