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 🙏

© 2025 – Pkg Stats / Ryan Hefner

connectwise-manage-sdk-v3

v1.0.9

Published

TypeScript SDK for ConnectWise Manage API

Readme

ConnectWise Manage SDK v3

A TypeScript SDK for the ConnectWise Manage API with full type safety and JSDoc documentation.

Installation

npm install connectwise-manage-sdk-v3

Quick Start

import { ConnectWiseClient } from 'connectwise-manage-sdk-v3';

const client = new ConnectWiseClient({
  baseUrl: 'https://your-instance.com',
  auth: {
    username: 'company+publicKey',
    password: 'privateKey'
  },
  clientId: 'your-client-id'
});

// List open tickets
const tickets = await client.service.tickets.list({
  conditions: "status/name='Open'",
  orderBy: 'id desc',
  pageSize: 50
});

// Get a specific company
const company = await client.company.companies.get(123);

// Create a new ticket
const newTicket = await client.service.tickets.create({
  summary: 'New support ticket',
  board: { id: 1 },
  company: { id: 123 }
});

Deferred Configuration

You can create a client instance without configuration and configure it later. This is useful when credentials are loaded asynchronously or when you need to set up the client before configuration is available.

import { ConnectWiseClient, ConfigurationError } from 'connectwise-manage-sdk-v3';

// Create client without configuration
const client = new ConnectWiseClient();

// Configure later (e.g., after loading from environment)
client.configure({
  baseUrl: 'https://your-instance.com',
  auth: {
    username: process.env.CW_USERNAME!,
    password: process.env.CW_PASSWORD!
  },
  clientId: process.env.CW_CLIENT_ID
});

// Check if configured
if (client.isConfigured()) {
  const tickets = await client.service.tickets.list();
}

// If you call an endpoint before configuring, a ConfigurationError is thrown
const unconfigured = new ConnectWiseClient();
await unconfigured.service.tickets.list(); // Throws ConfigurationError

CORS Proxy Support

For browser-based applications, you can route requests through a CORS proxy:

const client = new ConnectWiseClient({
  baseUrl: 'https://your-instance.com/v4_6_release/apis/3.0',
  auth: {
    username: 'company+publicKey',
    password: 'privateKey'
  },
  clientId: 'your-client-id',
  corsProxyUrl: 'https://your-proxy.com/api/proxy'
});

When corsProxyUrl is set, all requests are POSTed to the proxy with:

  • method - HTTP method (GET, POST, PATCH, DELETE)
  • url - Target API URL
  • headers - Authorization and clientId headers
  • body - Request body (for POST/PATCH)

Available Namespaces

The SDK organizes resources into namespaces matching the ConnectWise API structure:

  • client.service - Tickets, boards, priorities
  • client.company - Companies, contacts, configurations
  • client.finance - Agreements, additions
  • client.project - Projects, phases, team members
  • client.time - Time entries, work roles, work types
  • client.expense - Expense entries, types, classifications
  • client.procurement - Catalog items, products, purchase orders
  • client.sales - Sales orders
  • client.schedule - Schedule entries
  • client.system - Members, departments, locations, documents, system info
  • client.marketing - Groups

CRUD Operations

All resources support standard CRUD operations:

List items with filtering

const tickets = await client.service.tickets.list({
  conditions: "status/name='Open' and priority/id > 3",
  childConditions: "configurations/type/name='Server'",
  orderBy: 'lastUpdated desc',
  fields: 'id,summary,status',
  page: 1,
  pageSize: 100
});

Get all items (auto-pagination)

// Automatically fetches all pages
const allTickets = await client.service.tickets.listAll({
  conditions: "closedFlag=false"
});

Get single item

const ticket = await client.service.tickets.get(12345);

Create item

const company = await client.company.companies.create({
  name: 'Acme Corp',
  identifier: 'ACME',
  status: { id: 1 }
});

Update item (PATCH)

const updated = await client.service.tickets.update(12345, [
  { op: 'replace', path: '/status/id', value: 5 },
  { op: 'replace', path: '/priority/id', value: 1 }
]);

Delete item

await client.company.companies.delete(123);

Count items

const count = await client.service.tickets.count({
  conditions: "status/name='Open'"
});

Singleton resources

Some endpoints return a single object without an ID parameter:

// Get system info (no ID needed)
const info = await client.system.systemInfo.get();
console.log(info.version);    // "v2025.1.10431"
console.log(info.isCloud);    // false
console.log(info.cloudRegion); // "NA"

Nested Resources

Some resources are nested under parent resources:

// Get notes for a specific ticket
const notes = await client.service.ticketNotes(12345).list();

// Create a note on a ticket
const note = await client.service.ticketNotes(12345).create({
  text: 'This is a note',
  internalAnalysisFlag: true
});

// Get team members for a project
const members = await client.project.projectTeamMembers(100).list();

Query Parameters

All query parameters are fully documented with JSDoc:

