Server runtime

How Verklet moves from the browser backend to managed server execution for server-only Python, uv, and native-tool workloads.

Verklet is browser-first. The default backend runs Node-style workloads inside the visitor's tab, using workers, WebAssembly, OPFS, and the preview service worker.

Some workloads need a real server environment: uv, native Python wheels, subprocess-heavy Python, native Linux tools, larger memory ceilings, or a workspace that should outlive browser storage. Supported Python runs in the browser through Pyodide first. For everything else, the SDK can boot a server backend directly or promote from the browser backend when a command requires it.

Choosing a backend

import { Runtime } from '@verklet/sdk';

const runtime = await Runtime.boot({
  projectId: 'prj_your_project_id',
  backend: 'auto',
  persistenceKey: 'tutorial-session',
  server: {
    grant: async (request) => {
      const response = await fetch('/api/verklet/server-grant', {
        method: 'POST',
        headers: { 'content-type': 'application/json' },
        body: JSON.stringify(request),
      });
      return response.json();
    },
    prepare: [['npm', 'ci']],
  },
});

backend: 'auto' is the recommended default for hybrid products. It starts in the browser, runs supported python, python3, pip, and pip3 through Pyodide, then promotes to the server backend for unsupported Python workloads, uv, and native-tool commands.

Server promotion requires a short-lived broker grant. The SDK does not fetch that grant automatically because production apps usually need their own authentication, authorization, headers, and budget checks. Pass server.grant as a grant object, token string, or callback. The callback receives projectId, runtimeId, workspaceKey, the action, and the browser installation ID, so your backend can mint a scoped grant only after it knows what workspace is being promoted.

Use backend: 'browser' when you explicitly want browser-only execution. Use backend: 'server' when the whole session should start on the server.

Account setup

Before server promotion can work, configure the project in /account:

The fixed beta resource policy is one CPU and 512 MB memory per server session. Those limits are enforced by the broker and are not editable in the account UI yet.

Private grant endpoint

For production, create a small endpoint in your app that authenticates the user, checks that the requested project/workspace belongs to that user or tenant, then asks Verklet to mint the broker grant with your private vks_... secret.

This example uses a Next.js route handler:

// app/api/verklet/server-grant/route.ts
import type { ServerRuntimeGrantRequest } from '@verklet/sdk';

const verkletGrantSecret = process.env.VERKLET_PRIVATE_GRANT_SECRET;

export async function POST(request: Request) {
  if (!verkletGrantSecret) {
    return Response.json({ error: 'Server grants are not configured' }, { status: 503 });
  }

  const user = await requireUser(request);
  const body = (await request.json()) as ServerRuntimeGrantRequest;

  if (!await userCanUseVerkletProject(user, body.projectId, body.workspaceKey)) {
    return Response.json({ error: 'Not found' }, { status: 404 });
  }

  const response = await fetch(
    `https://verklet.com/api/runtime/server/grants?projectId=${encodeURIComponent(body.projectId)}`,
    {
      method: 'POST',
      headers: {
        authorization: `Bearer ${verkletGrantSecret}`,
        'content-type': 'application/json',
      },
      body: JSON.stringify(body),
    },
  );

  const grant = await response.json();
  return Response.json(grant, {
    status: response.status,
    headers: { 'cache-control': 'no-store' },
  });
}

The SDK calls your server.grant callback with:

FieldMeaning
actioncreate_session when starting or promoting a server session, delete_persistence when deleting server persistence.
projectIdThe public project ID from Runtime.boot(). Verify it belongs to the current tenant.
runtimeIdThe SDK runtime ID. Required for create_session.
workspaceKeyThe server workspace key. Defaults to server.workspaceKey, then persistenceKey, then runtimeId.
persistenceKeyIncluded when the workspace came from a persistence key.
browserInstallationIdA stable browser installation identifier for grant telemetry and rate checks.

The hosted grant endpoint returns:

type ServerRuntimeGrant = {
  brokerToken: string;
  brokerUrl?: string;
  expiresAt?: string;
};

Return that JSON directly from your backend. Do not store it in local storage, cookies, or logs. Grants are short-lived, action-scoped, and single-use for create-session and persistence deletion.

Browser SDK wiring

Point server.grant at your endpoint. The callback runs only when the SDK has enough context to request a scoped grant:

const runtime = await Runtime.boot({
  projectId: 'prj_your_project_id',
  backend: 'auto',
  persistenceKey: 'tutorial-session',
  server: {
    grant: async (grantRequest) => {
      const response = await fetch('/api/verklet/server-grant', {
        method: 'POST',
        headers: {
          authorization: `Bearer ${await getYourAppSessionToken()}`,
          'content-type': 'application/json',
        },
        body: JSON.stringify(grantRequest),
      });

      if (!response.ok) {
        throw new Error(`Server grant denied: ${response.status}`);
      }

      return response.json();
    },
  },
});

