API Reference

Edge Tunnel

Expose a local port to the internet through the Oblien edge. Traffic to either a managed subdomain or an exact verified custom hostname is forwarded through a multiplexed WebSocket tunnel to an agent running on your machine.

How it works

  1. Check the hostname if you want to use an exact custom domain
  2. Create a tunnel specifying a name, port, and optional slug/domain
  3. Issue a token — a short-lived JWT for the tunnel connection
  4. Connect an agent (SDK or CLI) using the token — traffic flows through the WebSocket tunnel to localhost:port

The edge handles TLS termination. The agent authenticates with a JWT and maintains a persistent WebSocket connection to the broker.

For exact custom hostnames, certificate issuance is shared with the same custom-domain SSL flow used by pages and workspaces. Existing valid certificates are reused, and the scheduler auto-renews them before expiry.

Tunnels are independent of workspaces. They route traffic from the edge directly to a local port on whatever machine runs the agent.

Custom domain flow

There are two tunnel modes:

  1. Managed subdomain: pass a slug and optional base domain, for example my-app.preview.oblien.com
  2. Exact custom hostname: pass domain: "api.example.com" and leave slug empty

Exact custom hostnames are gated by TXT ownership verification and global route collision checks. The create call re-checks ownership server-side, so the preflight check is advisory and cannot be used to bypass verification.

Check a custom tunnel domain

Use this before create when you want an exact custom hostname.

POST /edge/tunnels/check-domain
Content-Type: application/json

{
  "domain": "api.example.com",
  "port": 3000
}
const check = await client.edgeTunnel.checkDomain({
  domain: 'api.example.com',
  port: 3000,
});

if (check.mode === 'custom_domain' && !check.ownership) {
  console.log(check.required_records.txt);
}
curl -X POST "https://api.oblien.com/edge/tunnels/check-domain" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"domain":"api.example.com","port":3000}'

Check response

{
  "success": true,
  "mode": "custom_domain",
  "domain": "api.example.com",
  "available": true,
  "needs_verification": true,
  "verified": false,
  "cname": false,
  "ownership": false,
  "reusable": false,
  "existing_tunnel": null,
  "required_records": {
    "cname": { "host": "api.example.com", "target": "edge.oblien.com" },
    "txt": { "host": "_oblien.api.example.com", "value": "verify=tunnel_abc123" }
  },
  "errors": [
    "No matching TXT record found — add TXT \"verify=tunnel_abc123\" at _oblien.api.example.com"
  ]
}

SDK Usage

One-liner: create + connect

import Oblien from 'oblien';

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

const tunnel = await client.edgeTunnel.connect({
  name: 'dev-server',
  port: 3000,
});

console.log(tunnel.url);
// → https://dev-server-x7k.preview.oblien.com

tunnel.on('request', (streamId, port) => {
  console.log(`→ localhost:${port}`);
});

// Later…
tunnel.close();

Two-sided: SaaS backend generates token, client connects

// ── Server side (your SaaS backend) ──
import Oblien from 'oblien';

const admin = new Oblien({ clientId, clientSecret });

// Create a tunnel for each user/project
const { tunnel } = await admin.edgeTunnel.create({
  name: 'user-preview',
  port: 3000,
  slug: 'user-123-preview',
});

// Issue a token to hand to the client
const { token, connect_url } = await admin.edgeTunnel.issueToken(tunnel.id);

// Send token + connect_url to user's machine via your own API
// ── Client side (user's machine) ──
import { TunnelClient } from 'oblien';

const tc = new TunnelClient({
  connectUrl: connect_url,   // from your backend
  token: token,              // from your backend
  localPort: 3000,
});

tc.on('open', () => console.log('connected'));
tc.on('request', (id, port) => console.log(`→ localhost:${port}`));
tc.connect();

Exact custom hostname

const check = await client.edgeTunnel.checkDomain({
  domain: 'api.example.com',
});

if (!check.ownership) {
  console.log('Add this TXT record first:', check.required_records.txt);
}