interface QueryParams {
  /**
   * Search results based on the fields returned in a GET.
   *
   * Operators: =, !=, <, <=, >, >=, contains, like, in, not
   * Logic: and, or
   *
   * @example "board/name='Integration' and summary='xyz'"
   */
  conditions?: string;

  /**
   * Allows searching arrays on endpoints
   * @example "communicationItems/value like '[email protected]'"
   */
  childConditions?: string;

  /**
   * Allows searching custom fields
   * @example "caption='CustomField' AND value != null"
   */
  customFieldConditions?: string;

  /**
   * Sort results
   * @example "contact/name asc"
   */
  orderBy?: string;

  /**
   * Limit returned fields
   * @example "id,name,status/id"
   */
  fields?: string;

  /** Page number (starts at 1) */
  page?: number;

  /** Results per page (max 1000, default 25) */
  pageSize?: number;
}

Error Handling

The SDK provides typed errors for different scenarios:

import {
  ConnectWiseError,
  AuthenticationError,
  ForbiddenError,
  NotFoundError,
  ValidationError,
  ConfigurationError
} from 'connectwise-manage-sdk-v3';

try {
  const ticket = await client.service.tickets.get(99999);
} catch (error) {
  if (error instanceof ConfigurationError) {
    console.log('Client not configured');
  } else if (error instanceof NotFoundError) {
    console.log('Ticket not found');
  } else if (error instanceof AuthenticationError) {
    console.log('Invalid credentials');
  } else if (error instanceof ConnectWiseError) {
    console.log(`API error: ${error.status} - ${error.message}`);
  }
}

TypeScript Types

All entities are fully typed based on the ConnectWise API:

import type { Ticket, Company, Contact, TimeEntry } from 'connectwise-manage-sdk-v3';

const processTicket = (ticket: Ticket) => {
  console.log(ticket.id, ticket.summary, ticket.status?.name);
};

Code Generation

The SDK is generated from sample API responses. All generation inputs are in the generator/ folder.

Generator Structure

generator/
├── index.ts           # Pipeline orchestrator
├── config.ts          # Paths and type definitions
├── utils.ts           # String utilities
├── schema.ts          # Type inference from samples
├── objects.json       # API endpoint mappings
├── generators/
│   ├── types.ts       # Generate TypeScript interfaces
│   ├── resources.ts   # Generate resource classes
│   ├── namespaces.ts  # Generate namespace modules
│   └── client.ts      # Generate main client
├── validators/
│   └── types.ts       # Validate generated types
└── samples/           # Sample JSON responses (gitignored)
    ├── ticket.json
    ├── company.json
    └── ...

Regenerating the SDK

After modifying samples or adding new endpoints:

npm run generate
npm run build

Adding New Endpoints

  1. Get a sample response from the ConnectWise API endpoint you want to add:

    curl -u "company+publicKey:privateKey" \
      "https://your-instance.com/v4_6_release/apis/3.0/service/tickets" \
      > generator/samples/ticket.json
  2. Add the endpoint mapping to generator/objects.json:

    {
      "ticket": {
        "path": "/service/tickets/{ticketId}",
        "operations": ["list", "get", "create", "update", "delete", "count"]
      },
      "newEntity": {
        "path": "/path/to/entities/{entityId}",
        "operations": ["list", "get", "create", "update", "delete", "count"]
      }
    }

    Available operations:

    • list - List items with pagination (list(), listAll())
    • get - Get single item by ID (get(id))
    • getOne - Get singleton resource without ID (get())
    • create - Create new item (create(data))
    • update - Update via PATCH operations (update(id, operations))
    • delete - Delete by ID (delete(id))
    • count - Count matching items (count(params))
  3. Run the generator:

    npm run generate
    npm run build

Sample Format

For most endpoints, samples should be JSON arrays containing at least one object:

[
  {
    "id": 123,
    "name": "Example",
    "status": {
      "id": 1,
      "name": "Active"
    }
  }
]

For singleton endpoints (using getOne operation), samples should be a single object:

{
  "version": "v2025.1.10431",
  "isCloud": false,
  "serverTimeZone": "Pacific Standard Time"
}

The generator will:

  • Infer TypeScript types from the sample data
  • Create resource classes with CRUD operations
  • Group resources into namespaces based on the API path
  • Validate generated types against samples

Validation

After generating code, the generator automatically validates that all generated types match the sample data. Validation warnings are displayed but don't prevent output:

Validating generated types...
  ⚠ Contact.types: expected 'array', got 'unknown[]'
  ⚠ Member.memberPersonas: expected 'array', got 'unknown[]'
Validation complete: 2 warning(s)
Writing output files...

Validation checks:

  • Property existence - All sample properties exist in generated types
  • Type matching - Inferred types match actual sample values
  • Nested objects - Recursively validates nested structures
  • Arrays - Validates array item types

Note: Empty arrays in samples are typed as unknown[] since there's no data to infer the item type.

License

MIT