SDK Reference
Every method on the NanoTermClient class. All methods are async and
return typed results. Errors follow the shared error envelope.
Workspaces
createWorkspace(config?)
Create a new workspace.
const ws = await client.createWorkspace({
name: 'my-agent', // optional
image: 'ubuntu:22.04' // optional, default: ubuntu:22.04
})
console.log(ws)
// {
// id: 'ws_a1b2c3d4',
// name: 'my-agent',
// image: 'ubuntu:22.04',
// status: 'running',
// createdAt: '2026-04-06T...'
// }
Returns: Promise<WorkspaceInfo>
listWorkspaces()
List all workspaces in the current org.
const workspaces = await client.listWorkspaces()
// WorkspaceInfo[]
getWorkspace(id)
Get details for a specific workspace.
const ws = await client.getWorkspace('ws_a1b2c3d4')
terminateWorkspace(id)
Stop and remove a workspace.
await client.terminateWorkspace('ws_a1b2c3d4')
Execution
exec(workspaceId, command, opts?)
Execute a command and return the result.
const result = await client.exec('ws_a1b2c3d4', ['npm', 'test'])
console.log(result)
// {
// stdout: 'PASS src/index.test.ts\nTests: 12 passed',
// stderr: '',
// exitCode: 0
// }
// Run in a subdirectory
await client.exec('ws_a1b2c3d4', ['pnpm', 'build'], {
cwd: '/workspace/api',
})
Parameters:
| Name | Type | Description |
|---|---|---|
workspaceId | string | Target workspace ID |
command | string[] | Command and arguments |
opts.cwd | string | Working directory inside the workspace (default /workspace) |
Returns: Promise<ExecResult>
interface ExecResult {
stdout: string
stderr: string
exitCode: number
}
getTerminalUrl(workspaceId)
Get the WebSocket URL for interactive terminal access.
const url = client.getTerminalUrl('ws_a1b2c3d4')
// wss://api.nanoterm.dev/api/workspaces/ws_a1b2c3d4/terminal
Use this with a WebSocket client to establish a PTY session. The
WebSocket upgrade handshake is gated by the same auth as HTTP — pass
getAuthHeaders() to your client constructor:
import WebSocket from 'ws'
const ws = new WebSocket(client.getTerminalUrl(id), {
headers: client.getAuthHeaders(),
})
Messages are JSON-framed:
Client → Server:
// Send input
{ type: 'stdin', data: '<base64>' }
// Resize terminal
{ type: 'resize', cols: 80, rows: 24 }
Server → Client:
// Terminal output
{ type: 'stdout', data: '<base64>' }
Types
Files
writeFile(workspaceId, filePath, content)
Write a file inside the workspace. The server stats the file after
writing and returns the actual bytes and mtime — verify these
against what you sent before claiming success.
const content = new TextEncoder().encode('hello\n').buffer
const result = await client.writeFile('ws_a1b2c3d4', '/workspace/hello.txt', content)
// { bytes: 6, mtime: '2026-05-06T08:01:23.000Z' }
if (result.bytes !== 6) {
throw new Error('write was truncated upstream')
}
A 4xx/5xx response throws a NanoTermError with the standard
error envelope. Common error codes:
| Code | Meaning |
|---|---|
FILE_WRITE_BYTE_MISMATCH | The bytes on disk don't match what you uploaded — usually a streaming/proxy issue. Retry. |
readFile(workspaceId, filePath)
Read a file from the workspace as an ArrayBuffer. Throws on
non-2xx responses (including the standard error envelope).
const buf = await client.readFile('ws_a1b2c3d4', '/workspace/result.json')
const text = new TextDecoder().decode(buf)
Auth helpers
getAuthHeaders()
Headers the SDK would attach to a regular HTTP request —
Authorization, X-Org-Id, X-Client-Name, X-Client-Version.
Use this when opening raw WebSocket / fetch connections (terminal,
proxy) so the upgrade handshake authenticates correctly:
const ws = new WebSocket(client.getTerminalUrl(id), {
headers: client.getAuthHeaders(),
})
Error handling
Errors thrown by the SDK are either NanoTermError (HTTP envelope
from the server) or TransientNetworkError (network / non-JSON 5xx).
Both carry agent-actionable fields:
| Field | When set | What it means |
|---|---|---|
code | NanoTermError | Stable error identifier (e.g. WORKSPACE_NOT_FOUND) |
retryable | both | true if the same call may succeed later |
requestSent | both | false = server never saw the request (safe to retry non-idempotent ops); true = effects may have been applied |
attempt | NanoTermError | Retry attempt that produced this error (1-based) |
status | both | HTTP status if applicable |
retryAfterSec | NanoTermError 429 | Seconds to wait before retry |
The CLI's --json mode emits exactly this shape on stderr so an
agent can decide how to recover without parsing prose.
Types
All types are available from @nanoterm/types:
import type {
WorkspaceInfo,
WorkspaceStatus,
CreateWorkspaceRequest,
ExecRequest,
ExecResult,
TerminalInputMessage,
TerminalOutputMessage,
OrgInfo,
ApiKeyInfo,
} from '@nanoterm/types'