const { tunnel } = await client.edgeTunnel.create({
  name: 'production-api',
  port: 3000,
  domain: 'api.example.com',
});

console.log(tunnel.url);
// → https://api.example.com

List tunnels

GET /edge/tunnels
curl "https://api.oblien.com/edge/tunnels" \
  -H "Authorization: Bearer $TOKEN"

Response

{
  "success": true,
  "tunnels": [
    {
      "id": 1,
      "name": "dev-server",
      "slug": "dev-server-x7k",
      "domain": "preview.oblien.com",
      "tunnel_id": "a1b2c3d4e5f6...",
      "port": 3000,
      "url": "https://dev-server-x7k.preview.oblien.com",
      "status": "active",
      "created_at": "2026-04-13T10:00:00Z"
    }
  ]
}

Create a tunnel

POST /edge/tunnels
Content-Type: application/json

{
  "name": "dev-server",
  "port": 3000,
  "slug": "my-app"
}
curl -X POST "https://api.oblien.com/edge/tunnels" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"name":"dev-server","port":3000}'

Parameters

ParameterTypeRequiredDescription
namestringYesDisplay name (max 100 chars)
portnumberYesTarget port on the agent machine (1–65535)
slugstringNoSubdomain slug. Leave empty when using an exact custom hostname.
domainstringNoBase domain for slug-based tunnels, or an exact custom hostname when slug is omitted.

If you pass an exact custom hostname like api.example.com, the API requires TXT ownership verification and rejects the create call until verification succeeds.

If the same user already owns the hostname on the same port, the API can reuse the existing tunnel instead of creating a duplicate.

Response

{
  "success": true,
  "tunnel": {
    "id": 1,
    "name": "dev-server",
    "slug": "dev-server-x7k",
    "domain": "preview.oblien.com",
    "hostname": "dev-server-x7k.preview.oblien.com",
    "is_custom": false,
    "tunnel_id": "a1b2c3d4e5f6...",
    "port": 3000,
    "url": "https://dev-server-x7k.preview.oblien.com",
    "status": "active"
  }
}

For exact custom hostnames, tunnel objects also include a live ssl object:

{
  "ssl": {
    "status": "active",
    "expiresAt": "2026-07-22",
    "error": null
  }
}

Update a tunnel

Update name, slug, or port.

Exact custom-domain tunnels cannot change slug after creation.

PUT /edge/tunnels/:id
Content-Type: application/json

{ "name": "new-name", "port": 8080 }
ParameterTypeDescription
namestringNew display name
slugstringNew subdomain slug
portnumberNew target port

Issue a connection token

Generate a short-lived JWT for agent authentication. The token is valid for 24 hours.

POST /edge/tunnels/:id/token
curl -X POST "https://api.oblien.com/edge/tunnels/1/token" \
  -H "Authorization: Bearer $TOKEN"

Response

{
  "success": true,
  "token": "eyJhbGciOiJIUzI1NiIs...",
  "tunnel_id": "a1b2c3d4e5f6...",
  "port": 3000,
  "expires_in": "24h",
  "connect_url": "wss://edge.oblien.com/connect"
}

Enable / Disable

Toggle a tunnel without deleting it. Disabling removes the edge route (traffic stops), enabling re-creates it.

POST /edge/tunnels/:id/enable
POST /edge/tunnels/:id/disable

Renew tunnel SSL

Force certificate provisioning or renewal for a connected custom-domain tunnel.

POST /edge/tunnels/:id/ssl/renew
const result = await client.edgeTunnel.renewSSL(42);

console.log(result.ssl.status);
console.log(result.ssl.expiresAt);
curl -X POST "https://api.oblien.com/edge/tunnels/42/ssl/renew" \
  -H "Authorization: Bearer $TOKEN"

Renew response

{
  "success": true,
  "domain": "api.example.com",
  "ssl": {
    "status": "active",
    "expiresAt": "2026-07-22",
    "error": null
  },
  "tunnel": {
    "id": 42,
    "url": "https://api.example.com"
  }
}

Delete a tunnel

Removes the edge route and DB record permanently.

DELETE /edge/tunnels/:id