SDK reference

vos.net — Network

Sandboxed apps cannot reach the internet directly. vos.net.fetch routes requests through the VibeOS host, which re-validates every URL against the origins your app declared — and sidesteps CORS entirely.

window.vos.net1 methodrequires network

Why a proxy

VibeOS injects a strict Content-Security-Policy built from your declared origins, so a plain fetch(), XMLHttpRequest or WebSocket to anywhere else is blocked by the browser before it leaves the window. Even for allowed origins, most public APIs would then fail CORS.

  • No CORS. The request is made server-side, so APIs without CORS headers work normally.
  • Re-validated host-side. The allow-list is checked against the installed grant on every call — an app cannot widen its own reach at runtime.
  • Hardened. Private networks are unreachable and unsafe headers are stripped in both directions.

Declaring origins

List each https:// origin — optionally with a path prefix — in permissions.network. An entry matches any URL on the same origin whose path begins with the given prefix.

json
{
  "permissions": {
    "network": [
      "https://api.openlibrary.org/",
      "https://covers.openlibrary.org/"
    ]
  }
}
EntryMatches
https://api.example.comEvery path on that origin.
https://api.example.com/v1/Only URLs beginning /v1/.
https://api.example.com/v1/*Same as above — a trailing /* is stripped.
  • Wildcard hostnames and credentials in the URL are rejected.
  • "network": [] declares the capability with no targets — every request is blocked. Omit the key entirely if your app is offline.
  • Users approve the list at install time and can revoke individual origins later from Web Apps → app details → Permissions, so handle a permission failure gracefully at any time.

net.fetch

net.fetch

requires network
fetch(
  url: string,
  init?: { method?: string; headers?: Record<string, string>; body?: string },
): Promise<{
  status: number;
  statusText: string;
  headers: Record<string, string>;
  body?: string;        // text responses
  bodyBase64?: string;  // binary responses
}>

returns a plain response object — never a Response instance

Performs the request through the host. It resolves for any HTTP status, including 404 and 500 — check status yourself. It rejects only when the request never happened: missing permission, a disallowed URL, no session, or a transport failure.

javascript
{
  status: 200,
  statusText: "OK",
  headers: { "content-type": "application/json; charset=utf-8" },
  body: "{\"docs\":[...]}"     // text responses
  // bodyBase64: "iVBORw0K..."  // binary responses instead of body
}
  • init.body must be a string. Serialise JSON yourself; there is no FormData or stream support.
  • Response header names are lower-cased. set-cookie is stripped, so cookie-based sessions are not available.
  • These request headers are removed for safety: host, connection, content-length, transfer-encoding, cookie, authorization, proxy-authorization, origin, referer.

You cannot ship a secret API key

Your app is a plain HTML document the user can read, and the authorization header is stripped by the proxy. Only use APIs that are safe to call without a secret, or ask the user to paste their own key and store it in your app sandbox.

Examples

Fetch JSON

javascript
const res = await window.vos.net.fetch(
  "https://api.openlibrary.org/search.json?q=tolkien",
  { method: "GET", headers: { Accept: "application/json" } },
);

if (res.status >= 200 && res.status < 300) {
  const data = JSON.parse(res.body);
  render(data.docs.slice(0, 10));
} else {
  setStatus("Request failed with status " + res.status);
}

Send JSON

javascript
const res = await window.vos.net.fetch("https://api.example.com/v1/items", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ title: "New item" }),
});

// The proxy never throws on a non-2xx response — check the status yourself.
if (res.status === 201) setStatus("Created");

A fetch-like helper

Wrap the call once so the rest of your app reads like ordinary code.

javascript
// A small wrapper that behaves like the fetch you already know.
async function apiGet(url) {
  const res = await window.vos.net.fetch(url, {
    headers: { Accept: "application/json" },
  });
  if (res.status < 200 || res.status >= 300) {
    throw new Error("HTTP " + res.status + " " + res.statusText);
  }
  return JSON.parse(res.body);
}

Binary responses

The host decides text versus binary from the response content type. Binary bodies arrive as bodyBase64 instead of body — turn them into a data URI to display them, since the CSP blocks loading remote images directly.

javascript
const res = await window.vos.net.fetch("https://covers.openlibrary.org/b/id/240727-L.jpg");

if (res.bodyBase64) {
  const type = res.headers["content-type"] || "image/jpeg";
  img.src = "data:" + type + ";base64," + res.bodyBase64;
}

Limits & blocked hosts

LimitValue
Upstream request timeout15 seconds
SDK call timeout (whole round trip)30 seconds
Maximum response size5 MB — larger responses fail with a “response too large” error
Allowed protocolshttp and https only
Allow-list entriesUp to 32 per app

Requests to private and internal networks are refused outright, whatever your manifest says: localhost, *.local, *.internal, loopback and link-local addresses, IPv6 link-local and unique-local ranges, and the private IPv4 blocks 10.x, 127.x, 169.254.x, 172.16–31.x, 192.168.x and multicast.

Failure modes

Rejection message containsCause
permissionNo network permission, or the URL is not covered by the allow-list the user approved.
invalid urlMalformed URL, or a protocol other than http/https.
authNo signed-in VibeOS session to proxy the request under.
blocked hostThe target resolves to a private or internal address.
response too largeThe response exceeded the 5 MB cap.
timeoutThe upstream server did not answer within 15 seconds.

Degrade gracefully

Because a user can revoke an origin after install, treat network failure as a normal state: show cached data from your app sandbox when you have it, and a clear retry when you do not. For the full error model see errors & limits.