Skip to content

TypeScript SDK reference

Audience: TypeScript / JavaScript engineers integrating Nanorix into a Node.js backend or modern bundler-built browser app. Package: @nanorix/sdk (v0.5.0). The 1.0 contract freeze is planned; the current surface is stable and additive. Node: 18+ · TypeScript: 5.x · ES Modules + CommonJS dual export. Substrate framing. The SDK wraps the HTTP API documented in api-reference.md. Surface is byte-symmetric with the Python SDK: every method has the same name (camelCase instead of snake_case), the same arguments, the same return shape, the same exception hierarchy.


Install

npm install @nanorix/sdk

Or from source: github.com/nanorix-io/nanorix-sdk.

Dependencies: zero runtime deps for crypto (uses Node crypto builtins for HMAC, signatures, hashing).


Top-level surface

import { Client, AsyncClient } from "@nanorix/sdk";

// Construction
const client = new Client({ apiKey: "nrx_live_..." });
const client2 = new Client();                                     // auto-loads NANORIX_API_KEY
const client3 = new Client({ baseUrl: "https://nanorix.io",
                              timeout: 30000, maxRetries: 4 });

// Resources
client.capsulefiles      // Capsulefile lifecycle
client.capsules          // capsule lifecycle + .run(), .batch(), .session()
client.cdp               // AuditProof retrieval (alias: client.auditProofs)
client.auditProofs       // alias of .cdp
client.egressProfiles    // named egress allowlists
client.usage             // usage summary, line-items, CSV export
client.webhooks          // webhook endpoints + deliveries

// Subpackage namespaces
import { verifyAuditProof, type VerifierPolicy } from "@nanorix/sdk";
import { Webhook } from "@nanorix/sdk";
import { HIPAAContext, GDPRContext, SOC2Context } from "@nanorix/sdk";

// Top-level
await client.health();    // GET /v1/health
client.close();           // release HTTP transport

The SDK is async-first; every method returns a Promise. There is no separate AsyncClient because Node-side TypeScript is async by default. The AsyncClient symbol is exported as an alias to Client for symmetry with the Python SDK.


Exceptions

Full reference: error catalog.

import {
  NanorixError,                       // base
  AuthenticationError,                // 401
  NotFoundError,                      // 404
  ConflictError,                      // 409
  BadRequestError,                    // 400
  QuotaExceededError,                 // 402, 403 (quota)
  PayloadTooLargeError,               // 413
  GoneError,                          // 410 (capsule destroyed)
  RateLimitError,                     // 429 (after auto-retry budget exhausted)
  UnprocessableEntityError,           // 422 default
  InputProvenanceRejectedError,       // 422 input_provenance_rejected
  SecretsInjectionRejectedError,      // 422 secrets_injection_rejected
  ExecutionError,                     // non-zero exit; .cdp attached if destroyed
  CeilingError,                       // safety-ceiling destroy; .cdp attached
  TimeoutError,                       // exec timeout; .capsuleId attached
} from "@nanorix/sdk";

Every exception carries statusCode?: number and body?: Record<string, unknown>. The body.error.request_id field is the support-ticket correlation id; quote it when filing.


client.capsulefiles

Class: Capsulefiles.

create(opts)

async create(opts: {
  name: string;
  runtime: string;
  entrypoint: string;
  files?: string[] | Record<string, Buffer>;
  dataClassification?: "general" | "PHI" | "PII" | "financial" | "credentials";
  egressDeclaration?: string[];
  defaultTtl?: number;
  memoryMb?: number;
  envVars?: Record<string, string>;
  packages?: Record<string, string[]>;
  finalize?: boolean;        // default true
}): Promise<Capsulefile>

Maps to POST /v1/capsulefiles (+ POST /:id/files + POST /:id/finalize).

Example:

const cf = await client.capsulefiles.create({
  name: "med-ai-extraction",
  runtime: "python3.12",
  entrypoint: "python main.py",
  files: ["main.py"],
  dataClassification: "PHI",
  egressDeclaration: ["api.anthropic.com"],
  defaultTtl: 1800,
  memoryMb: 2048,
  envVars: { ANTHROPIC_MODEL: "claude-3-5-sonnet-latest" },
  packages: { python: ["anthropic==0.39.0", "pypdf==5.0.1"] },
});
console.log(cf.id, cf.contentHash);

list(params) / get(id) / delete(id)

async list(params?: Record<string, string>): Promise<CapsulefileList>
async get(capsulefileId: string): Promise<Capsulefile>
async delete(capsulefileId: string): Promise<void>

update(id, fields)

async update(id: string, fields: Partial<Capsulefile>): Promise<Capsulefile>

waitUntilFinalized(id, opts)

Polls until the Capsulefile transitions from draft to finalized.

async waitUntilFinalized(id: string, opts?: { timeoutMs?: number; pollIntervalMs?: number }): Promise<Capsulefile>

uploadDir(id, dirPath)

Uploads every file under dirPath recursively. Returns the file count.

async uploadDir(capsulefileId: string, dirPath: string): Promise<number>

