BoxOS

Developer documentation

Build small, durable BoxOS apps.

Publish immutable pages, put state and authority in boxes, and compose asynchronous work with durable Tasks.

Quickstart

Download the dependency-free CLI, create an Ed25519 account, and publish an HTML page.

curl -fsSL https://boxos.org/boxos-cli.js -o boxos
chmod +x boxos
./boxos account create
./boxos page publish ./index.html

Each command writes one JSON value to stdout. The page command returns its immutable ID and public https://<page-id>.boxos.org/ URL.

Core model

  • Accounts are Ed25519 public keys. Private keys remain with clients.
  • Blobs are immutable text addressed by SHA-256.
  • Pages are immutable HTML blobs with short public IDs.
  • Boxes contain validated methods and their own public and private storage.

A box method is one atomic SQLite turn. Its storage writes, transfers, Task declarations, and effects either commit together or roll back together.

Pages

Pages are ordinary HTML modules. Import the reference browser client from /client.js.

<!doctype html>
<button id="run">Run</button>
<script type="module">
  import { boxos } from "/client.js";

  document.querySelector("#run").onclick = async () => {
    const result = await boxos.invoke("BOX_ID", "increment", { amount: 1 });
    console.log(result.value);
  };
</script>

Every page subdomain has a separate browser origin and an origin-scoped page account stored in IndexedDB.

Boxes

A box definition is JSON containing JavaScript method bodies. Methods receive ctx and input and run in a deliberately small, validated JavaScript subset.

{
  "methods": {
    "increment": "let n = ctx.storage.public.get(\"count\") || 0; n = n + input.amount; ctx.storage.public.set(\"count\", n); return n;"
  }
}

Add an optional 16-to-128-character nonce to create a box with independent storage while reusing identical methods. The nonce participates in the content hash but is not tied to an account and grants no ownership.

{
  "nonce": "550e8400-e29b-41d4-a716-446655440000",
  "methods": { "run": "return input;" }
}

Method context

ctx.account
ctx.clientId
ctx.storage.public.get(key)
ctx.storage.public.set(key, value)
ctx.storage.private.get(key)
ctx.storage.private.set(key, value)
ctx.transfer(receiver, amount)
ctx.message(clientId, value)
ctx.invoke(boxId, method, input)
ctx.publish(kind, arguments)
ctx.request(request)

Box values are JSON-like: null, booleans, finite numbers, strings, arrays, and plain objects. Functions, Tasks, binary data, cycles, and undefined are not values.

Durable Tasks

ctx.invoke, ctx.publish, and ctx.request return runtime-owned Tasks. Returning a Task makes the caller wait for its complete chain.

return ctx.invoke(input.target, "read", input.query).then(
  function completed(result, saved) {
    ctx.storage.private.set(saved.key, result);
    return result;
  },
  { key: input.key }
);

Continuations execute later as fresh atomic turns. They cannot capture method locals; durable data must be supplied through the explicit callback context.

Tasks resemble Promises but are not native Promises. Box methods do not use async, await, or Promise.

CLI

./boxos box publish ./box.json
./boxos page publish ./index.html
./boxos invoke <box-id> increment '{"amount":1}'
./boxos blob publish ./data.txt
./boxos storage get <box-id> count
./boxos startup
./boxos health

Link local boxes

Pages and boxes can reference a box definition by a path relative to the file containing the reference:

const counterBox = "{{BOXOS_BOX:./counter.box.json}}";

Publishing resolves the complete graph, calculates its content IDs, and validates every linked box locally with the same parser used by the server. Only after the whole graph passes does the CLI publish boxes in dependency order and replace each marker with its immutable ID. Repeated paths are deduplicated and circular dependencies are rejected.

A parser rejection exits non-zero, reports the local box path, method, and source location, and publishes nothing. The server validates every box again as the security boundary.

Use --key, --url, BOXOS_KEY, and BOXOS_URL to override configuration. Run ./boxos --help for every command.

HTTP API

Public reads require no authentication. Mutations use canonical JSON and Ed25519 signatures.

GET  /health
GET  /v1/startup
GET  /v1/boxes/<box-id>
GET  /v1/boxes/<box-id>/storage/public?key=<key>
GET  /v1/blobs/<blob-id>
GET  /v1/pages/<page-id>
POST /v1/boxes
POST /v1/invoke
POST /v1/operations
POST /v1/events

An invocation response is sent only after its returned durable Task settles. Exact signed-request replay is idempotent.