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@3Initialize 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:
- Check cache - if a valid runtime token exists (within TTL), return it instantly
- Check status - lightweight
GETto see if the Runtime API is already enabled - Get token - if enabled, fetch the gateway JWT (read-only)
- Enable - if not enabled, enable it first (only writes when necessary)
- 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 minutesConfiguration 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
- API Reference - Full endpoint documentation with SDK + REST examples
- Quickstart - Build something in 5 minutes
- Concepts: Modes - Permanent vs temporary VMs