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

@damonbslr/activecollab-sdk

v0.1.0

Published

TypeScript SDK for the ActiveCollab API

Readme

ActiveCollab TypeScript SDK

A modern TypeScript SDK for the ActiveCollab API with full type support and IntelliSense.

Installation

# Using bun
bun add activecollab-sdk

# Using npm
npm install activecollab-sdk

# Using yarn
yarn add activecollab-sdk

# Using pnpm
pnpm add activecollab-sdk

Requirements

  • Node.js 18+ (for native fetch support)

Quick Start

Authentication

For self-hosted ActiveCollab installations:

import { SelfHostedAuthenticator, ActiveCollabClient, configFromToken } from 'activecollab-sdk';

// Authenticate with email/password
const auth = new SelfHostedAuthenticator({
  baseUrl: 'https://my.company.com/projects',
  email: '[email protected]',
  password: 'your-password',
  clientName: 'My App',
  clientVendor: 'My Company',
});

const token = await auth.issueToken();
const client = new ActiveCollabClient(configFromToken(token));

For Cloud ActiveCollab instances:

import { CloudAuthenticator, ActiveCollabClient } from 'activecollab-sdk';

// Authenticate and get accounts
const auth = new CloudAuthenticator({
  email: '[email protected]',
  password: 'your-password',
  clientName: 'My App',
  clientVendor: 'My Company',
});

const accounts = await auth.getAccounts();
const token = await auth.issueToken(accounts[0].id);
const client = new ActiveCollabClient({
  baseUrl: token.url,
  token: token.token,
});

Two-Factor Authentication

When two-factor authentication (2FA) is enabled on your ActiveCollab account, you have two options for handling the 2FA challenge:

Option 1: Callback (Transparent)

Provide a getTwoFactorCode callback that will be called automatically when 2FA is required:

import { CloudAuthenticator } from 'activecollab-sdk';

const auth = new CloudAuthenticator({
  email: '[email protected]',
  password: 'your-password',
  clientName: 'My App',
  clientVendor: 'My Company',
  // This callback will be called if 2FA is required
  getTwoFactorCode: async () => {
    // Prompt user for code (e.g., via CLI, UI prompt, etc.)
    return await promptUser('Enter 2FA code: ');
  },
});

// Authentication happens transparently
const accounts = await auth.getAccounts();

This also works for self-hosted instances:

import { SelfHostedAuthenticator } from 'activecollab-sdk';

const auth = new SelfHostedAuthenticator({
  baseUrl: 'https://my.company.com/projects',
  email: '[email protected]',
  password: 'your-password',
  clientName: 'My App',
  clientVendor: 'My Company',
  getTwoFactorCode: async () => promptUser('Enter 2FA code: '),
});

const token = await auth.issueToken();

Option 2: Catch and Submit

Handle the TwoFactorRequiredError and submit the code manually:

import { CloudAuthenticator, TwoFactorRequiredError } from 'activecollab-sdk';

const auth = new CloudAuthenticator({
  email: '[email protected]',
  password: 'your-password',
  clientName: 'My App',
  clientVendor: 'My Company',
});

try {
  const accounts = await auth.getAccounts();
  const token = await auth.issueToken(accounts[0].id);
} catch (error) {
  if (error instanceof TwoFactorRequiredError) {
    // Prompt user for 2FA code
    const code = await promptUser('Enter 2FA code: ');
    
    // Submit the code
    await auth.submitTwoFactorCode(code);
    
    // Retry the operation
    const accounts = await auth.getAccounts();
    const token = await auth.issueToken(accounts[0].id);
  }
}

For self-hosted:

import { SelfHostedAuthenticator, TwoFactorRequiredError } from 'activecollab-sdk';

const auth = new SelfHostedAuthenticator({
  baseUrl: 'https://my.company.com/projects',
  email: '[email protected]',
  password: 'your-password',
  clientName: 'My App',
  clientVendor: 'My Company',
});

try {
  const token = await auth.issueToken();
} catch (error) {
  if (error instanceof TwoFactorRequiredError) {
    const code = await promptUser('Enter 2FA code: ');
    const token = await auth.submitTwoFactorCode(code);
  }
}

Note: Both authenticator apps codes and backup recovery codes can be used in the code field.

Token Persistence

After authenticating with 2FA once, you should store the token and reuse it for future API calls. This way, you only need to enter your 2FA code once.

import { CloudAuthenticator, ActiveCollabClient } from 'activecollab-sdk';
import { writeFile, readFile } from 'fs/promises';

// Authenticate once and get a token
const auth = new CloudAuthenticator({
  email: '[email protected]',
  password: 'your-password',
  clientName: 'My App',
  clientVendor: 'My Company',
  getTwoFactorCode: async () => promptUser('Enter 2FA code: '),
});

const accounts = await auth.getAccounts();
const token = await auth.issueToken(accounts[0].id);

// Save the token for future use
await writeFile('.token.json', JSON.stringify({
  token: token.token,
  url: token.url,
  issuedAt: new Date().toISOString(),
}));

// Later, load and reuse the token (no 2FA needed!)
const stored = JSON.parse(await readFile('.token.json', 'utf-8'));
const client = new ActiveCollabClient({
  baseUrl: stored.url,
  token: stored.token,
});

// Use the client - no re-authentication needed!
const projects = await client.projects.list();

