The Nexical SDK (@nexical/sdk) is the official TypeScript library for interacting with the Nexical Orchestrator. It provides a typed, robust interface for managing users, teams, projects, and executing jobs.
This SDK is designed for four primary developer personas:
- Browser Extension Developers: Building tools to monitor and trigger jobs.
- Web Application Developers: Building management interfaces or dashboards.
- Factory Worker Developers: Building custom job execution runtimes (workers).
- Remote Agent Developers: Building AI agents running inside job containers.
npm install @nexical/sdk
# or
yarn add @nexical/sdkInitialize the client with your access token.
import { NexicalClient } from '@nexical/sdk';
// Initialize with a static token (e.g., from environment or local storage)
const client = new NexicalClient({
token: 'nx_abc123...', // API Token or JWT
});The SDK supports multiple authentication strategies depending on your runtime environment.
Best for: CI/CD pipelines, Backend Scripts, Web Servers.
- Log in to the Nexical Dashboard.
- Navigate to Settings > API Tokens.
- Click "Generate New Token" and select scope (e.g.,
read,write). - Copy the token (starts with
nx_...) and store it securely (e.g.,.env).
const client = new NexicalClient({
token: process.env.NEXICAL_API_TOKEN
});Best for: Headless factory workers, GPU nodes.
Worker auth uses a "Golden Enrollment Token" to bootstrap trust. The SDK handles the exchange automatically.
- Generate an Enrollment Token in the dashboard (System Admin only).
- Set
NEXICAL_ENROLLMENT_TOKENin your worker's environment. - The
NexicalWorkerclass will:- Detect the env var.
- Call
/auth/worker/enrollto exchange it for a session JWT. - Cache the JWT in memory for subsequent requests.
// No manual token handling needed if using NexicalWorker + Env Var
const worker = new NexicalWorker(client, {
workerId: 'my-node',
enrollmentToken: process.env.NEXICAL_ENROLLMENT_TOKEN
});Best for: CLI Tools, Desktop Apps, Developer-facing extensions.
Allows a user to authorize a headless device by visiting a URL on their phone/laptop.
// 1. Start Flow
// This tells the API "I want to authenticate user on this device"
const token = await client.auth.authenticateDevice('my-cli-app', (userCode, verificationUri) => {
// 2. Prompt User
console.log(`Please visit ${verificationUri} and enter code: ${userCode}`);
// Ideally, open the browser automatically for them here.
});
// 3. Authenticated!
// The SDK polls until the user approves, then returns the token.
client.setToken(token);
console.log('Successfully logged in as', (await client.users.me()).fullName);Goal: Monitor build status, trigger deployments, and view logs from a browser popup.
Recommended Auth: API Token (generated by user) or Device Flow (if interactive).
// 1. List my teams
const teams = await client.teams.list();
const teamId = teams[0].id;
// 2. List projects in the team
const projects = await client.projects.list(teamId);
const project = projects.find(p => p.name === 'Frontend App');
if (project) {
// 3. Find target branch (e.g., main)
const branches = await client.branches.list(teamId, project.id);
let branch = branches.find(b => b.name === 'main');
// Create branch if missing (optional logic)
if (!branch) {
branch = await client.branches.create(teamId, project.id, { name: 'main' });
}
// 4. Trigger a new deployment on the branch
const newJob = await client.jobs.create(teamId, project.id, branch.id, {
type: 'deploy',
inputs: { commit: 'latest' }
});
console.log(`Triggered Job ${newJob.id}`);
// 5. Poll for status
const interval = setInterval(async () => {
const job = await client.jobs.get(teamId, project.id, branch.id, newJob.id);
if (job.status !== 'pending' && job.status !== 'running') {
clearInterval(interval);
console.log(`Job finished: ${job.status}`);
}
}, 2000);
}Goal: Build a full management dashboard for creating teams, inviting users, and configuring projects.
// 1. Create a new Team
const team = await client.teams.create({
name: 'Research Division',
slug: 'research-div'
});
// 2. Invite a colleague
await client.teams.inviteMember(team.id, {
email: '[email protected]',
role: 'admin'
});
// 3. Configure a Project
const project = await client.projects.create(team.id, {
name: 'AI Model Training',
repoUrl: 'github.com/org/ai-model',
productionUrl: 'https://model.ai'
});
// 4. Create a Default Branch
const mainBranch = await client.branches.create(team.id, project.id, {
name: 'main',
previewUrl: 'https://main.model.ai'
});
// 4. Update User Profile
await client.users.update({
fullName: 'Bob Manager',
avatarUrl: 'https://...'
});Goal: Build a custom runtime (e.g., a GPU cluster node) that polls the Orchestrator for heavy compute jobs.
Recommended Auth: Enrollment Token (headless) or Device Flow (interactive CLI).
The SDK provides a NexicalWorker class that handles polling logic, backoff (jitter), and concurrency for you.
import { NexicalClient, NexicalWorker } from '@nexical/sdk';
const client = new NexicalClient();
// 1. Initialize Worker
// The worker will automatically exchange the ENROLLMENT_TOKEN for a session token.
const worker = new NexicalWorker(client, {
workerId: 'gpu-node-01',
concurrency: 2, // Process 2 jobs in parallel
enrollmentToken: process.env.NEXICAL_ENROLLMENT_TOKEN
});
// 2. Define Job Processor
// This function is compatible with the "JobProcessor" type.
const processor = async (job) => {
console.log(`[Job ${job.id}] Starting execution...`);
// ... Perform heavy compute ...
// Add logs back to the platform
// Note: We need teamId/projectId/branchId from job metadata
await client.jobs.addLog(job.teamId, job.projectId, job.branchId, job.id, {
message: 'Compute complete. Uploading artifacts...',
level: 'info'
});
};
// 3. Start Polling
// This promise resolves only when worker.stop() is called.
await worker.start(processor);Goal: Write code that runs inside the job container (e.g., an LLM Agent) and needs to interact with the platform securely.
Auth: The Agent uses a temporary token minted by the Worker.
// Inside the job container, these might be passed as ENV vars or args
const TEAM_ID = ...;
const PROJECT_ID = ...;
const BRANCH_ID = ...;
const JOB_ID = ...;
// 1. Get a GitHub Token to clone the repo
// (Only works if the worker specifically requests it or if it's part of the job context)
try {
const gitToken = await client.jobs.getGitToken(TEAM_ID, PROJECT_ID, BRANCH_ID, JOB_ID);
console.log(`Cloning with token expiring at ${gitToken.expires_at}`);
} catch (err) {
console.error('Failed to get Git token:', err);
}
// 2. Mint a sub-token for a sub-agent
// If this process spawns another isolated process, it can vend a restricted token.
const agentToken = await client.jobs.getAgentToken(TEAM_ID, PROJECT_ID, BRANCH_ID, JOB_ID);This table maps the Orchestrator API endpoints to the corresponding SDK methods.
β = Supported & Verified
β οΈ = Partially Supported / Different Signature β = Not Implemented in SDK
| API Endpoint | SDK Method | Status |
|---|---|---|
POST /auth/users |
createSystemUser(data) |
β |
POST /auth/tokens |
generateToken(data) |
β |
GET /auth/tokens |
listTokens() |
β |
DELETE /auth/tokens/:id |
revokeToken(id) |
β |
POST /auth/worker/enroll |
enrollWorker(data) |
β |
POST /device/* |
authenticateDevice(clientId, cb) |
β (Orchestrated) |
| API Endpoint | SDK Method | Status |
|---|---|---|
GET /users/me |
me() |
β |
PUT /users/me |
update(data) |
β |
| API Endpoint | SDK Method | Status |
|---|---|---|
GET /teams |
list() |
β |
POST /teams |
create(data) |
β |
GET /teams/:id |
get(id) |
β |
PUT /teams/:id |
update(id, data) |
β |
POST /teams/:id/invites |
inviteMember(id, data) |
β |
DELETE /teams/:id/members/:uid |
removeMember(id, uid) |
β |
DELETE /teams/:id |
delete(id) |
β |
| API Endpoint | SDK Method | Status |
|---|---|---|
GET /teams/:tid/projects |
list(teamId) |
β |
POST /teams/:tid/projects |
create(teamId, data) |
β |
GET /teams/:tid/projects/:pid |
get(teamId, projId) |
β |
PUT /teams/:tid/projects/:pid |
update(teamId, projId, data) |
β |
DELETE /teams/:tid/projects/:pid |
delete(teamId, projId) |
β |
| API Endpoint | SDK Method | Status |
|---|---|---|
GET /teams/:tid/projects/:pid/branches |
list(teamId, projId) |
β |
POST /teams/:tid/projects/:pid/branches |
create(teamId, projId, data) |
β |
GET /teams/:tid/projects/:pid/branches/:bid |
get(teamId, projId, branchId) |
β |
DELETE /teams/:tid/projects/:pid/branches/:bid |
delete(teamId, projId, branchId) |
β |
| API Endpoint | SDK Method | Status |
|---|---|---|
GET /:branchId/jobs |
list(teamId, projId, branchId) * |
teamId scope) |
POST /:branchId/jobs |
create(teamId, projId, branchId, data) * |
teamId scope) |
GET /:jobId |
get(teamId, projId, branchId, jobId) * |
|
GET /:jobId/logs |
getLogs(teamId, projId, branchId, jobId) |
β |
POST /:jobId/logs |
addLog(teamId, projId, branchId, jobId, data) |
β |
GET /jobs/:id/git-token |
getGitToken(teamId, projId, branchId, jobId) |
β |
POST /jobs/:id/agent-token |
getAgentToken(teamId, projId, branchId, jobId) |
β |
* Note: The SDK enforces strict hierarchical scoping (/teams/:id/projects/:id/branches/:id/...) to ensure correct resource addressing and team-based authorization checks.
| API Endpoint | SDK Method | Status |
|---|---|---|
POST /workers/acquire |
acquireJob() |
β |
The SDK throws typed error classes for easier handling:
NexicalNetworkError: Connection failures (DNS, Timeout).NexicalAuthError: 401 Unauthorized / 403 Forbidden.NexicalRateLimitError: 429 Too Many Requests.NexicalContractError: Response did not match expected Zod schema.NexicalAPIError: Generic 4xx/5xx errors (includescodeandmessage).
try {
await client.teams.create({ ... });
} catch (error) {
if (error instanceof NexicalContractError) {
console.error('Server returned invalid data format', error.validationErrors);
} else if (error instanceof NexicalAPIError) {
console.error(`API Error ${error.statusCode}: ${error.message}`);
}
}The SDK exports TypeScript interfaces for all API resources. These match the Zod schemas used for runtime validation.
export interface User {
id: string; // UUID
email?: string;
fullName: string | null;
avatarUrl: string | null;
role: 'user' | 'system';
createdAt: string;
updatedAt: string;
}
export interface Team {
id: number;
name: string;
slug: string;
billingPlan: string;
role?: 'owner' | 'admin' | 'member'; // Current user's role
createdAt: string;
updatedAt: string;
}
export interface Project {
id: number;
teamId: number;
name: string;
repoUrl: string | null;
productionUrl: string | null;
mode: 'managed' | 'self_hosted';
contextHash: string | null;
createdAt: string;
updatedAt: string;
}
export interface Branch {
id: number;
projectId: number;
name: string;
previewUrl: string | null;
createdAt: string;
updatedAt: string;
}
export interface Job {
id: number;
branchId: number;
type: string; // e.g. "deploy", "build"
status: 'pending' | 'running' | 'completed' | 'failed';
queue: string; // e.g. "public", "private-123"
inputs: Record<string, any> | null;
outputs: Record<string, any> | null;
startedAt: string | null;
completedAt: string | null;
createdAt: string;
}
export interface JobLog {
id: number;
jobId: number;
level: 'info' | 'warn' | 'error';
message: string;
metadata: Record<string, any> | null;
timestamp: string;
}Workers
export interface Worker {
id: string; // UUID
name: string;
teamId: number | null; // Null for managed/public workers
lastSeenAt: string | null;
}Tokens
export interface ApiToken {
id: number;
name: string;
tokenPrefix: string;
scopes: string[] | null;
lastUsedAt: string | null;
expiresAt: string | null;
}
export interface GitTokenResponse {
token: string;
expires_at: string; // ISO Date
}Useful payload types for create and update methods.
export interface CreateBranchRequest {
name: string;
previewUrl?: string;
}
export interface CreateJobRequest {
type: string;
inputs?: Record<string, any>;
}
export interface EnrollWorkerRequest {
token: string; // The Golden Enrollment Token
metadata?: Record<string, any>;
}