@nexical/sdk
v0.4.2
Published
Official TypeScript SDK for the Nexical Orchestrator API
Maintainers
Readme
Nexical SDK Reference
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.
Installation
npm install @nexical/sdk
# or
yarn add @nexical/sdk1. Getting Started
Initialize 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
});1.1 Authentication Deep Dive
The SDK supports multiple authentication strategies depending on your runtime environment.
A. API Tokens (Long-Lived)
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
});B. Worker Enrollment (Automated)
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
});C. Device Flow (Interactive)
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);2. Developer Guides
🧩 Browser Extension Developer
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);
}💻 Web Application Developer
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://...'
});🏭 Factory Worker Developer
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);🤖 Remote Agent Developer
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);3. API Reference & Coverage
This table maps the Orchestrator API endpoints to the corresponding SDK methods.
✅ = Supported & Verified ⚠️ = Partially Supported / Different Signature ❌ = Not Implemented in SDK
Authentication (client.auth)
| 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) |
Users (client.users)
| API Endpoint | SDK Method | Status |
| :--- | :--- | :--- |
| GET /users/me | me() | ✅ |
| PUT /users/me | update(data) | ✅ |
Teams (client.teams)
| 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) | ✅ |
Projects (client.projects)
| 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) | ✅ |
Branches (client.branches)
| 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) | ✅ |
Jobs (client.jobs)
| API Endpoint | SDK Method | Status |
| :--- | :--- | :--- |
| GET /:branchId/jobs | list(teamId, projId, branchId) * | ⚠️ (Requires teamId scope) |
| POST /:branchId/jobs | create(teamId, projId, branchId, data) * | ⚠️ (Requires teamId scope) |
| GET /:jobId | get(teamId, projId, branchId, jobId) * | ⚠️ (Requires full scope) |
| 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.
Workers (client.workers)
| API Endpoint | SDK Method | Status |
| :--- | :--- | :--- |
| POST /workers/acquire | acquireJob() | ✅ |
Error Handling
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}`);
}
}4. Data Models
The SDK exports TypeScript interfaces for all API resources. These match the Zod schemas used for runtime validation.
Core Resources
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;
}Context Interfaces
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
}Request Objects
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>;
}