@damonbslr/activecollab-sdk
v0.1.0
Published
TypeScript SDK for the ActiveCollab API
Maintainers
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-sdkRequirements
- 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.
