@workast/sdk
TypeScript library for the Workast API. Works in Node.js 18+ and in browsers.
Installation
npm install @workast/sdk
Usage
Create a token in Workast under Preferences → API. Secret API keys are server-only — passing apiKey in a browser throws.
import { Workast } from '@workast/sdk';
const workast = new Workast({ apiKey: process.env.WORKAST_API_KEY });
// shorthand: new Workast(process.env.WORKAST_API_KEY)
const task = await workast.tasks.create(listId, { text: 'Ship SDK' });
await workast.tasks.complete(task.id);
const page = await workast.tasks.list({
predicates: [{ type: 'status', attribute: 'status', comparison: 'eq', value: 'pending' }],
});
Browser / user session:
const workast = new Workast({ token: sessionToken });
// or
const workast = new Workast({ getToken: () => auth.getAccessToken() });
Configuration
| Option | Description |
|---|---|
apiKey |
Secret API token. Server-only. |
token |
User or session token. Allowed in browsers. |
getToken |
Function that returns a token (sync or async). |
baseUrl |
API host. Defaults to https://api.workast.com. |
headers |
Extra headers (for example W-USER-ID, W-TEAM-ID). Authorization is set by the client. |
fetch |
Custom fetch implementation. |
timeout |
Request timeout in milliseconds. Defaults to 30000. 0 disables the timeout. |
withHeaders(h) returns a cloned client. setHeaders(h) updates the current one.
const workast = new Workast({
apiKey: process.env.WORKAST_API_KEY,
headers: { 'W-USER-ID': userId },
});
await workast.withHeaders({ 'W-USER-ID': otherUserId }).tasks.create(listId, { text: 'Hi' });
workast.setHeaders({ 'W-TEAM-ID': teamId });
Errors
Failed requests throw a subclass of ApiError:
| Status | Error |
|---|---|
| 400 | ValidationError |
| 401 | AuthenticationError |
| 403 | PermissionError |
| 404 | NotFoundError |
| other | ApiError |
When the API body is { error: { name, message } } and name is TeamDeactivatedError, UserDeactivatedError, UserSuspendedError, or TeamSuspendedError, the SDK throws AccountError instead. AccountError extends ApiError. It has reason (the API name) and message (the API message).
import { AccountError } from '@workast/sdk';
try {
await workast.tasks.retrieve(taskId);
} catch (err) {
if (err instanceof AccountError) {
console.log(err.reason, err.message, err.status);
}
}
TimeoutError has no HTTP status. It is thrown when the request exceeds timeout.
import { NotFoundError } from '@workast/sdk';
try {
await workast.tasks.retrieve(taskId);
} catch (err) {
if (err instanceof NotFoundError) {
console.log(err.status, err.body);
}
}
Resources
tasks · lists · fields · users · searches · tags · notes · notifications · meetings · calendar.events · workflows · reactions · attachments · tokens
Methods use create / retrieve / update / list / del, plus domain verbs like complete and assign. Path ids first, body second, request options last. Types match the API reference.
Testing
@workast/sdk/mock stubs SDK methods on any Workast instance (including one your app already constructed). No real HTTP while a mock is active. It also exports examples: Public API response fixtures (examples.task, examples.list, examples.userResource, …) generated from the spec. Spread them in .resolves() and override the fields your test cares about.
import { Workast } from '@workast/sdk';
import { examples, mockWorkast } from '@workast/sdk/mock';
const workast = new Workast({ apiKey: process.env.WORKAST_API_KEY });
async function createShipTask() {
return workast.tasks.create(examples.list.id, { text: examples.task.text });
}
const mock = mockWorkast();
mock.tasks.create.on(examples.list.id, { text: examples.task.text }).resolves({
...examples.task,
text: 'Ship from my test',
});
const created = await createShipTask();
expect(created.text).toBe('Ship from my test');
expect(mock.calls()).toEqual([
{ method: 'tasks.create', args: [examples.list.id, { text: examples.task.text }] },
]);
mock.users.me.on().resolves({ ...examples.userResource, name: 'Ada Lovelace' });
.on(...args) is a prefix: extra trailing options on the real call still match. Nested objects match regardless of key order. Pass a function for a loose match (true → match):
mock.tasks.create.on(examples.list.id, (body) => body.text === examples.task.text).resolves({
...examples.task,
});
Queue errors with .rejects(). errors.* are the same classes the client throws:
import { AuthenticationError } from '@workast/sdk';
import { errors } from '@workast/sdk/mock';
mock.users.me.on().rejects(errors.unauthorized);
await expect(workast.users.me()).rejects.toBeInstanceOf(AuthenticationError);
| Helper | Meaning |
|---|---|
mock.calls() |
Every SDK call while this mock is active ({ method, args }). |
mock.pending() |
Interceptors that were not used. |
interceptor.wasCalled() |
Whether that .resolves() / .rejects() fired. |
mock.reset() |
Clear queue and calls. Stay intercepting. |
mock.restore() |
Unpatch. Later SDK calls hit the real API. |
One mock per test, or one shared mock and reset() between tests:
const mock = mockWorkast();
afterEach(() => mock.reset());
afterAll(() => mock.restore());
mockWorkast() last-wins: a second call replaces the active queue. Unmatched SDK methods throw and list pending interceptors.
Upgrading from v2
v3 is a rewrite. The v2 positional constructor, apiCall, and generated resource helpers are gone. A string argument is now a secret apiKey (server-only), not a session token.
// v2
const workast = new Workast(process.env.WORKAST_TOKEN);
// v3
const workast = new Workast({ apiKey: process.env.WORKAST_API_KEY });
What shipped in each version is on Releases.
Contributing
See CONTRIBUTING.md. To report a vulnerability, see SECURITY.md.
License
MIT