Skip to content

Your first deploy

Run402 is a full-stack platform a coding agent provisions, deploys, and pays for on its own: Postgres, REST, auth, storage, functions, and static hosting behind one CLI. Use the CLI by default. This tutorial deploys one manifest and its referenced HTML file with one command. Use a new application directory; --name requests creation and -y approves the required setup.

Terminal window
npm install -g run402@latest

The manifest is the whole app: a migration, which tables the browser may reach, and the site. Wire fields are snake_case.

{
"$schema": "https://run402.com/schemas/release-spec.v1.json",
"database": {
"migrations": [
{ "id": "001_init", "sql": "CREATE TABLE IF NOT EXISTS items (id serial PRIMARY KEY, title text NOT NULL);" }
],
"expose": {
"version": "1",
"tables": [{ "name": "items", "expose": true, "policy": "public_read_append_only" }]
}
},
"site": {
"replace": {
"index.html": { "path": "index.html", "content_type": "text/html" }
}
}
}

This demo allows public reads and new submissions. Visitors cannot edit or delete existing items. Use an authenticated policy for private or user-owned data.

Create index.html alongside the manifest. It reads its own keys from the host it is served on — never paste a key into HTML:

<!doctype html>
<script src="/_run402/config.js"></script>
<ul id="items"></ul>
<script type="module">
const { api_base, anon_key } = window.RUN402;
const headers = { apikey: anon_key, "Content-Type": "application/json" };
await fetch(`${api_base}/rest/v1/items`, { method: "POST", headers, body: JSON.stringify({ title: "hello" }) });
const rows = await fetch(`${api_base}/rest/v1/items`, { headers }).then((r) => r.json());
const list = document.querySelector("#items");
for (const row of rows) {
const item = document.createElement("li");
item.textContent = row.title;
list.append(item);
}
</script>

window.RUN402 is { project_id, api_base, anon_key } for whatever project the page was loaded from — a branch copy or a transferred project stays correct without touching the HTML. The anon key is public by design; the service key is never served there.

A copy-and-deploy version of exactly this pattern (manifest, seeded notes table, page reading window.RUN402) lives in examples/static-config-js/.

Terminal window
run402 up --name my-app -y

up validates the files and explicit destination, then performs missing setup and deploys. A fresh Cloud profile uses the free prototype tier on testnet; an existing profile retains its configured authority and payment settings. The SDK coordinates the release; database migration effects are not automatically undone by promoting an older release.

The result is JSON. The following is a shortened example, not the complete response. Hand your human the two links it carries:

{
"result": {
"project_id": "prj_…",
"identity": {
"principal": { "id": "principal_example", "type": "agent", "display_name": "claude-code" },
"client": { "detected": "claude-code", "declared_name": null },
"display_name": "claude-code", "source": "detected"
},
"deploy": {
"status": "ready",
"urls": { "site": "https://my-app.run402.com", "console": "https://console.run402.com/orgs/…/projects/prj_…" },
"next_actions": [{ "type": "hand_to_operator", "credited_as": "claude-code", "why": "Show your human the site and the console link…" }]
}
}
}

A ready release is not proof that every application behavior works. Open the returned site, confirm the item appears, and check failures before reporting success. Use the deployment and verification reference for automated checks. For later authorized changes, run run402 up in the linked app directory.

Terminal window
run402 redeem <code>