VM

SDK Setup

The Oblien SDK provides a typed, ergonomic interface for the full VM API. It handles authentication, request formatting, error handling, and streaming for you.

SDK 3 requires the matching /vms API and vm.oblien.com gateway release. Upgrade customers after that service release is deployed and verified. See Migrating to SDK 3 for renamed calls, fields and credentials. SDK 2 is incompatible with the new service.

Installation

npm install oblien@3
# or with your preferred package manager
yarn add oblien@3
pnpm add oblien@3

Initialize the client

import Oblien from 'oblien';

const client = new Oblien({
  clientId: process.env.OBLIEN_CLIENT_ID!,
  clientSecret: process.env.OBLIEN_CLIENT_SECRET!,
});

The client.vms namespace gives you access to all VM operations. You can also use client.vm(id) to get a scoped handle bound to a single VM - see Scoped VM handle below.

SDK structure

// ── Core CRUD ──────────────────────────────────────
client.vms.create(params)          // Create a vm
client.vms.list(params?)           // List vms
client.vms.get(vmId)        // Get vm details
client.vms.update(vmId, data) // Update vm
client.vms.delete(vmId)     // Delete vm
client.vms.getDetails(vmId) // Aggregated details
client.vms.getQuota()              // Get resource quota
client.vms.getEstimate(params?)    // Estimate monthly cost

// ── Power ──────────────────────────────────────────
client.vms.start(vmId, { force? }) // Start vm
client.vms.stop(vmId)       // Stop vm
client.vms.restart(vmId)    // Restart vm
client.vms.pause(vmId)      // Pause vm
client.vms.resume(vmId)     // Resume paused vm
client.vms.ping(vmId, { ttl_seconds? }) // Keep-alive

// ── Runtime (data plane) ───────────────────────────
const rt = await client.vms.runtime(vmId)
rt.files       // .list(), .read(), .write(), .mkdir(), .stat(), .delete(), .stream()
rt.transfer    // .download(), .upload()
rt.exec        // .run(), .stream(), .subscribe(), .list(), .get(), .kill(), .input()
rt.terminal    // .create(), .list(), .close(), .scrollback()
rt.search      // .content(), .files(), .status(), .init()
rt.watcher     // .create(), .list(), .get(), .delete()
rt.ws()        // WebSocket for terminal I/O + watcher events

// ── Lifecycle ──────────────────────────────────────
client.vms.lifecycle  // .get(), .makePermanent(), .makeTemporary(), .updateTtl(), .ping(), .destroy()

// ── Network ────────────────────────────────────────
client.vms.network    // .get(), .update(), .applyOutboundIp()

// ── SSH ────────────────────────────────────────────
client.vms.ssh        // .status(), .enable(), .disable(), .setPassword(), .setKey()

// ── Public Access ──────────────────────────────────
client.vms.publicAccess // .list(), .expose(), .revoke()

// ── Resources ──────────────────────────────────────
client.vms.resources  // .get(), .update(), .patch()

// ── Snapshots ──────────────────────────────────────
client.vms.snapshots  // .create(), .restore(), .createArchive(), .listArchives(),
                             //  .getArchive(), .deleteArchive(), .deleteAllArchives()

// ── Workloads ──────────────────────────────────────
client.vms.workloads  // .create(), .list(), .get(), .update(), .patch(), .delete(),
                             //  .deleteAll(), .start(), .stop(), .status(), .logs(),
                             //  .stats(), .logsStream(), .statsStream(), .allStatsStream()

// ── Monitoring ─────────────────────────────────────
client.vms.metrics    // .stats(), .statsStream(), .info(), .config()

// ── Usage ──────────────────────────────────────────
client.vms.usage      // .get(), .totals(), .creditsChart(), .global(), .activity(),
                             //  .wipe(), .startTracking(), .stopTracking()

// ── Metadata ───────────────────────────────────────
client.vms.metadata   // .get(), .update(), .patch()

// ── API Access ─────────────────────────────────────
client.vms.apiAccess  // .status(), .enable(), .disable(), .rotateToken(), .getToken(), .rawToken()

// ── Logs ───────────────────────────────────────────
client.vms.logs       // .get(), .clear(), .listFiles(), .getFile(), .streamBoot(), .streamCmd()

// ── Images ─────────────────────────────────────────
client.vms.images     // .list()

Scoped VM handle

When working with a single VM, use client.vm(id) to get a scoped handle. Every method is pre-bound to that VM ID - no need to pass it repeatedly:

const ws = client.vm('ws_abc');

// Core operations - no ID needed
await ws.get();
await ws.start();
await ws.stop();
await ws.delete();

// Sub-resources are also scoped
await ws.ssh.enable();
await ws.network.get();
await ws.snapshots.create();
await ws.workloads.list();
await ws.metrics.stats();
await ws.logs.get();

// Runtime - smart enable + token caching
const rt = await ws.runtime();
await rt.exec.run(['ls', '-la']);
await rt.files.list({ directory: '/app' });

Use the collection style (client.vms.…) when iterating over many VMs. Use the scoped handle (client.vm(id)) when focused on a single VM.