If you already minted a grant on the server while rendering the page, you can pass that object directly. A direct grant is useful for backend: 'server', but a callback is usually better for backend: 'auto' because promotion may happen later and needs the final runtimeId and workspaceKey.

const runtime = await Runtime.boot({
  projectId: 'prj_your_project_id',
  backend: 'server',
  server: { grant: initialServerGrant },
});

Public grant minting

Public minting lets browser code call Verklet's hosted grant endpoint without your private vks_... secret. It is disabled by default and only works after you explicitly enable it in /account.

const runtime = await Runtime.boot({
  projectId: 'prj_your_project_id',
  backend: 'auto',
  server: {
    grant: async (grantRequest) => {
      const response = await fetch(
        `https://verklet.com/api/runtime/server/grants?projectId=${encodeURIComponent(grantRequest.projectId)}`,
        {
          method: 'POST',
          headers: { 'content-type': 'application/json' },
          body: JSON.stringify(grantRequest),
        },
      );
      return response.json();
    },
  },
});

Use this only for controlled demos or low-risk apps with strict caps. Origin checks reduce drive-by use, but they are not a secret or authentication boundary. Anything that can run JavaScript on an allowed origin can attempt to mint public grants until the project caps stop it. For example, a request with an allowed Origin header is enough to ask for a public grant when public minting is enabled.

Server dependency setup

Promotion sends source files and project manifests to the server workspace, not browser-specific install output such as node_modules. Successful deterministic install commands run in the browser before promotion are replayed on the server by default. This includes commands such as npm install, npm ci, pnpm install, yarn install, pip install, python -m pip install, uv sync, uv pip install, and poetry install.

Use server.prepare for explicit server-only setup:

const runtime = await Runtime.boot({
  projectId: 'prj_your_project_id',
  backend: 'auto',
  server: {
    grant: window.__serverGrantFromYourBackend,
    prepare: [
      ['npm', 'ci'],
      ['pip', 'install', '-r', 'requirements.txt'],
    ],
  },
});

On first promotion, Verklet mounts the source snapshot, replays the successful browser install commands unless server.replayInstalls is false, runs server.prepare, then reruns the command that required the server backend.

What promotion does

When automatic promotion happens, Verklet exports a binary snapshot of the browser filesystem without browser-only dependency artifacts, creates a server runtime session, mounts that snapshot into the server workspace, performs server dependency setup, tears down the browser runtime, and continues through the same SDK object.

Your UI keeps using the same APIs:

projectId is public and safe in browser code. Broker grants and vks_... private grant secrets are not. Use a backend endpoint for production grants, keep the private secret server-side, and set allowed origins plus caps in /account. Hosted public minting is available only when explicitly enabled for a project and should be treated as an abuse-prone convenience path.

Deleting server persistence also needs a scoped grant:

await Runtime.clearPersistence('tutorial-session', {
  backend: 'server',
  projectId: 'prj_your_project_id',
  grant: async (grantRequest) => {
    const response = await fetch('/api/verklet/server-grant', {
      method: 'POST',
      headers: { 'content-type': 'application/json' },
      body: JSON.stringify(grantRequest),
    });
    return response.json();
  },
});

Capabilities

The browser backend is best for cheap, low-latency JavaScript, Node-compatible demos, and supported Python/Pyodide work. It uses OPFS for persisted snapshots when the browser supports it.

The browser backend reports python: 'pyodide'. The server backend reports capabilities such as backend: 'server', linux: true, nativePackages: true, python: 'system', checkpoints: true, and persistentFilesystem: 'server'. It is the right path for Python package stacks that need native wheels, agent tasks that call native tools, and workflows that should not depend on browser storage.

Pricing and server hours

Server-runtime usage is included during beta. After beta, non-commercial projects keep browser-runtime access and paid projects include a monthly pool of server hours.

A server hour is one hour where a Verklet server runtime is active for a project. Browser-only execution does not use server hours. Usage is measured by the minute and shown as hours on invoices.

Starter includes 40 server hours per month, Pro includes 300 server hours per month, and Scale includes 2,500 server hours per month. Extra server time is billed at $0.30/hr on Starter and Pro, and $0.18/hr on Scale. See pricing for the current plan limits.

Data and limits

Server runtime sessions send workspace snapshots, commands, stdout/stderr, exit codes, quota events, and usage metadata to Verklet's runtime broker. The broker enforces beta limits for session creation, active sessions, active processes, snapshot size, and request size. Grant attempts, denials, allowed origins, project caps, and usage events are stored so Verklet can enforce suspension, budget, and quota decisions.

Browser runtime files stay in the origin's OPFS unless you explicitly promote or start a server backend. See the privacy policy for the data boundary and pricing for usage terms.