client.capsules

Class: Capsules.

create(opts)

async create(opts?: {
  capsulefile?: string;
  maxLifetimeSeconds?: number;
  egressProfile?: string;
  metadata?: Record<string, string>;
}): Promise<LiveCapsule>

Maps to POST /v1/capsules. metadata binds customer-declared context (16 keys max, 64-char keys, 256-char values, 4 KB total; nanorix:* prefix reserved).

run(opts)

async run(opts: {
  capsulefile: string;
  inputs?: Record<string, Buffer>;
  command?: string;
  timeout?: number;
  maxLifetimeSeconds?: number;
  metadata?: Record<string, string>;
}): Promise<{ outputs: Record<string, Buffer>; cdp: CdpAccess; result: ExecutionResult }>

One-shot lifecycle: create → upload → execute → download → destroy.

Example (Pattern 3, single-shot extraction):

import * as fs from "node:fs";

const { outputs, cdp, result } = await client.capsules.run({
  capsulefile: "cf_018f...",
  inputs: { "input.pdf": fs.readFileSync("test.pdf") },
  metadata: { matter_id: "ACME-001" },
});
console.log(result.stdout.toString().slice(0, 200));
console.log("AuditProof id:", cdp.full().id);
console.log("final_hash:", cdp.full().finalHash);

batch(opts)

Anthropic-Batches-shape batch processing with RFC 6962 Merkle-root client-side (byte-equivalent to Rust runtime + Python SDK).

async batch(opts: {
  capsulefile: string;
  records: Iterable<Record<string, unknown>>;
  onRecord?: (record: BatchRecord) => void;
  recordIdField?: string;        // default "custom_id"
  onRecordError?: "fail_isolated" | "abort_batch";   // default "fail_isolated"
  maxLifetimeSeconds?: number;
  metadata?: Record<string, string>;
  timeout?: number;
}): Promise<BatchResult>

BatchResult carries merkleRoot, auditProof, records, successCount, errorCount. Customer entrypoint reads /data/input/records.jsonl and writes /data/output/results.jsonl per the same wire shape as the Python SDK.

session(opts)

LangGraph-shape stateful session-mode for multi-capsule reasoning chains. Customer-managed encrypted state continuity via parentCdpId.

async session(opts: {
  capsulefile: string;
  sessionId: string;
  parentCdpId?: string;
  initialState?: Buffer;
  maxLifetimeSeconds?: number;
  metadata?: Record<string, string>;
  timeout?: number;
}): Promise<SessionResult>

list(params) / get(id)

Standard list/get.

LiveCapsule workspace methods

LiveCapsule is the handle returned by create(). Methods:

Method Maps to
.id (property) capsule id
.status (property) current status
.upload(filename, data) POST /v1/capsules/:id/upload?filename=...
.execute({ command?, inputData?, timeout? }) POST /v1/capsules/:id/exec
.listOutputs() GET /v1/capsules/:id/output
.download(filename) GET /v1/capsules/:id/output/:filename
.destroy()CdpAccess DELETE /v1/capsules/:id
.refresh() GET /v1/capsules/:id

Workspace-mode example:

const capsule = await client.capsules.create({ capsulefile: "cf_018f..." });
await capsule.upload("input.pdf", pdfBytes);
const result = await capsule.execute({ command: "python main.py", timeout: 60 });
console.log(result.stdout);

// Iterate without re-uploading
const result2 = await capsule.execute({ command: "python main.py --verbose" });

// Destroy and get the AuditProof
const cdp = await capsule.destroy();
console.log(cdp.full().finalHash);

client.cdp (alias: client.auditProofs)

Class: CdpResource.

async get(capsuleId: string): Promise<CdpAccess>
async verify(capsuleId: string): Promise<AuditProof>   // the verification proof object

get() retrieves the AuditRecord (GET /v1/capsules/:id/cdp); verify() retrieves the shareable AuditProof projection (GET /v1/capsules/:id/cdp/verify).

CdpAccess exposes:

  • .full()CdpDocument — the AuditRecord (private complete record).
  • .verification()AuditProof — the shareable verification proof object.
  • .id, .capsuleId, .finalHash, .createdAt — top-level fields.

client.auditProofs is the brand-aligned alias; both names point at the same resource and coexist forever.


client.egressProfiles

Class: EgressProfiles. Tier: Team+.

client.egressProfiles.create({ name, description })
client.egressProfiles.list()
client.egressProfiles.get(profileId)
client.egressProfiles.update(profileId, { name?, description? })
client.egressProfiles.delete(profileId)
client.egressProfiles.addRule(profileId, { kind: "domain", value: "api.example.com", port: 443 })
client.egressProfiles.listRules(profileId)
client.egressProfiles.removeRule(profileId, ruleId)

Maps to /v1/egress/profiles/....

kind enum: "domain", "ip_v4", "ip_v6", "cidr_v4", "cidr_v6".


client.webhooks

Class: Webhooks.

