Workspace

SDK Setup

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

Installation

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

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.workspaces namespace gives you access to all workspace operations. You can also use client.workspace(id) to get a scoped handle bound to a single workspace - see Scoped workspace handle below.

SDK structure

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

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

// ── Runtime (data plane) ───────────────────────────
const rt = await client.workspaces.runtime(workspaceId)
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.workspaces.lifecycle  // .get(), .makePermanent(), .makeTemporary(), .updateTtl(), .ping(), .destroy()

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

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

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

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

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

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

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

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

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

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

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

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

Scoped workspace handle

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

const ws = client.workspace('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.workspaces.…) when iterating over many workspaces. Use the scoped handle (client.workspace(id)) when focused on a single workspace.

Scoped handle reference

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

ws.id                    // 'ws_abc'

// ── Core ──────────────────────────────────────────
ws.get()                 // Fetch workspace 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.workspaces.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 {
  WorkspaceCreateParams,
  WorkspaceData,
  WorkspaceUpdateParams,
  WorkspaceListParams,
  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: 'node-20' });
} catch (err) {
  if (err instanceof OblienError) {
    console.log(err.code);    // 'SANDBOX_LIMIT_REACHED'
    console.log(err.status);  // 402
    console.log(err.message); // 'Workspace 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.workspaces.create({
    image: 'node-20',
    config: { cpus: 1, memory_mb: 256 },
  });

  const ws = client.workspace(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.workspaces.create({
    image: 'python-3.12',
  });

  const ws = client.workspace(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