Scoped handle reference

const ws = client.vm('ws_abc');

ws.id                    // 'ws_abc'

// ── Core ──────────────────────────────────────────
ws.get()                 // Fetch vm data
ws.update(params)        // Update name/config
ws.delete()              // Permanently delete
ws.getDetails()          // Aggregated details

// ── Power ─────────────────────────────────────────
ws.start({ force? })     // Start
ws.stop()                // Stop
ws.restart()             // Restart
ws.pause()               // Pause (preserves memory)
ws.resume()              // Resume paused
ws.ping({ ttl_seconds? })// Keep-alive

// ── Runtime ───────────────────────────────────────
ws.runtime()             // → Runtime (auto-enables + caches token)
ws.invalidateRuntime()   // Clear cached runtime token

// ── All sub-resources are available ───────────────
ws.lifecycle             // .get(), .makePermanent(), .makeTemporary(), …
ws.network               // .get(), .update(), .applyOutboundIp()
ws.ssh                   // .status(), .enable(), .disable(), …
ws.publicAccess          // .list(), .expose(), .revoke()
ws.resources             // .get(), .update()
ws.snapshots             // .create(), .restore(), .createArchive(), …
ws.workloads             // .create(), .list(), .get(), .start(), .stop(), …
ws.metrics               // .stats(), .statsStream(), .info(), .config()
ws.usage                 // .get(), .totals(), .creditsChart(), .wipe()
ws.metadata              // .get(), .update(), .patch()
ws.apiAccess             // .status(), .enable(), .disable(), .rotateToken(), …
ws.logs                  // .get(), .clear(), .listFiles(), .streamBoot(), …

Runtime - smart enable

Calling .runtime() uses a smart enable-if-needed flow:

  1. Check cache - if a valid runtime token exists (within TTL), return it instantly
  2. Check status - lightweight GET to see if the Runtime API is already enabled
  3. Get token - if enabled, fetch the gateway JWT (read-only)
  4. Enable - if not enabled, enable it first (only writes when necessary)
  5. Cache - store the token with a 55-minute TTL (tokens expire at 60 min)

This means repeated .runtime() calls in a hot loop are essentially free - no redundant network requests.

// Token is cached - second call is instant
const rt1 = await ws.runtime();
const rt2 = await ws.runtime(); // ← cache hit, no API call

// Force a fresh token when needed
const rt3 = await ws.runtime({ force: true });

// Manually invalidate the cache
ws.invalidateRuntime();

// Customize the cache TTL (default: 55 minutes)
client.vms.setTokenTtl(30 * 60 * 1000); // 30 minutes

Configuration options

const client = new Oblien({
  // Required
  clientId: 'your-client-id',
  clientSecret: 'your-client-secret',

  // Optional
  baseUrl: 'https://api.oblien.com',   // Custom base URL (for testing/self-hosted)
});

TypeScript support

The SDK is fully typed. All request params and responses have TypeScript interfaces:

import type {
  VMCreateParams,
  VMData,
  VMUpdateParams,
  VMListParams,
  ExecRunParams,
  ExecTask,
  ExecStreamEvent,
  NetworkUpdateParams,
  TerminalSession,
  StatsEvent,
  FileListParams,
  FileEntry,
} from 'oblien';

Error handling

The SDK throws typed errors you can catch and inspect:

import { OblienError } from 'oblien';

try {
  await ws.create({ image: 'oblien/node:24' });
} catch (err) {
  if (err instanceof OblienError) {
    console.log(err.code);    // 'SANDBOX_LIMIT_REACHED'
    console.log(err.status);  // 402
    console.log(err.message); // 'VM limit reached for your plan'
  }
}

Common error codes are documented in the Errors Reference.

Framework examples

Next.js API route

// app/api/sandbox/route.ts
import Oblien from 'oblien';

const client = new Oblien({
  clientId: process.env.OBLIEN_CLIENT_ID!,
  clientSecret: process.env.OBLIEN_CLIENT_SECRET!,
});

export async function POST(req: Request) {
  const { code } = await req.json();

  // Create and bind a scoped handle
  const data = await client.vms.create({
    image: 'oblien/node:24',
    config: { cpus: 1, memory_mb: 256 },
  });

  const ws = client.vm(data.id);
  await ws.start();
  const rt = await ws.runtime();

  const result = await rt.exec.run(
    ['node', '-e', code],
    { timeoutSeconds: 10 },
  );

  await ws.delete();
  return Response.json(result);
}

Express middleware

import Oblien from 'oblien';

const client = new Oblien({
  clientId: process.env.OBLIEN_CLIENT_ID!,
  clientSecret: process.env.OBLIEN_CLIENT_SECRET!,
});

app.post('/run', async (req, res) => {
  const data = await client.vms.create({
    image: 'oblien/python:3.13',
  });

  const ws = client.vm(data.id);
  await ws.start();
  const rt = await ws.runtime();

  const result = await rt.exec.run(
    ['python3', '-c', req.body.code],
  );

  await ws.delete();
  res.json(result);
});

Next steps