Security Best Practices:

  • Store tokens securely (encrypted storage, secure key management, environment variables)
  • Don't commit token files to version control
  • Implement token refresh logic when tokens expire
  • Use different tokens for different environments (dev, staging, production)

See examples/token-persistence.ts for a complete working example.

Using an Existing Token

If you already have an API token:

import { ActiveCollabClient } from 'activecollab-sdk';

const client = new ActiveCollabClient({
  baseUrl: 'https://my.company.com/projects',
  token: 'your-api-token',
});

Usage Examples

Projects

// List all projects
const projects = await client.projects.list();

// Get a specific project
const project = await client.projects.get(123);

// Create a new project
const newProject = await client.projects.create({
  name: 'New Project',
  company_id: 1,
});

// Update a project
await client.projects.update(123, { name: 'Updated Name' });

// Archive a project
await client.projects.archive(123);

Tasks

// List tasks in a project
const tasks = await client.tasks.list(projectId);

// Get a specific task
const task = await client.tasks.get(projectId, taskId);

// Create a task
const newTask = await client.tasks.create(projectId, {
  name: 'New Task',
  task_list_id: taskListId,
  assignee_id: userId,
  due_on: '2024-12-31',
});

// Complete a task
await client.tasks.complete(projectId, taskId);

// Reopen a task
await client.tasks.reopen(projectId, taskId);

Task Lists

// List task lists in a project
const taskLists = await client.taskLists.list(projectId);

// Create a task list
const newList = await client.taskLists.create(projectId, {
  name: 'Sprint 1',
});

Subtasks

// List subtasks for a task
const subtasks = await client.subtasks.list(projectId, taskId);

// Create a subtask
const subtask = await client.subtasks.create(projectId, taskId, {
  body: 'Sub-item description',
  assignee_id: userId,
});

// Complete a subtask
await client.subtasks.complete(projectId, taskId, subtaskId);

// Promote a subtask to a task
const promotedTask = await client.subtasks.promoteToTask(projectId, taskId, subtaskId);

Users

// List all users
const users = await client.users.list();

// Get a specific user
const user = await client.users.get(userId);

// Invite a new user
const invitedUser = await client.users.invite({
  email: '[email protected]',
  type: 'Member',
  company_id: 1,
});

Companies

// List all companies
const companies = await client.companies.list();

// Create a company
const company = await client.companies.create({
  name: 'Client Company',
  address: '123 Main St',
});

Time Records

// List time records for a project
const timeRecords = await client.timeRecords.listByProject(projectId);

// Log time on a task
const timeRecord = await client.timeRecords.createOnTask(projectId, taskId, {
  value: 2.5, // hours
  user_id: userId,
  job_type_id: 1,
  record_date: '2024-01-15',
  summary: 'Worked on feature implementation',
});

Comments

// List comments on a task
const comments = await client.comments.listOnTask(projectId, taskId);

// Add a comment to a task
const comment = await client.comments.createOnTask(projectId, taskId, 'This looks great!');

// Add a comment to a discussion
const discussionComment = await client.comments.createOnDiscussion(
  projectId,
  discussionId,
  'I agree with this approach.',
);

Labels

// List task labels
const taskLabels = await client.labels.listTaskLabels();

// Create a project label
const label = await client.labels.createProjectLabel({
  name: 'High Priority',
  color: 'red',
});

API Info

Get information about the API and logged-in user:

const info = await client.info();
console.log(info.logged_user_email);
console.log(info.system_version);

Low-Level API Access

For endpoints not covered by resource methods, use the raw HTTP methods:

// Custom GET request
const data = await client.get('/custom-endpoint');

// Custom POST request
const result = await client.post('/custom-endpoint', { key: 'value' });

Error Handling

The SDK provides typed errors for different failure scenarios:

import { ApiError, AuthenticationError, NetworkError } from 'activecollab-sdk';

try {
  const project = await client.projects.get(999999);
} catch (error) {
  if (error instanceof ApiError) {
    console.error(`API Error: ${error.message}`);
    console.error(`HTTP Status: ${error.httpCode}`);
    
    if (error.isNotFoundError) {
      console.error('Project not found');
    }
    
    if (error.isValidationError) {
      console.error('Validation errors:', error.fieldErrors);
    }
  } else if (error instanceof AuthenticationError) {
    console.error('Authentication failed');
  } else if (error instanceof NetworkError) {
    console.error('Network error:', error.message);
  }
}

TypeScript Support

All API responses are fully typed:

import type { Project, Task, User, CreateTaskParams } from 'activecollab-sdk';

const taskParams: CreateTaskParams = {
  name: 'My Task',
  task_list_id: 1,
  due_on: '2024-12-31',
};

Available Resources

| Resource | Description | |----------|-------------| | projects | Manage projects | | tasks | Manage tasks | | taskLists | Manage task lists | | subtasks | Manage subtasks | | users | Manage users | | companies | Manage companies | | teams | Manage teams | | timeRecords | Track time | | expenses | Track expenses | | notes | Manage notes | | files | Manage files | | discussions | Manage discussions | | labels | Manage labels | | comments | Manage comments | | attachments | Manage attachments |

Configuration Options

const client = new ActiveCollabClient({
  baseUrl: 'https://my.company.com/projects',
  token: 'your-api-token',
  apiVersion: 1,           // API version (default: 1)
  timeout: 30000,          // Request timeout in ms (default: 30000)
  userAgent: 'My App/1.0', // Custom user agent
});

License

MIT

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.