npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

culligan-iot

v1.0.0

Published

Node.js client for the Culligan IoT API

Readme

Culligan IoT API - Node.js Client

A TypeScript/JavaScript client for the Culligan IoT API. Control your Culligan smart water softeners programmatically.

Features

  • 🔐 Authentication - Login with email/password, automatic token refresh
  • 📱 Device Discovery - List all registered devices
  • 🎛️ Device Control - Send commands to water softeners
  • 🏖️ Vacation Mode - Start/stop vacation (away) mode
  • 🔄 Bypass Mode - Control bypass mode (permanent or timed)
  • 📊 Telemetry - Request device telemetry updates
  • 💾 Token Persistence - Export/import auth state for session persistence
  • 🚀 Zero Dependencies - Uses Node.js 18+ built-in fetch

Requirements

  • Node.js 18.0.0 or higher

Installation

npm install culligan-iot

Quick Start

import { CulliganApi, isSoftener } from 'culligan-iot';

// Create API client
const api = new CulliganApi();

// Login
await api.login({
  email: '[email protected]',
  password: 'your-password'
});

console.log('Authenticated:', api.isAuthenticated());

// Get all devices
const devices = await api.getDevices();

for (const device of devices) {
  console.log(`Device: ${device.name} (${device.serialNumber})`);

  // Check if it's a water softener
  if (isSoftener(device)) {
    // Fetch device properties
    await device.update();

    console.log('  Salt Level:', device.saltLevel);
    console.log('  Vacation Mode:', device.isVacationMode);
    console.log('  Bypassed:', device.isBypassed);

    // Control the softener
    // await device.startVacationMode();
    // await device.stopVacationMode();
    // await device.startBypassMode();
    // await device.startBypassMinutes(30);
    // await device.stopBypassMode();
  }
}

API Reference

CulliganApi

Main API client for interacting with the Culligan IoT API.

const api = new CulliganApi(options?: ClientOptions);

Options

| Option | Type | Default | Description | |--------|------|---------|-------------| | baseUrl | string | Production URL | API base URL | | appId | string | Culligan App ID | Application identifier | | timeout | number | 30000 | Request timeout (ms) | | autoRefresh | boolean | true | Auto-refresh expiring tokens | | refreshBuffer | number | 600000 | Time before expiry to refresh (ms) |

Methods

Authentication
// Login with credentials
await api.login({ email: '[email protected]', password: 'secret' });

// Check authentication status
api.isAuthenticated(); // boolean

// Get time until token expires (ms)
api.getTimeUntilExpiry(); // number | null

// Manually refresh token
await api.refreshAccessToken();

// Logout (clear tokens)
api.logout();

// Export auth state for persistence
const state = api.exportAuth(); // AuthState | null

// Import previously exported auth state
api.importAuth(state);
Devices
// Get raw device registry
const registry = await api.getDeviceRegistry();

// Get typed device instances
const devices = await api.getDevices();

// Get device properties
const data = await api.getDeviceData('SERIAL_NUMBER');

// Send a command
const response = await api.sendCommand('SERIAL_NUMBER', 'telemetry.get');
User
// Get user profile
const profile = await api.getUserProfile();

// Get user metadata
const metadata = await api.getUserMetadata();

CulliganDevice

Base class for all Culligan devices.

Properties

| Property | Type | Description | |----------|------|-------------| | serialNumber | string | Device serial number | | name | string | Device name | | model | string \| undefined | Device model | | isOnline | boolean | Online status | | properties | DeviceProperty[] | Device properties (after update) |

Methods

// Fetch fresh device properties
await device.update();

// Get a specific property value
const value = device.getPropertyValue<number>('saltLevel');

// Request telemetry update
await device.requestTelemetry();

CulliganSoftener

Specialized class for water softeners (extends CulliganDevice).

Additional Properties

| Property | Type | Description | |----------|------|-------------| | saltLevel | number \| undefined | Current salt level | | currentFlow | number \| undefined | Current water flow | | waterUsageToday | number \| undefined | Water usage today | | isBypassed | boolean | Bypass mode status | | isVacationMode | boolean | Vacation mode status | | lastRegeneration | string \| undefined | Last regeneration date | | daysUntilRegeneration | number \| undefined | Days until next regen |

Methods

// Vacation mode
await softener.startVacationMode();
await softener.stopVacationMode();

// Bypass mode
await softener.startBypassMode();           // Permanent
await softener.startBypassTimedMode(3600);  // Timed (seconds)
await softener.startBypassMinutes(30);      // Convenience (minutes)
await softener.startBypassHours(2);         // Convenience (hours)
await softener.stopBypassMode();

Type Guard

import { isSoftener } from 'culligan-iot';

if (isSoftener(device)) {
  // TypeScript knows this is a CulliganSoftener
  await device.startVacationMode();
}

Token Persistence

Save and restore authentication state between sessions:

import { CulliganApi } from 'culligan-iot';
import { readFileSync, writeFileSync } from 'fs';

const api = new CulliganApi();

// Try to restore previous session
try {
  const saved = JSON.parse(readFileSync('auth.json', 'utf-8'));
  api.importAuth(saved);
  console.log('Restored session, expires in:', api.getTimeUntilExpiry(), 'ms');
} catch {
  // No saved session, login fresh
  await api.login({ email: process.env.EMAIL!, password: process.env.PASSWORD! });
}

// Save session for later
const state = api.exportAuth();
if (state) {
  writeFileSync('auth.json', JSON.stringify(state));
}

Error Handling

import {
  CulliganApi,
  CulliganAuthError,
  CulliganNotAuthedError,
  CulliganApiError,
} from 'culligan-iot';

try {
  await api.login({ email, password });
} catch (error) {
  if (error instanceof CulliganAuthError) {
    console.error('Invalid credentials');
  } else if (error instanceof CulliganNotAuthedError) {
    console.error('Not logged in');
  } else if (error instanceof CulliganApiError) {
    console.error('API error:', error.statusCode, error.message);
  }
}

License

MIT