{"_id":"@damonbslr/activecollab-sdk","name":"@damonbslr/activecollab-sdk","dist-tags":{"latest":"0.1.0"},"versions":{"0.1.0":{"name":"@damonbslr/activecollab-sdk","version":"0.1.0","description":"TypeScript SDK for the ActiveCollab API","type":"module","main":"./dist/index.js","module":"./dist/index.js","types":"./dist/index.d.ts","sideEffects":false,"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.js","default":"./dist/index.js"}},"scripts":{"build":"tsc","prepack":"npm run build","prepare":"npm run build","lint":"eslint src/","format":"prettier --write src/","format:check":"prettier --check src/","typecheck":"tsc --noEmit","docs":"typedoc","docs:watch":"typedoc --watch","docs:serve":"typedoc && cd docs && bunx serve"},"repository":{"type":"git","url":"git+https://github.com/DamonBslr/activecollab-sdk-typescript.git"},"homepage":"https://github.com/DamonBslr/activecollab-sdk-typescript#readme","bugs":{"url":"https://github.com/DamonBslr/activecollab-sdk-typescript/issues"},"publishConfig":{"access":"public"},"keywords":["activecollab","api","sdk","typescript"],"license":"MIT","engines":{"node":">=18.0.0"},"devDependencies":{"@eslint/js":"^9.39.2","@types/bun":"latest","@typescript-eslint/eslint-plugin":"^8.50.0","@typescript-eslint/parser":"^8.50.0","eslint":"^9.39.2","eslint-config-prettier":"^10.1.8","eslint-plugin-prettier":"^5.5.4","prettier":"^3.7.4","typedoc":"^0.28.15","typescript":"^5.0.0","typescript-eslint":"^8.50.0"},"_id":"@damonbslr/activecollab-sdk@0.1.0","gitHead":"5dd5c07328ad5404f68b730fe70edc0a06e317d0","_nodeVersion":"22.21.1","_npmVersion":"10.9.4","dist":{"integrity":"sha512-AjUbGZetM87Pbf7BXpvd0PSWpDhvuE0f3l8vHQ2tM7iWXi4f/pB3km84rZI88psLXl5IK5a7urhnGdmI0mYuHg==","shasum":"b1a392f5c5b23e332e10ad96abc0621e18b82500","tarball":"https://registry.npmjs.org/@damonbslr/activecollab-sdk/-/activecollab-sdk-0.1.0.tgz","fileCount":195,"unpackedSize":345450,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIFJ6+x9hj71lLmlLOPu9DVvH5JB0tFUMynLHLXdKt84gAiEA1Vl0VOpcoZZFKy5lS2SJqats5Y5itlbrFRGSZiGoLbM="}]},"_npmUser":{"name":"damonbslr","email":"basler.damon9@gmail.com"},"directories":{},"maintainers":[{"name":"damonbslr","email":"basler.damon9@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/activecollab-sdk_0.1.0_1781528965280_0.7104234355151697"},"_hasShrinkwrap":false}},"time":{"created":"2026-06-15T13:09:25.106Z","0.1.0":"2026-06-15T13:09:25.454Z","modified":"2026-06-15T13:09:25.604Z"},"maintainers":[{"name":"damonbslr","email":"basler.damon9@gmail.com"}],"description":"TypeScript SDK for the ActiveCollab API","homepage":"https://github.com/DamonBslr/activecollab-sdk-typescript#readme","keywords":["activecollab","api","sdk","typescript"],"repository":{"type":"git","url":"git+https://github.com/DamonBslr/activecollab-sdk-typescript.git"},"bugs":{"url":"https://github.com/DamonBslr/activecollab-sdk-typescript/issues"},"license":"MIT","readme":"# ActiveCollab TypeScript SDK\n\nA modern TypeScript SDK for the ActiveCollab API with full type support and IntelliSense.\n\n## Installation\n\n```bash\n# Using bun\nbun add activecollab-sdk\n\n# Using npm\nnpm install activecollab-sdk\n\n# Using yarn\nyarn add activecollab-sdk\n\n# Using pnpm\npnpm add activecollab-sdk\n```\n\n## Requirements\n\n- Node.js 18+ (for native fetch support)\n\n## Quick Start\n\n### Authentication\n\nFor self-hosted ActiveCollab installations:\n\n```typescript\nimport { SelfHostedAuthenticator, ActiveCollabClient, configFromToken } from 'activecollab-sdk';\n\n// Authenticate with email/password\nconst auth = new SelfHostedAuthenticator({\n  baseUrl: 'https://my.company.com/projects',\n  email: 'user@company.com',\n  password: 'your-password',\n  clientName: 'My App',\n  clientVendor: 'My Company',\n});\n\nconst token = await auth.issueToken();\nconst client = new ActiveCollabClient(configFromToken(token));\n```\n\nFor Cloud ActiveCollab instances:\n\n```typescript\nimport { CloudAuthenticator, ActiveCollabClient } from 'activecollab-sdk';\n\n// Authenticate and get accounts\nconst auth = new CloudAuthenticator({\n  email: 'user@company.com',\n  password: 'your-password',\n  clientName: 'My App',\n  clientVendor: 'My Company',\n});\n\nconst accounts = await auth.getAccounts();\nconst token = await auth.issueToken(accounts[0].id);\nconst client = new ActiveCollabClient({\n  baseUrl: token.url,\n  token: token.token,\n});\n```\n\n### Two-Factor Authentication\n\nWhen two-factor authentication (2FA) is enabled on your ActiveCollab account, you have two options for handling the 2FA challenge:\n\n#### Option 1: Callback (Transparent)\n\nProvide a `getTwoFactorCode` callback that will be called automatically when 2FA is required:\n\n```typescript\nimport { CloudAuthenticator } from 'activecollab-sdk';\n\nconst auth = new CloudAuthenticator({\n  email: 'user@company.com',\n  password: 'your-password',\n  clientName: 'My App',\n  clientVendor: 'My Company',\n  // This callback will be called if 2FA is required\n  getTwoFactorCode: async () => {\n    // Prompt user for code (e.g., via CLI, UI prompt, etc.)\n    return await promptUser('Enter 2FA code: ');\n  },\n});\n\n// Authentication happens transparently\nconst accounts = await auth.getAccounts();\n```\n\nThis also works for self-hosted instances:\n\n```typescript\nimport { SelfHostedAuthenticator } from 'activecollab-sdk';\n\nconst auth = new SelfHostedAuthenticator({\n  baseUrl: 'https://my.company.com/projects',\n  email: 'user@company.com',\n  password: 'your-password',\n  clientName: 'My App',\n  clientVendor: 'My Company',\n  getTwoFactorCode: async () => promptUser('Enter 2FA code: '),\n});\n\nconst token = await auth.issueToken();\n```\n\n#### Option 2: Catch and Submit\n\nHandle the `TwoFactorRequiredError` and submit the code manually:\n\n```typescript\nimport { CloudAuthenticator, TwoFactorRequiredError } from 'activecollab-sdk';\n\nconst auth = new CloudAuthenticator({\n  email: 'user@company.com',\n  password: 'your-password',\n  clientName: 'My App',\n  clientVendor: 'My Company',\n});\n\ntry {\n  const accounts = await auth.getAccounts();\n  const token = await auth.issueToken(accounts[0].id);\n} catch (error) {\n  if (error instanceof TwoFactorRequiredError) {\n    // Prompt user for 2FA code\n    const code = await promptUser('Enter 2FA code: ');\n    \n    // Submit the code\n    await auth.submitTwoFactorCode(code);\n    \n    // Retry the operation\n    const accounts = await auth.getAccounts();\n    const token = await auth.issueToken(accounts[0].id);\n  }\n}\n```\n\nFor self-hosted:\n\n```typescript\nimport { SelfHostedAuthenticator, TwoFactorRequiredError } from 'activecollab-sdk';\n\nconst auth = new SelfHostedAuthenticator({\n  baseUrl: 'https://my.company.com/projects',\n  email: 'user@company.com',\n  password: 'your-password',\n  clientName: 'My App',\n  clientVendor: 'My Company',\n});\n\ntry {\n  const token = await auth.issueToken();\n} catch (error) {\n  if (error instanceof TwoFactorRequiredError) {\n    const code = await promptUser('Enter 2FA code: ');\n    const token = await auth.submitTwoFactorCode(code);\n  }\n}\n```\n\n**Note:** Both authenticator apps codes and backup recovery codes can be used in the `code` field.\n\n### Token Persistence\n\nAfter 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.\n\n```typescript\nimport { CloudAuthenticator, ActiveCollabClient } from 'activecollab-sdk';\nimport { writeFile, readFile } from 'fs/promises';\n\n// Authenticate once and get a token\nconst auth = new CloudAuthenticator({\n  email: 'user@company.com',\n  password: 'your-password',\n  clientName: 'My App',\n  clientVendor: 'My Company',\n  getTwoFactorCode: async () => promptUser('Enter 2FA code: '),\n});\n\nconst accounts = await auth.getAccounts();\nconst token = await auth.issueToken(accounts[0].id);\n\n// Save the token for future use\nawait writeFile('.token.json', JSON.stringify({\n  token: token.token,\n  url: token.url,\n  issuedAt: new Date().toISOString(),\n}));\n\n// Later, load and reuse the token (no 2FA needed!)\nconst stored = JSON.parse(await readFile('.token.json', 'utf-8'));\nconst client = new ActiveCollabClient({\n  baseUrl: stored.url,\n  token: stored.token,\n});\n\n// Use the client - no re-authentication needed!\nconst projects = await client.projects.list();\n```\n\n**Security Best Practices:**\n- Store tokens securely (encrypted storage, secure key management, environment variables)\n- Don't commit token files to version control\n- Implement token refresh logic when tokens expire\n- Use different tokens for different environments (dev, staging, production)\n\nSee `examples/token-persistence.ts` for a complete working example.\n\n### Using an Existing Token\n\nIf you already have an API token:\n\n```typescript\nimport { ActiveCollabClient } from 'activecollab-sdk';\n\nconst client = new ActiveCollabClient({\n  baseUrl: 'https://my.company.com/projects',\n  token: 'your-api-token',\n});\n```\n\n## Usage Examples\n\n### Projects\n\n```typescript\n// List all projects\nconst projects = await client.projects.list();\n\n// Get a specific project\nconst project = await client.projects.get(123);\n\n// Create a new project\nconst newProject = await client.projects.create({\n  name: 'New Project',\n  company_id: 1,\n});\n\n// Update a project\nawait client.projects.update(123, { name: 'Updated Name' });\n\n// Archive a project\nawait client.projects.archive(123);\n```\n\n### Tasks\n\n```typescript\n// List tasks in a project\nconst tasks = await client.tasks.list(projectId);\n\n// Get a specific task\nconst task = await client.tasks.get(projectId, taskId);\n\n// Create a task\nconst newTask = await client.tasks.create(projectId, {\n  name: 'New Task',\n  task_list_id: taskListId,\n  assignee_id: userId,\n  due_on: '2024-12-31',\n});\n\n// Complete a task\nawait client.tasks.complete(projectId, taskId);\n\n// Reopen a task\nawait client.tasks.reopen(projectId, taskId);\n```\n\n### Task Lists\n\n```typescript\n// List task lists in a project\nconst taskLists = await client.taskLists.list(projectId);\n\n// Create a task list\nconst newList = await client.taskLists.create(projectId, {\n  name: 'Sprint 1',\n});\n```\n\n### Subtasks\n\n```typescript\n// List subtasks for a task\nconst subtasks = await client.subtasks.list(projectId, taskId);\n\n// Create a subtask\nconst subtask = await client.subtasks.create(projectId, taskId, {\n  body: 'Sub-item description',\n  assignee_id: userId,\n});\n\n// Complete a subtask\nawait client.subtasks.complete(projectId, taskId, subtaskId);\n\n// Promote a subtask to a task\nconst promotedTask = await client.subtasks.promoteToTask(projectId, taskId, subtaskId);\n```\n\n### Users\n\n```typescript\n// List all users\nconst users = await client.users.list();\n\n// Get a specific user\nconst user = await client.users.get(userId);\n\n// Invite a new user\nconst invitedUser = await client.users.invite({\n  email: 'new.user@company.com',\n  type: 'Member',\n  company_id: 1,\n});\n```\n\n### Companies\n\n```typescript\n// List all companies\nconst companies = await client.companies.list();\n\n// Create a company\nconst company = await client.companies.create({\n  name: 'Client Company',\n  address: '123 Main St',\n});\n```\n\n### Time Records\n\n```typescript\n// List time records for a project\nconst timeRecords = await client.timeRecords.listByProject(projectId);\n\n// Log time on a task\nconst timeRecord = await client.timeRecords.createOnTask(projectId, taskId, {\n  value: 2.5, // hours\n  user_id: userId,\n  job_type_id: 1,\n  record_date: '2024-01-15',\n  summary: 'Worked on feature implementation',\n});\n```\n\n### Comments\n\n```typescript\n// List comments on a task\nconst comments = await client.comments.listOnTask(projectId, taskId);\n\n// Add a comment to a task\nconst comment = await client.comments.createOnTask(projectId, taskId, 'This looks great!');\n\n// Add a comment to a discussion\nconst discussionComment = await client.comments.createOnDiscussion(\n  projectId,\n  discussionId,\n  'I agree with this approach.',\n);\n```\n\n### Labels\n\n```typescript\n// List task labels\nconst taskLabels = await client.labels.listTaskLabels();\n\n// Create a project label\nconst label = await client.labels.createProjectLabel({\n  name: 'High Priority',\n  color: 'red',\n});\n```\n\n## API Info\n\nGet information about the API and logged-in user:\n\n```typescript\nconst info = await client.info();\nconsole.log(info.logged_user_email);\nconsole.log(info.system_version);\n```\n\n## Low-Level API Access\n\nFor endpoints not covered by resource methods, use the raw HTTP methods:\n\n```typescript\n// Custom GET request\nconst data = await client.get('/custom-endpoint');\n\n// Custom POST request\nconst result = await client.post('/custom-endpoint', { key: 'value' });\n```\n\n## Error Handling\n\nThe SDK provides typed errors for different failure scenarios:\n\n```typescript\nimport { ApiError, AuthenticationError, NetworkError } from 'activecollab-sdk';\n\ntry {\n  const project = await client.projects.get(999999);\n} catch (error) {\n  if (error instanceof ApiError) {\n    console.error(`API Error: ${error.message}`);\n    console.error(`HTTP Status: ${error.httpCode}`);\n    \n    if (error.isNotFoundError) {\n      console.error('Project not found');\n    }\n    \n    if (error.isValidationError) {\n      console.error('Validation errors:', error.fieldErrors);\n    }\n  } else if (error instanceof AuthenticationError) {\n    console.error('Authentication failed');\n  } else if (error instanceof NetworkError) {\n    console.error('Network error:', error.message);\n  }\n}\n```\n\n## TypeScript Support\n\nAll API responses are fully typed:\n\n```typescript\nimport type { Project, Task, User, CreateTaskParams } from 'activecollab-sdk';\n\nconst taskParams: CreateTaskParams = {\n  name: 'My Task',\n  task_list_id: 1,\n  due_on: '2024-12-31',\n};\n```\n\n## Available Resources\n\n| Resource | Description |\n|----------|-------------|\n| `projects` | Manage projects |\n| `tasks` | Manage tasks |\n| `taskLists` | Manage task lists |\n| `subtasks` | Manage subtasks |\n| `users` | Manage users |\n| `companies` | Manage companies |\n| `teams` | Manage teams |\n| `timeRecords` | Track time |\n| `expenses` | Track expenses |\n| `notes` | Manage notes |\n| `files` | Manage files |\n| `discussions` | Manage discussions |\n| `labels` | Manage labels |\n| `comments` | Manage comments |\n| `attachments` | Manage attachments |\n\n## Configuration Options\n\n```typescript\nconst client = new ActiveCollabClient({\n  baseUrl: 'https://my.company.com/projects',\n  token: 'your-api-token',\n  apiVersion: 1,           // API version (default: 1)\n  timeout: 30000,          // Request timeout in ms (default: 30000)\n  userAgent: 'My App/1.0', // Custom user agent\n});\n```\n\n## License\n\nMIT\n\n## Contributing\n\nContributions are welcome! Please feel free to submit a Pull Request.\n","readmeFilename":"README.md","_rev":"1-011362aefad24a31b36d6d287c415fa7"}