API Reference

Scoped Tokens

Issue short-lived JWTs bound to a specific namespace or workspace. Designed for SaaS platforms that need to give end-users limited, temporary access without sharing long-lived API keys.

When to use scoped tokens — You're building a SaaS product on Oblien. Your backend holds an admin API key. When an end-user logs in, you issue them a namespace-scoped token that only lets them interact with workspaces in their tenant's namespace. The token expires automatically — no revocation needed.

How it works

┌─────────────────┐      POST /tokens       ┌──────────────────┐
│  Your Backend    │ ──────────────────────▶  │  Oblien API      │
│  (admin API key) │ ◀──────────────────────  │  returns JWT     │
└────────┬────────┘                          └──────────────────┘
         │ hand JWT to user

┌─────────────────┐    Bearer <jwt>          ┌──────────────────┐
│  End User        │ ──────────────────────▶  │  /workspace/*    │
│  (browser/app)   │                          │  (namespace only)│
└─────────────────┘                          └──────────────────┘
  1. Your backend authenticates with its admin API key
  2. Calls POST /tokens specifying the scope (namespace or workspace) and TTL
  3. Returns the short-lived JWT to the end-user
  4. The end-user uses Authorization: Bearer <jwt> — scoped enforcement kicks in automatically

Issue a token

Requires an admin-scoped API key.

The namespace must exist before you issue a token for it. Create it first via client.namespaces.create() or oblien ns create. The API validates that the namespace (or workspace) exists and belongs to your account — you'll get a 400 if it doesn't.

const { token, expiresAt } = await client.tokens.create({
  scope: 'namespace',
  namespace: 'tenant-abc',
  ttl: 900,
  label: 'user-session-42',
});

// Hand the token to your end-user
POST /tokens
{
  "scope": "namespace",
  "namespace": "tenant-abc",
  "ttl": 900,
  "label": "user-session-42"
}
curl -X POST https://api.oblien.com/tokens \
  -H "X-Client-ID: $OBLIEN_CLIENT_ID" \
  -H "X-Client-Secret: $OBLIEN_CLIENT_SECRET" \
  -H "Content-Type: application/json" \
  -d '{
    "scope": "namespace",
    "namespace": "tenant-abc",
    "ttl": 900
  }'
oblien tokens create --scope namespace --namespace tenant-abc --ttl 900

# With JSON output
oblien tokens create --scope namespace --namespace tenant-abc --json

# Workspace-scoped
oblien tokens create --scope workspace --workspace-id ws_abc123

Parameters

ParameterTypeRequiredDescription
scopestringYes"namespace" or "workspace"
namespacestringWhen scope=namespaceNamespace slug to bind the token to
workspaceIdstringWhen scope=workspaceWorkspace ID to bind the token to
ttlnumberNoToken lifetime in seconds (1–3600, default: 900)
labelstringNoHuman-readable label (included in JWT for debugging)

Response 201

{
  "success": true,
  "token": "eyJhbGciOiJIUzI1NiIs...",
  "expiresAt": "2026-03-15T12:15:00.000Z",
  "scope": "namespace",
  "ttl": 900
}

Errors

StatusCodeCause
400validation_errorMissing or invalid scope/namespace/workspace
400validation_errorNamespace or workspace does not exist
401missing_credentialsNo API key provided
403scope_deniedAPI key is not admin-scoped

Using scoped tokens

The end-user passes the JWT as a Bearer token. It works everywhere API keys work — SDK, REST, cURL, and MCP.

// End-user client — initialize with the scoped token
import Oblien from 'oblien';

const userClient = new Oblien({ token });

// Only sees workspaces in the bound namespace
const workspaces = await userClient.workspaces.list();

// Can create workspaces (forced into the bound namespace)
const ws = await userClient.workspaces.create({
  image: 'node-20',
  config: { cpus: 2, memory_mb: 2048 },
});

// When the token is refreshed, swap it without re-creating the client:
userClient.setToken(newToken);
const response = await fetch('https://api.oblien.com/workspace', {
  headers: {
    'Authorization': `Bearer ${token}`,
    'Content-Type': 'application/json',
  },
});
curl https://api.oblien.com/workspace \
  -H "Authorization: Bearer $SCOPED_TOKEN"

Scope permissions

Scoped tokens follow the exact same enforcement as scoped API keys:

Namespace scope

ActionResult
List workspacesFiltered to bound namespace only
Create workspaceForced into bound namespace (server-side)
Access workspace in own namespaceAllowed
Access workspace in another namespaceBlocked (403)
Access other services (pages, CDN, etc.)Blocked (403)
Create API keys or tokensBlocked (403)

Workspace scope

ActionResult
Access bound workspaceAllowed (all sub-resources)
List workspacesBlocked (403)
Create workspacesBlocked (403)
Access any other workspaceBlocked (403)

Token properties

PropertyValue
FormatSigned JWT (HS256), standard Authorization: Bearer header
Max TTL1 hour (3600 seconds)
Default TTL15 minutes (900 seconds)
StorageStateless — no DB row, no Redis entry
RevocationNot supported (by design — short TTL makes it unnecessary)
ValidationJWT signature + expiry check only
EnforcementSame middleware chain as API keys (requireScope, enforceScopeOnList, requireSandboxOwnership)

Full SaaS example

End-to-end flow: customer signs up → create namespace → issue scoped token → user creates workspaces.

import Oblien from 'oblien';

// ─── Your backend (runs on your server with admin key) ─────────

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

// 1. Customer signs up → create their namespace
const ns = await admin.namespaces.create({
  name: `customer-${customerId}`,
  type: 'production',
  resource_limits: {
    max_workspaces: plan.maxWorkspaces,
    max_vcpus: plan.maxCpus,
    max_ram_mb: plan.maxRamMb,
  },
});

// 2. Set spending quota
await admin.namespaces.setQuota({
  namespace: ns.data.slug,
  service: 'sandbox',
  quotaLimit: plan.monthlyCredits,
  period: 'monthly',
  overdraft: plan.monthlyCredits * 0.1,
  onOverdraftAction: 'stop_workspaces',
});

// 3. Customer logs into your app → issue them a scoped token
const { token, expiresAt } = await admin.tokens.create({
  scope: 'namespace',
  namespace: ns.data.slug,
  ttl: 3600, // 1 hour
});

// Return token + expiresAt to your frontend

// ─── Your frontend (runs in the user's browser) ────────────────

const userClient = new Oblien({ token });

// User can only see and create workspaces in their namespace
const workspaces = await userClient.workspaces.list();

const newWorkspace = await userClient.workspaces.create({
  image: 'node-20',
  config: { cpus: 2, memory_mb: 2048 },
});
// namespace is automatically set to the bound namespace — cannot be overridden

// When the token expires, get a fresh one and swap it in:
const fresh = await fetchNewTokenFromYourBackend();
userClient.setToken(fresh.token);

API key scopes vs scoped tokens

Both enforce the same permissions. Choose based on your use case:

API Key (client_id/secret)Scoped Token (JWT)
LifetimeUntil revoked1 hour max
Auth methodX-Client-ID + X-Client-Secret headersAuthorization: Bearer header
StorageRow in databaseStateless (no storage)
RevocableYes (delete from dashboard or API)No (expires naturally)
Best forBackend services, CI/CD, long-running agentsEnd-user sessions, browser apps, temporary access
CreationDashboard UI or session authPOST /tokens with admin API key

Rule of thumb — Use API keys for your backend. Use scoped tokens for anything you hand to an end-user.