client.webhooks.create({ url, events, description? })
client.webhooks.list()
client.webhooks.get(webhookId)
client.webhooks.update(webhookId, { url?, events?, description?, active? })
client.webhooks.delete(webhookId)
client.webhooks.test(webhookId)
client.webhooks.deliveries(webhookId)

The signingSecret returned from create() is shown once. Use Webhook.verifySignature() in your handler:

import { Webhook } from "@nanorix/sdk";

if (!Webhook.verifySignature({
  payload: req.body,
  signatureHeader: req.headers["nanorix-webhook-signature"],
  signingSecret: process.env.NANORIX_WEBHOOK_SECRET!,
})) {
  res.status(401).end();
  return;
}

Maps to /v1/webhooks/.... Full event-type catalog at integrations/webhooks.md.


client.usage

Class: Usage.

async summary(billingCycle?: string): Promise<UsageSummary>
async estimate(): Promise<UsageEstimate>
async listItems(opts?: ListItemsOptions): AsyncIterable<UsageItem>
async exportCsv(opts?: ListItemsOptions): Promise<Buffer>

listItems() returns an AsyncIterable paginated automatically. exportCsv() returns the raw CSV bytes — same shape as the API's /v1/usage/items.csv.

Maps to /v1/usage/....


Verifier subpackage

import { verifyAuditProof, type AuditProofVerificationResult, type VerifierPolicy } from "@nanorix/sdk";
import * as fs from "node:fs";

const proof = JSON.parse(fs.readFileSync("auditproof.json", "utf8"));

const result: AuditProofVerificationResult = verifyAuditProof(proof, {
  rejectDiagnostic: true,
  requiredRegion: "us-central1",
  requiredAuthorityId: undefined,
});

if (!result.valid) {
  console.log("failure:", result.failureReason?.type);
  console.log("stage_reached:", result.stageReached);
}

Cross-impl byte-equivalence: verifyAuditProof() produces output byte-equivalent to the Rust nanorix-verify, Go auditproof-verifier-go, and Python SDK verify_auditproof() on the 100-fixture reference corpus. The FailureReason discriminated union is identical across all five paths (Rust, Go, Python, TypeScript, server-side POST /v1/verify).

The failure-reason enum is closed-set and byte-equivalent across all verifier implementations.


Compliance helpers

Framework-typed metadata builders that produce Record<string, string> payloads for the metadata argument above. Bind customer-declared context that lands in the AuditProof's metadata_hash.

import { HIPAAContext, GDPRContext, SOC2Context } from "@nanorix/sdk";

const metadata = HIPAAContext({
  coveredEntityId: "ce-001",
  businessAssociateId: "ba-002",
  treatmentPurpose: true,
}).toMetadata();

const { outputs, cdp } = await client.capsules.run({
  capsulefile: "cf_...",
  inputs: { "input.pdf": pdfBytes },
  metadata,
});

The metadata is customer-defined; the compliance helpers are Nanorix-published convenience builders, not Nanorix-imposed schemas.


End-to-end mapping to the API surface

Every TypeScript SDK method maps to one or more API endpoints documented in api-reference.md:

TypeScript method API endpoint
Client.health() GET /v1/health
client.capsulefiles.create() POST /v1/capsulefiles (+ files + finalize)
client.capsulefiles.list() GET /v1/capsulefiles
client.capsulefiles.get(id) GET /v1/capsulefiles/:id
client.capsulefiles.delete(id) DELETE /v1/capsulefiles/:id
client.capsules.create() POST /v1/capsules
LiveCapsule.upload() POST /v1/capsules/:id/upload
LiveCapsule.execute() POST /v1/capsules/:id/exec
LiveCapsule.listOutputs() GET /v1/capsules/:id/output
LiveCapsule.download() GET /v1/capsules/:id/output/:filename
LiveCapsule.destroy() DELETE /v1/capsules/:id
client.capsules.list() GET /v1/capsules
client.capsules.get(id) GET /v1/capsules/:id
client.cdp.get() / client.auditProofs.get() GET /v1/capsules/:id/cdp
client.cdp.verify() GET /v1/capsules/:id/cdp/verify
client.egressProfiles.* /v1/egress/profiles/...
client.webhooks.* /v1/webhooks/...
client.usage.* /v1/usage/...
verifyAuditProof() offline (no network)

client.capsules.run(), .batch(), and .session() are SDK-side compositions over create + upload + exec + destroy, not separate endpoints.


Browser support

The SDK is Node-first; browser bundlers (Vite, esbuild, webpack) can import it but the Node crypto builtins must be polyfilled or the verifier subpackage must be excluded. The browser-native verifier ships as a separate package (forthcoming TypeScript browser verifier).

Recommended browser pattern: keep API key on a server-side proxy, expose only verifier-relevant calls to the client.


Cross-references

Forever-Standard discipline. Every public TypeScript SDK method, every option key, every exception class, every model field is permanent. New methods / options / classes / fields are additive only. The internal-vs-external naming alias (client.cdpclient.auditProofs) is structural, not transitional — both names exist forever.