Node.js & TypeScript SDK
Use the server-side JavaScript client to upload documents and query your company’s talent graph. TypeScript declarations are included.
Every call works in your company workspace, which is identified by your API key. Requires Node.js 20 or later.
Install
The package isn’t on npm yet. Build version 1.0.0 from the workerbee-sdk repository on GitHub and install the tarball in your project:
git clone --branch sdk-v1.0.0 https://github.com/Workerbee-Inc/workerbee-sdk.git npm pack ./workerbee-sdk/node npm install ./workerbee-sdk-1.0.0.tgz
The package is @workerbee/sdk and uses ES modules. Use a .mjs file, or set "type": "module" in your application’s package.json. For TypeScript, use an ESM project with NodeNext module resolution; type declarations are included. The client uses Node filesystem APIs and is meant for server-side code, not browser bundles.
Configure authentication
Create a key in Settings → Workspaces & SDK → SDK API keys. Upload-and-query examples need files:write, files:read, and graph:read. Restrict the key to Default or allow all company workspaces. Store the full key in your application’s secret manager or environment.
export WORKERBEE_API_KEY="YOUR_API_KEY"
A key restricted to workspaces other than Default can’t use these methods, and its restrictions aren’t widened automatically.
import { Workerbee } from '@workerbee/sdk';
const wb = new Workerbee({ timeoutMs: 60_000, maxRetries: 3 });
for await (const file of wb.listFiles()) {
console.log(file.id, file.filename, file.status);
}The client reads your key from WORKERBEE_API_KEY. Client and wait timeouts are in milliseconds. Methods are asynchronous, and file lists use for await.
Upload a resume and query skills
Save as quickstart.mjs, place a resume at ./resume.pdf, set your API key, and run node quickstart.mjs.
import { Workerbee } from '@workerbee/sdk';
const wb = new Workerbee(); // reads WORKERBEE_API_KEY
const resume = await wb.uploadFile('./resume.pdf', {
document_types: ['resume'],
external_id: 'employee-123',
source_system: 'hris',
source_document_id: 'employee-123-resume',
source_revision: 'v1',
idempotency_key: 'employee-123-resume-v1',
});
console.log(resume.id, resume.status);
await resume.waitUntilReady({ timeoutMs: 600_000 });
const response = await wb.chat(
[{ role: 'user', content: "List this employee's skills" }],
{ subject: { external_id: 'employee-123', source_system: 'hris' }, limit: 20 },
);
if (response.refused) {
console.log(response.refusal_reason);
} else if (response.clarification) {
console.log(response.clarification);
} else {
console.log(response.interpretation);
for (const row of response.rows) console.log(row);
console.log(response.references);
}A wait succeeds at PROCESSED. Inspect adapter outcomes, readiness, and has_queryable_content: stored-only or irrelevant content may not appear in answers, and graph visibility may lag ingestion. A wait timeout does not cancel server processing.
Method names use camelCase, while upload fields and subject identities use snake_case. Pass the path as the first argument to uploadFile(path, options).
Document types and formats
| Document type | How to use it |
|---|---|
resume | Employee resume. Use a stable employee external ID and source system. |
job_description | Job description. Use your job or requisition external ID. |
performance_review / review | Employee review. Supply employee identity. Reviews are linked to the employee, but their evidence isn’t added to graph queries yet. |
call_transcript | Written call or meeting transcript; no audio transcription. Currently stored with a deferred adapter and no searchable graph content. |
other | Generic content or ZIP default. Use this type on its own. |
import { Workerbee } from '@workerbee/sdk';
const wb = new Workerbee();
const jd = await wb.uploadFile('./job-description.pdf', {
document_types: ['job_description'], external_id: 'requisition-456',
source_system: 'hris',
});
const review = await wb.uploadFile('./review.pdf', {
document_types: ['performance_review'], external_id: 'employee-123',
source_system: 'hris',
});
console.log(jd.id, review.id);Declare types in document_types, not inside metadata. Supported formats include PDF, DOCX, TXT, Markdown, JSON, and ZIP; SRT and VTT are accepted as text. JSON is document text, not an arbitrary graph-property payload. Legacy DOC files, scanned PDFs, audio and video aren’t supported.
| Default limit | Value |
|---|---|
| Single document | 25 MiB |
| ZIP upload | 100 MiB |
| Expanded ZIP | 500 MiB |
| ZIP entries | 200 |
Limits can differ by deployment. Nested archives and unsafe paths are rejected. In a mixed resume/JD document, provide typed subjects for employee and job identities. A mixed document is not a ZIP and does not trigger ZIP matching.
Source revisions and retries
Keep external_id and source_system stable across an employee’s uploads and queries. Names alone are not reliable identities. source_document_id identifies a source document; source_revision identifies an immutable version. Change the revision when bytes change; reusing one for different content returns SOURCE_REVISION_CONFLICT.
Use an idempotency_key for retries of the same upload request. Reusing it for different input is a conflict. The clients retry eligible safe requests and transient failures; unkeyed upload creation is not blindly retried.
ZIP uploads and matching
For mixed-document ZIP uploads, include workerbee-manifest.json at the archive root so Workerbee knows the document type and identity of each included file. Declare every document using its exact archive path, including subdirectories. An entry’s types replace the ZIP default; an entry omitted from the manifest inherits that default, so do not rely on filenames to identify resumes or job descriptions. The manifest itself is control information, not an ingested document.
{
"files": {
"resumes/employee-123.pdf": {
"document_types": ["resume"],
"external_id": "employee-123",
"metadata": {"match_job_paths": ["jobs/platform-engineer.pdf"]}
},
"jobs/platform-engineer.pdf": {
"document_types": ["job_description"],
"external_id": "requisition-456"
}
}
}hiring-bundle.zip ├── workerbee-manifest.json ├── resumes/employee-123.pdf └── jobs/platform-engineer.pdf
The example below packages local ./resume.pdf and ./job-description.pdf under those paths, writes the manifest, then uploads the ZIP with the generic other default and hris source system. Per-file declarations select the resume and job-description processing. One invalid child does not discard successful siblings.
This Node.js example uses the macOS/Linux zip command to create the archive. On other systems, create the same layout with your ZIP tool and run the upload portion. ZIP creation is separate from the Workerbee client.
import { Workerbee, FilePartialSuccessError } from '@workerbee/sdk';
import { mkdtemp, mkdir, copyFile, writeFile, rm } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import { join, resolve } from 'node:path';
import { execFileSync } from 'node:child_process';
const manifest = {
files: {
'resumes/employee-123.pdf': {
document_types: ['resume'], external_id: 'employee-123',
metadata: { match_job_paths: ['jobs/platform-engineer.pdf'] },
},
'jobs/platform-engineer.pdf': {
document_types: ['job_description'], external_id: 'requisition-456',
},
},
};
// Requires the zip command (macOS/Linux); it is not part of the SDK.
const staging = await mkdtemp(join(tmpdir(), 'workerbee-zip-'));
const bundlePath = resolve('hiring-bundle.zip');
try {
await mkdir(join(staging, 'resumes'));
await mkdir(join(staging, 'jobs'));
await copyFile('./resume.pdf', join(staging, 'resumes/employee-123.pdf'));
await copyFile('./job-description.pdf', join(staging, 'jobs/platform-engineer.pdf'));
await writeFile(join(staging, 'workerbee-manifest.json'), JSON.stringify(manifest, null, 2));
// Rebuild from scratch so an older ZIP cannot retain stale entries.
await rm(bundlePath, { force: true });
execFileSync('zip', ['-q', bundlePath, 'workerbee-manifest.json',
'resumes/employee-123.pdf', 'jobs/platform-engineer.pdf'], { cwd: staging });
} finally {
await rm(staging, { recursive: true, force: true });
}
const wb = new Workerbee();
let archive = await wb.uploadFile('./hiring-bundle.zip', {
document_types: ['other'], source_system: 'hris',
});
try {
await archive.waitUntilReady({ timeoutMs: 600_000 });
} catch (error) {
if (!(error instanceof FilePartialSuccessError)) throw error;
archive = error.file;
}
for await (const child of archive.childrenFiles()) {
console.log(child.data.archive_path, child.id, child.status);
}
for (const adapter of archive.adapters) {
console.log(adapter.document_type, adapter.status, adapter.error);
}With ZIP auto-matching enabled, a ZIP containing one JD and resumes adds successfully processed employees to that JD’s Internal Talent pool. Multiple JDs require explicit metadata.match_job_paths on each resume; paths must identify JDs in the same ZIP. Resume-only archives do not assign employees to a job.
Matching runs separately from ingestion. Inspect the parent’s archive_matching adapter: PENDING/RUNNING, NEEDS_REVIEW, or SUCCEEDED. Retryable scoring failures are recovered by the backend. A processed ZIP can still have matching in progress.
For individual uploads, open Workspaces & SDK → Employees, choose an employee and job, then select Match and open ranking. The SDK doesn’t expose employee-profile or direct job-matching methods; chat is not a replacement for a match operation.
Manage files
import { Workerbee } from '@workerbee/sdk';
const wb = new Workerbee();
for await (const file of wb.listFiles({ status: 'PROCESSED', pageSize: 50 })) {
console.log(file.id, file.filename);
}
const file = await wb.getFile('YOUR_FILE_ID');
await file.refresh();
await file.download('./downloaded-source.pdf');
// Only when intended:
// await file.retry();
// await file.delete();Listing paginates automatically. Use childrenFiles() for ZIP entries and file.data for the full response snapshot. Retrying failed processing preserves successful adapters. Deleting a source does not remove an employee from an existing job pool.
Chat responses
Check refusal and clarification before consuming rows. interpretation describes the query; row fields depend on the question. references retain available source identities, and audit provides query context. Not every result or aggregate has a reference.
A subject selects the employee using the same external ID and source system used for ingestion. Without one, supported inventory queries may be company-wide. Workspace restrictions control resource access; do not treat workspace selection or a chat subject as a department-level privacy boundary.
Handle errors and processing states
| Outcome | Action |
|---|---|
PROCESSED | Inspect adapters and queryable-content indicators before expecting graph results. |
NEEDS_REVIEW | Resolve identity or processing ambiguity in the Work Intelligence Console (WIC). |
PARTIAL_SUCCESS | Inspect failed adapters or ZIP children; preserve successful results. |
FAILED | Read the error and retry only when appropriate. |
DELETED | The source was deleted; stop polling. |
| Wait timeout | Processing continues. Refresh the same file later. |
import {
Workerbee, WorkerbeeError, ReadyTimeoutError, FileProcessingError,
} from '@workerbee/sdk';
try {
const wb = new Workerbee();
const file = await wb.getFile('YOUR_FILE_ID');
await file.waitUntilReady({ timeoutMs: 600_000, pollIntervalMs: 2_000 });
} catch (error) {
if (error instanceof ReadyTimeoutError) {
console.log('Still processing:', error.file.id, error.file.status);
} else if (error instanceof FileProcessingError) {
console.log(error.code, error.file.status, error.file.adapters);
} else if (error instanceof WorkerbeeError) {
console.log(error.code, error.status, error.requestId, error.retryable);
} else {
throw error;
}
}Processing subclasses include FileNeedsReviewError, FilePartialSuccessError, FileFailedError, and FileDeletedError. API errors include typed authentication, permission, conflict, validation, rate-limit, connection, and server failures. Save the request ID for support.
Last updated