Proxy
Reach an HTTP or WebSocket server running on a loopback port inside the workspace (for example a dev server on 127.0.0.1:3000) as if you were talking to it directly. Request and response headers, streaming/SSE bodies, and WebSocket upgrades all pass through near-natively.
Requires the internal server to be enabled.
The proxy is loopback-only. Targets must resolve to 127.0.0.1 / localhost / ::1. Requests to any other host are rejected with 400 — the proxy cannot be used to reach the workspace's private network or the internet.
Overview
1. A service listens on 127.0.0.1:<port> inside the workspace (e.g. a dev server)
2. Call /proxy with the target port → runtime forwards to 127.0.0.1:<port>
3. HTTP response / stream / WS frames → passed back through untouchedThe runtime connection token is used to authenticate the request to /proxy and is stripped before the request reaches the in-VM app, so it never leaks to code running inside the workspace.
Target resolution
The target port is selected by the first of these that is present:
| Source | Form | Notes |
|---|---|---|
| Header | X-Oblien-Proxy-Target: <port> or <host>:<port> | Preferred for HTTP. Path passes through untouched. |
| Query | x_oblien_target=<port> or <host>:<port> | For browsers / WebSocket, which cannot set request headers. |
| Path | /proxy/<port>/<upstream path> | Convenience form; the port segment is stripped. |
When the header or query form is used, the upstream path is the request path with the leading /proxy removed. The host, if given, must be a loopback address. token and x_oblien_target are reserved query params and are removed before forwarding upstream.
HTTP request
Forward an HTTP request to a loopback port. The SDK returns the raw Response so you get the upstream status, headers, and body (including streams).
const rt = await client.workspaces.runtime('ws_a1b2c3d4');
// GET → raw fetch Response
const res = await rt.proxy(3000).fetch('/api/users?limit=10');
const users = await res.json();
// POST with a body
await rt.proxy(3000).fetch('/api/users', {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify({ name: 'ada' }),
});GET https://workspace.oblien.com/proxy/api/users?limit=10
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...
X-Oblien-Proxy-Target: 3000Equivalent path form (no header):
GET https://workspace.oblien.com/proxy/3000/api/users?limit=10
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...# Header form
curl "https://workspace.oblien.com/proxy/api/users?limit=10" \
-H "Authorization: Bearer $GATEWAY_JWT" \
-H "X-Oblien-Proxy-Target: 3000"
# Path form
curl "https://workspace.oblien.com/proxy/3000/api/users?limit=10" \
-H "Authorization: Bearer $GATEWAY_JWT"The response is returned verbatim — status code, headers, and body. Streaming responses (chunked, Server-Sent Events) are flushed through immediately.
Streaming (SSE)
const stream = await rt.proxy(8080).fetch('/events');
for await (const chunk of stream.body!) {
process.stdout.write(new TextDecoder().decode(chunk));
}WebSocket
Open a WebSocket to a loopback port. Auth and target are passed as query params (token, x_oblien_target) because the browser WebSocket API cannot set request headers.
const ws = rt.proxy(3000).ws('/socket');
ws.onmessage = (e) => console.log(e.data);
ws.onopen = () => ws.send('hello');wss://workspace.oblien.com/proxy/socket?token=<GATEWAY_JWT>&x_oblien_target=3000Error responses
| Status | Meaning |
|---|---|
400 | No target given, invalid port, or a non-loopback host was requested |
401 | Missing or invalid token |
502 | Nothing is listening on 127.0.0.1:<port>, or the upstream connection failed |