Dory is in development. The invite-only beta has not opened yet; prices and limits are planned and may change.

Expose a web app

Start your application inside a running, ready computer, check its guest-local health endpoint and create a preview for its listening port. Preview creation returns a hostname and short-lived access information; issuing it does not prove the application is healthy.

Browser and program access

A preview is private by default. A program client uses a per-preview program token in Authorization. A browser uses a clean same-origin POST exchange of an access link to obtain a browser grant. Neither credential is a general control API key. Keep the returned access_url_expires_at; do not assume the access URL lasts as long as the preview.

Program-token and access-link deadlines are capped by their issuing credential. An account-owned share link can outlive the API key that created it, subject to its own expiry, preview lifetime and revocation. Reloading an already redeemed clean browser page uses the existing grant; it is not another share-link redemption.

Share and revoke

Create an explicit share link only for someone who should see the application. The default is ten uses; each redemption consumes a use atomically. Redemptions are limited to ten per minute and at most 100 live grants per link. Creating another key does not reset these budgets. Revoking the link also invalidates its redeemed grants within 15 seconds. Revoke a program token separately when that integration ends.

Tier 1 permits 20 previews, 100 active program credentials and five share links; Tier 2 permits 100, 500 and 25. Exhausted shares remain share objects until revoked or expired. A downgrade limits new issuance without silently extending or changing existing authority. See Previews for the full deadline table and exact exchange rules.

Diagnose application failures

An inaccessible or stopped guest service can return 502; start/resume remains an explicit lifecycle action. A preview does not start the computer, renew its runtime timeout or change billing mode. Check the process, port, guest logs and current computer state. Close or revoke the preview when finished. Do not trust cookies set for the parent preview domain in your app.

Upload and read application files

These complete programs use CE_COMPUTER_ID for an existing running computer. Each creates one fresh guest directory, checks raw binary and Range reads, sends two multipart files, and removes only that directory in cleanup. No JSON encoding is applied to the file bytes and no local secret file is uploaded.

Python

import os
import uuid
from computeengines import Client

with Client() as client:
    computer = client.computers.get(os.environ["CE_COMPUTER_ID"])
    directory = "/home/dev/docs-" + uuid.uuid4().hex
    computer.files.mkdir(directory)
    try:
        payload = b"\x00\xff\x01hello\x00"
        path = directory + "/payload.bin"
        computer.files.write(path, payload)  # raw bytes, not a JSON envelope
        if computer.files.read(path) != payload or computer.files.read(path, range="bytes=1-3") != payload[1:4]:
            raise RuntimeError("Binary or Range read changed bytes")
        uploaded = computer.files.batch({directory + "/first.bin": payload, directory + "/second.bin": payload[::-1]})
        if len(uploaded.files) != 2:
            raise RuntimeError("Multipart upload did not return both files")
        for name, expected in (("first.bin", payload), ("second.bin", payload[::-1])):
            if computer.files.read(directory + "/" + name) != expected:
                raise RuntimeError("Multipart bytes changed")
        print("Binary, Range and multipart readbacks verified")
    finally:
        computer.files.remove(directory, recursive=True)

TypeScript

import { randomUUID } from "node:crypto";
import { Buffer } from "node:buffer";
import { Client } from "@computeengines/agent-computers";

const id = process.env.CE_COMPUTER_ID;
if (!id) throw new Error("Set CE_COMPUTER_ID");
const client = new Client();
try {
  const computer = await client.computers.get(id);
  const directory = "/home/dev/docs-" + randomUUID();
  await computer.files.mkdir(directory);
  try {
    const payload = Buffer.from([0, 255, 1, 104, 101, 108, 108, 111, 0]);
    const path = directory + "/payload.bin";
    await computer.files.write(path, payload);
    if (!Buffer.from(await computer.files.read(path)).equals(payload)
        || !Buffer.from(await computer.files.read(path, { range: "bytes=1-3" })).equals(payload.subarray(1, 4))) {
      throw new Error("Binary or Range read changed bytes");
    }
    const files = { [directory + "/first.bin"]: payload, [directory + "/second.bin"]: Buffer.from(payload).reverse() };
    if ((await computer.files.batch(files)).files.length !== 2) throw new Error("Multipart response incomplete");
    for (const [name, expected] of Object.entries(files)) {
      if (!Buffer.from(await computer.files.read(name)).equals(expected)) throw new Error("Multipart bytes changed");
    }
    console.log("Binary, Range and multipart readbacks verified");
  } finally { await computer.files.remove(directory, { recursive: true }); }
} finally { client.close(); }

Dory is in development; these pages describe the planned service and may change. Questions: support@computeengines.com.