Network
Configure network policies for your workspace - internet access, inbound/outbound firewall rules, private links between workspaces, and dedicated outbound IPs.
Get network
Get the current network configuration.
const network = await ws.network.get('ws_a1b2c3d4');GET /workspace/:workspaceId/networkcurl "https://api.oblien.com/workspace/ws_a1b2c3d4/network" \
-H "X-Client-ID: $OBLIEN_CLIENT_ID" \
-H "X-Client-Secret: $OBLIEN_CLIENT_SECRET"Response
{
"success": true,
"ip": "10.0.0.15",
"gateway": "10.0.0.1",
"public_access": false,
"allow_internet": true,
"ingress": [],
"ingress_ports": [],
"egress": [],
"egress_ports": [],
"outbound_ip": null,
"outbound_mode": "managed",
"outbound_proxy": null,
"private_links": []
}Response fields
| Field | Type | Description |
|---|---|---|
ip | string | Workspace private IP address (use this for direct connections) |
gateway | string | Network gateway |
public_access | boolean | Whether public inbound traffic is allowed (required for gateway access) |
allow_internet | boolean | Whether outbound internet is allowed |
ingress | string[] | Allowed inbound source IPs (managed automatically via private links) |
ingress_ports | number[] | Allowed inbound ports |
egress | string[] | Allowed outbound hosts/CIDRs |
egress_ports | number[] | Allowed outbound ports |
outbound_ip | string|null | Dedicated outbound IP if assigned |
outbound_mode | "managed"|"custom" | "managed" (default) routes through the pool IP; "custom" routes through your own proxy. See Custom outbound proxy |
outbound_proxy | object|null | Present only when outbound_mode === "custom". Shape: { host, port, protocol, has_credentials } — the password itself is never returned |
private_links | object[] | Linked workspaces with resolved IPs (see Private Links) |
The ip field is the workspace's internal network address. You'll need this when making direct workspace-to-workspace calls. You can also get it from the raw token endpoint, which returns both the IP and the connection token in one call.
Update network
Partially update network policy. Only the fields you provide are changed.
await ws.network.update('ws_a1b2c3d4', {
allow_internet: true,
ingress_ports: [80, 443, 3000],
egress: ['api.github.com', 'registry.npmjs.org'],
public_access: false,
});PATCH /workspace/:workspaceId/network{
"allow_internet": true,
"ingress_ports": [80, 443, 3000],
"egress": ["api.github.com", "registry.npmjs.org"],
"public_access": false
}curl -X PATCH "https://api.oblien.com/workspace/ws_a1b2c3d4/network" \
-H "X-Client-ID: $OBLIEN_CLIENT_ID" \
-H "X-Client-Secret: $OBLIEN_CLIENT_SECRET" \
-H "Content-Type: application/json" \
-d '{
"allow_internet": true,
"ingress_ports": [80, 443, 3000],
"egress": ["api.github.com"]
}'Parameters
All fields are optional:
| Parameter | Type | Description |
|---|---|---|
allow_internet | boolean | Enable/disable outbound internet. Setting to false clears egress |
ingress_ports | number[] | Allowed inbound ports (1–65535, max 50 entries) |
egress | string[] | Allowed outbound hosts/CIDRs (max 50 entries, printable ASCII) |
public_access | boolean | Allow public inbound traffic (required for gateway access) |
private_link_ids | string[] | Workspace IDs to link - resolved to private IPs and added to firewall ingress (see Private Links) |
outbound_mode | "managed"|"custom" | Set to "custom" to route through your own proxy (requires custom_proxy), "managed" to revert to the pool IP. See Custom outbound proxy |
custom_proxy | object | Required when outbound_mode: "custom". See Custom outbound proxy for shape |
Validation rules
- Port numbers must be between 1 and 65535
- Maximum 50 entries per list (ingress, egress, ingress_ports)
- Hosts must be printable ASCII characters
- Setting
allow_internet: falseforcesegress: [] public_access: trueadds thehostkeyword to the ingress list
Private links
Private links allow workspace-to-workspace communication over the internal network. By default, every workspace is network-dark - no inbound traffic is allowed from any source, including other workspaces in the same account. Private links explicitly whitelist specific workspaces.
When you set private_link_ids on a workspace, the platform resolves each workspace ID to its internal IP and adds it to the target's firewall ingress. The linked workspaces can then reach the target over the private 10.x.x.x network.
// Allow ws_caller to reach ws_target
await ws.network.update('ws_target', {
private_link_ids: ['ws_caller'],
});// Allow multiple workspaces
await ws.network.update('ws_target', {
private_link_ids: ['ws_caller_1', 'ws_caller_2', 'ws_orchestrator'],
});PATCH /workspace/:workspaceId/network{
"private_link_ids": ["ws_caller_1", "ws_caller_2"]
}curl -X PATCH "https://api.oblien.com/workspace/ws_target/network" \
-H "X-Client-ID: $OBLIEN_CLIENT_ID" \
-H "X-Client-Secret: $OBLIEN_CLIENT_SECRET" \
-H "Content-Type: application/json" \
-d '{ "private_link_ids": ["ws_caller_1", "ws_caller_2"] }'How it works
- You provide workspace IDs (not raw IPs) in
private_link_ids - The platform resolves each ID to its internal
10.x.x.xIP - Resolved IPs are added to the target workspace's firewall
ingresslist - The
public_accessstate is preserved - private links don't affect gateway access - Only workspaces that successfully resolve to an IP are linked
Response
The update returns the resolved link IDs:
{
"success": true,
"private_link_ids": ["ws_caller_1", "ws_caller_2"]
}Viewing linked workspaces
The GET /network response includes resolved private link details:
{
"private_links": [
{
"id": "ws_caller_1",
"ip": "10.40.0.5",
"name": "my-agent",
"image_logo": "node",
"image_color": "#3C873A"
},
{
"id": "ws_caller_2",
"ip": "10.40.0.12",
"name": "orchestrator",
"image_logo": "python",
"image_color": "#306998"
}
]
}Removing links
Set private_link_ids to an empty array to remove all links:
await ws.network.update('ws_target', {
private_link_ids: [],
});Private links are one-directional. Linking ws_caller → ws_target allows the caller to reach the target, but the target cannot reach the caller unless you also create a link in the other direction.
Private links are required for direct workspace-to-workspace access to the Runtime API. Without a link, the target's firewall blocks the connection even if the caller knows the IP and has a valid token.
List available outbound IPs
Get all Zones IPs in the pool that can be assigned to a workspace as a dedicated outbound IP.
GET /zone/minecurl "https://api.oblien.com/zone/mine" \
-H "X-Client-ID: $OBLIEN_CLIENT_ID" \
-H "X-Client-Secret: $OBLIEN_CLIENT_SECRET"Response
{
"success": true,
"proxies": [
{ "ip": "203.0.113.50", "country_code": "US" },
{ "ip": "198.51.100.12", "country_code": "DE" }
]
}Use any ip value from this list when calling Apply outbound IP below.
Apply outbound IP
Assign a dedicated outbound IP address to the workspace. All outbound traffic will route through this IP. Useful for whitelisting in third-party firewalls.
POST /workspace/:workspaceId/network/ip{
"ip": "203.0.113.50"
}curl -X POST "https://api.oblien.com/workspace/ws_a1b2c3d4/network/ip" \
-H "X-Client-ID: $OBLIEN_CLIENT_ID" \
-H "X-Client-Secret: $OBLIEN_CLIENT_SECRET" \
-H "Content-Type: application/json" \
-d '{ "ip": "203.0.113.50" }'Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
ip | string | Yes | Outbound IP from GET /zone/mine |
Response
{
"success": true,
"ip": "203.0.113.50",
"country_code": "US"
}Errors
| Code | Status | Cause |
|---|---|---|
ip_required | 400 | ip field missing from request body |
invalid_zone_ip | 400 | IP is not in the available pool |
zone_missing_host | 422 | Zone configuration is incomplete |
pool_empty | 503 | Egress zone temporarily unavailable |
Custom outbound proxy (BYOP)
Bring your own HTTP/HTTPS proxy and route this workspace's outbound traffic through it. Use this when you need outbound traffic to exit from an IP you control — for SOC2 audits, IP allowlists managed by a third party, or carrier-grade proxy providers.
Custom outbound proxy is a Scale plan feature. Workspaces on Free, Hobby, or Pro plans will receive a plan_required (HTTP 402) error. Upgrade in the billing dashboard to enable it.
How it works
When you set a custom proxy, the platform installs nftables DNAT rules on the host so that every outbound TCP connection from the workspace is redirected to your proxy. The workspace doesn't need to be proxy-aware — there is no HTTPS_PROXY env var to set, no library configuration, no SDK changes inside the workspace. The redirection is transparent at the kernel level.
Before applying, the platform runs a live CONNECT oblien.com:443 preflight against your proxy. If the preflight fails (unreachable, auth rejected, wrong protocol), the request is rejected and your previous outbound configuration is preserved — bad proxies cannot break outbound traffic.
Set custom proxy
await ws.network.setCustomProxy('ws_a1b2c3d4', {
host: '203.0.113.10',
port: 3128,
protocol: 'http',
username: 'my-user',
password: 'my-pass',
});Or use the generic update if you want to combine BYOP with other network changes:
await ws.network.update('ws_a1b2c3d4', {
outbound_mode: 'custom',
custom_proxy: {
host: '203.0.113.10',
port: 3128,
protocol: 'http',
},
ingress_ports: [443],
});PATCH /workspace/:workspaceId/network{
"outbound_mode": "custom",
"custom_proxy": {
"host": "203.0.113.10",
"port": 3128,
"protocol": "http",
"username": "my-user",
"password": "my-pass"
}
}curl -X PATCH "https://api.oblien.com/workspace/ws_a1b2c3d4/network" \
-H "X-Client-ID: $OBLIEN_CLIENT_ID" \
-H "X-Client-Secret: $OBLIEN_CLIENT_SECRET" \
-H "Content-Type: application/json" \
-d '{
"outbound_mode": "custom",
"custom_proxy": {
"host": "203.0.113.10",
"port": 3128,
"protocol": "http",
"username": "my-user",
"password": "my-pass"
}
}'Clear custom proxy (revert to managed)
await ws.network.clearCustomProxy('ws_a1b2c3d4');PATCH /workspace/:workspaceId/network{ "outbound_mode": "managed" }curl -X PATCH "https://api.oblien.com/workspace/ws_a1b2c3d4/network" \
-H "X-Client-ID: $OBLIEN_CLIENT_ID" \
-H "X-Client-Secret: $OBLIEN_CLIENT_SECRET" \
-H "Content-Type: application/json" \
-d '{ "outbound_mode": "managed" }'Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
host | string | Yes | Proxy IPv4 address. Hostnames are not accepted — pre-resolve to an IP. Reserved/private ranges (RFC1918, loopback, link-local, multicast, etc.) are rejected |
port | number | Yes | Proxy port, 1–65535 |
protocol | "http"|"https" | No | Proxy protocol. Defaults to "http". Both speak HTTP CONNECT — "https" adds TLS to the workspace↔proxy hop |
username | string | No | Username for Proxy-Authorization. If set, password is required |
password | string | No | Password for Proxy-Authorization. Required if username is set. Printable ASCII only, no : @ / ? or whitespace |
Reading back the proxy
GET /workspace/:workspaceId/network returns the proxy as outbound_proxy:
{
"outbound_mode": "custom",
"outbound_proxy": {
"host": "203.0.113.10",
"port": 3128,
"protocol": "http",
"has_credentials": true
}
}The password itself is never returned — only has_credentials: true|false so the dashboard can show a "saved" indicator without leaking the secret.
Errors
| Code | Status | Cause |
|---|---|---|
plan_required | 402 | Workspace owner is not on the Scale (or Enterprise) plan |
missing_custom_proxy | 400 | outbound_mode: "custom" was sent without a custom_proxy object |
invalid_outbound_mode | 400 | outbound_mode was not "custom" or "managed" |
invalid_proxy_host | 400 | host is not a valid IPv4 |
reserved_proxy_host | 400 | host is in a reserved/private range (e.g. 10.0.0.0/8, 192.168.0.0/16) |
invalid_proxy_port | 400 | port is not an integer in 1–65535 |
invalid_proxy_protocol | 400 | protocol is not "http" or "https" |
invalid_proxy_credentials | 400 | Credentials contain forbidden characters or username was set without password |
preflight_protocol_mismatch | 400 | The proxy responded but does not speak the protocol you specified |
preflight_auth_required | 401 | The proxy rejected your credentials |
preflight_bad_status | 502 | The proxy returned a non-2xx status to CONNECT |
preflight_unreachable | 502 | The proxy address is unreachable from the host |
preflight_timeout | 504 | The proxy did not respond within the preflight window |
preflight_closed | 502 | The proxy closed the connection without responding |
Security notes
- The
hostis validated against a list of 16 reserved IPv4 ranges before any kernel state is touched. You cannot point the proxy at the host's loopback or internal network. - Credentials are sent to your proxy as a base64-encoded
Proxy-Authorization: Basic …header. The base64 alphabet (A-Za-z0-9+/=) means credentials cannot inject extra HTTP headers regardless of what characters you put in them — but we still reject:,@,/,?, and whitespace in the validator for defense in depth. - nftables rules are built with typed netlink structs (host IP becomes 4 raw bytes, port becomes a
uint16). There is no string-rendered rule path, so a malicious-looking IP cannot inject extra nftables statements. - When the workspace is deleted, the per-VM nftables chain and cached credentials are torn down as part of normal VM cleanup.
Learn more about networking concepts in Concepts: Networking.