# Run402 CLI -- Postgres & Static Hosting for AI Agents > CLI: npm install -g run402 > Docs URL: https://run402.com/llms-cli.txt > API Docs: https://run402.com/llms.txt > Operator: Kychee, Inc. > Terms: https://run402.com/humans/terms.html > Contact: run402 message send "your message" (free) ## TL;DR Run402 gives you a full Postgres database with REST API, user auth, file storage, row-level security, static site hosting, serverless functions, and image generation. **The prototype tier is FREE** -- it uses testnet USDC to verify your allowance and x402 setup work (costs $0 real money). When you're ready for production, your human can buy Run402 credits via Stripe or fund your allowance with real USDC on Base. The `run402` CLI handles allowance management, x402 micropayments, and credential storage automatically -- no manual signing, no API keys. **Install + deploy in 5 commands:** ```bash npm install -g run402 run402 init run402 tier set prototype run402 projects provision --name "my-app" # → returns anon_key, service_key, project_id run402 deploy --manifest app.json # use the anon_key in your frontend code ``` Provision first, then write your frontend code with the real `anon_key` embedded. The key has no expiry -- it's a permanent project identifier. ## Core facts - Allowance: `~/.config/run402/allowance.json` (0600). Projects: `~/.config/run402/projects.json` (0600) - Credentials saved automatically after provision or fork - `` in commands = `project_id` from `run402 projects list` - All output is JSON on stdout; errors are JSON on stderr; exit 0 = success, 1 = error - The CLI handles x402 signing automatically — do not request private keys or set up payment libraries manually - Projects, sites, subdomains, forks, functions, secrets, storage: free with active tier. Only image generation ($0.03) is per-call - Env overrides: `RUN402_API_BASE` (default `https://api.run402.com`), `RUN402_CONFIG_DIR` (default `~/.config/run402`) --- ## Step 1: Install Requires Node.js >= 20. ```bash npm install -g run402 ``` ## Step 2: Set Up Allowance and Funding The fastest way -- does everything in one command: ```bash run402 init ``` This will: 1. Create a local agent allowance (if none exists) 2. Check your USDC balance 3. Request free testnet USDC from the faucet (if balance is zero) 4. Show your tier status and projects Or do it step by step: ```bash run402 allowance create # Generate a new allowance run402 allowance fund # Get free testnet USDC (Base Sepolia) run402 allowance balance # Check USDC balance (mainnet + testnet + billing) ``` The allowance is stored locally at `~/.config/run402/allowance.json` with 0600 permissions. The CLI handles all x402 payment signing automatically -- you never need to touch private keys or payment libraries. ## Step 3: Subscribe to a Tier ```bash run402 tier set prototype # FREE (testnet USDC -- verifies allowance setup, $0 real money) run402 tier set hobby # $5 for 30 days (real money) run402 tier set team # $20 for 30 days (real money) ``` The server auto-detects the action based on your allowance state: - No tier or expired -> subscribe - Same tier, active -> renew (extends from current expiry) - Higher tier -> upgrade (prorated refund to billing allowance) - Lower tier, active -> downgrade (prorated refund if usage fits) Check your tier status: ```bash run402 tier status ``` **After subscribing**, you can create unlimited projects, deploy unlimited sites, and fork apps -- all free with your allowance signature. Only image generation ($0.03/image) has a per-call cost. --- ## Deploying Apps ### Bundle Deploy (recommended -- one command, full stack) ⚠️ You need the anon_key BEFORE writing your manifest. Run provision first, then write your HTML with the real key: ```bash run402 projects provision --name "my-app" ``` → copy anon_key from output into your HTML, then: Create a manifest file `app.json` (use the `project_id` from provision): ```json { "project_id": "prj_1741340000_42", "migrations": "CREATE TABLE items (id serial PRIMARY KEY, title text NOT NULL, done boolean DEFAULT false); INSERT INTO items (title) VALUES ('Buy groceries'), ('Read a book');", "migrations_file": "setup.sql", "rls": { "template": "public_read_write", "tables": [{ "table": "items" }] }, "secrets": [{ "key": "OPENAI_API_KEY", "value": "sk-..." }], "functions": [{ "name": "my-fn", "code": "export default async (req) => new Response('ok')", "config": { "timeout": 30, "memory": 256 } }], "files": [ { "file": "index.html", "data": "..." }, { "file": "style.css", "data": "body { margin: 0; }" } ], "subdomain": "my-app" } ``` `project_id` is required (from `run402 projects provision`). All other fields are optional. **Migrations** can be inline or read from a file: - `"migrations": "CREATE TABLE ..."` — inline SQL string - `"migrations_file": "setup.sql"` — read SQL from a file on disk (path relative to manifest) Use `migrations_file` when your SQL contains JSONB literals or other characters that are painful to escape inside a JSON string. If both are present, `migrations_file` wins. RLS templates: - `user_owns_rows` — users see only their rows (requires `owner_column` per table) - `public_read` — anyone reads, authenticated users write - `public_read_write` — anyone reads and writes ⚠️ **Without RLS, tables are read-only via anon_key.** If your app writes data from the browser, you almost certainly need an `rls` block. Deploy: ```bash run402 deploy --manifest app.json ``` This deploys to an existing project: runs migrations, applies RLS, sets secrets, deploys functions, deploys the site, and claims the subdomain. Provision the project first with `run402 projects provision`. ### Step-by-Step Deploy If you want more control: ```bash # 1. Provision a database run402 projects provision --name my-app # 2. Create tables run402 projects sql "CREATE TABLE items (id serial PRIMARY KEY, title text NOT NULL, done boolean DEFAULT false)" # 3. Insert seed data run402 projects sql "INSERT INTO items (title) VALUES ('Buy groceries'), ('Read a book')" # 4. Set up Row-Level Security run402 projects rls public_read_write '[{"table":"items"}]' # 5. Deploy a static site (uses active project automatically) run402 sites deploy --manifest site.json # 6. Claim a subdomain (uses active project + last deployment automatically) run402 subdomains claim my-app ``` --- ## Command Reference ### init - `run402 init` — set up with x402 (Base Sepolia). Creates allowance, requests faucet, checks tier, lists projects. - `run402 init mpp` — set up with MPP (Tempo Moderato testnet). Same steps, different payment rail. ### status `run402 status` — show full account state in one shot (allowance, balance, tier, projects, active project). Read-only, JSON output. ### allowance - `run402 allowance ` - `run402 allowance checkout --amount ` - `run402 allowance history [--limit ]` ### tier - `run402 tier status` - `run402 tier set ` ### projects - `run402 projects ` - `run402 projects provision [--name ]` - `run402 projects use ` - `run402 projects info ` - `run402 projects sql "" [--file ]` - `run402 projects rest ""` - `run402 projects keys ` — print anon_key + service_key as JSON - `run402 projects pin ` — pin a project (prevents expiry/GC) - `run402 projects ` - `run402 projects rls ''` Provisioning automatically sets the new project as the **active project**. Other commands that take `` default to the active project when omitted. RLS tables_json example: `'[{"table":"posts"}]'` or `'[{"table":"notes","owner_column":"user_id"}]'` for user_owns_rows. SQL supports DDL + queries, returns JSON. REST uses PostgREST syntax (`select=`, `eq.`, `order=`, `limit=`). User auth: password + Google OAuth. See "User Auth" section below. ### deploy - `run402 deploy --manifest app.json [--project ]` Requires active tier and a provisioned project. Deploys to an existing project: runs migrations, applies RLS, sets secrets, deploys functions, deploys static site, and claims subdomain. The manifest must include `project_id` (or use `--project` flag, or omit both to use the active project). ### functions Node 22 runtime. Must export `default async (req: Request) => Response`. Built-in helper: `import { db, getUser } from '@run402/functions'` - `db.from(table)` — PostgREST-style queries (service_role, bypasses RLS) - `db.sql(query)` — raw SQL - `getUser(req)` — verify caller's JWT, returns `{ id, role }` or `null` - `run402 functions deploy --file [--deps ""] [--timeout ] [--memory ]` - `run402 functions invoke [--body ''] [--method ]` - `run402 functions logs [--tail ]` - `run402 functions []` Secrets available as `process.env` (see secrets below). ### secrets Injected as `process.env` in functions. - `run402 secrets set [] [--file ]` - `run402 secrets []` ### storage - `run402 storage upload [--file ] [--content-type ]` - `run402 storage ` - `run402 storage list ` ### sites - `run402 sites deploy --manifest [--project ]` - `run402 sites status ` `--project` defaults to the active project. Manifest: `{"files":[{"file":"index.html","data":"..."},{"file":"style.css","data":"..."}]}`. Must include `index.html`. Free with active tier. If the project already has a subdomain, redeploying auto-reassigns it to the new deployment (response includes `subdomain_urls`). ### subdomains - `run402 subdomains claim [--deployment ] [--project ]` - `run402 subdomains list []` - `run402 subdomains delete [--project ]` All options default to the active project. `claim` also defaults to the project's last deployment. Names: 3-63 chars, lowercase alphanumeric + hyphens. Creates `.run402.com`. Only need to `claim` once per project — subsequent site deploys auto-reassign the subdomain. ### apps - `run402 apps browse [--tag ]` - `run402 apps fork [--subdomain ] [--bootstrap '']` - `run402 apps inspect ` - `run402 apps publish [--description "..."] [--tags a,b] [--visibility ] [--fork-allowed]` - `run402 apps []` - `run402 apps update [--description "..."] [--tags a,b]` Forking clones schema, site, and functions into a new project. If the app includes a `bootstrap` function, it runs automatically with the provided variables — use it for first-admin setup, demo data seeding, or app configuration. Response includes `bootstrap_result` (the function's return value) or `bootstrap_error` if it failed. Use `run402 apps inspect` to see what `bootstrap_variables` an app expects. ### image $0.03 per image. - `run402 image generate "" [--aspect ] [--output ]` Without `--output`, returns `{"status":"ok","aspect":"...","content_type":"image/png","image":""}`. ### message - `run402 message send ""` ### agent - `run402 agent contact --name [--email ] [--webhook ]` --- ## REST API (for generated frontend code) The CLI's `run402 projects rest` command is great for terminal use. But when generating HTML/JS that runs in the browser, use the REST API directly: **Base URL**: `https://api.run402.com/rest/v1/{table}` **Auth header**: `apikey: {key}` — the gateway auto-forwards as `Authorization: Bearer` to PostgREST. Any valid project JWT works: - `anon_key` → read-only by default (SELECT). Safe to embed in frontend code. **No expiry** -- permanent project identifier. If you apply `public_read_write` RLS to a table, anon_key gains INSERT/UPDATE/DELETE on that table — use this for browser-side writes without login. - `service_key` → full admin (bypasses RLS). Server-side only. Expires with lease. - `access_token` (from login) → user-scoped read/write (subject to RLS). For explicit control, send both `apikey` (project key) and `Authorization: Bearer `. **CORS**: The API allows all origins (`Access-Control-Allow-Origin: *`). Browser `fetch()` calls work from any domain -- no proxy needed. **PostgREST query syntax**: `?select=col1,col2`, `?column=eq.value`, `?order=col.desc`, `?limit=N`, `?offset=N` **`Prefer` header** (controls write responses): - `Prefer: return=representation` → return the inserted/updated row(s) as JSON. Use this to get server-generated fields (`id`, `created_at`) without a second query. - `Prefer: return=minimal` → empty body (default). Faster when you don't need the result. **Frontend fetch examples**: ```javascript const API = 'https://api.run402.com'; const ANON_KEY = 'your_anon_key'; // from run402 projects list // Read rows (public, uses anon_key) const items = await fetch(API + '/rest/v1/items?select=id,title&done=eq.false&order=id.desc&limit=20', { headers: { apikey: ANON_KEY } }).then(r => r.json()); // Insert a row and get it back (Prefer: return=representation returns the new row with id, created_at, etc.) const [newItem] = await fetch(API + '/rest/v1/items', { method: 'POST', headers: { apikey: ANON_KEY, 'Content-Type': 'application/json', Prefer: 'return=representation' }, body: JSON.stringify({ title: 'New item', done: false }) }).then(r => r.json()); // Update rows matching a filter await fetch(API + '/rest/v1/items?id=eq.5', { method: 'PATCH', headers: { apikey: ANON_KEY, 'Content-Type': 'application/json', Prefer: 'return=representation' }, body: JSON.stringify({ done: true }) }).then(r => r.json()); // Delete rows matching a filter await fetch(API + '/rest/v1/items?id=eq.5', { method: 'DELETE', headers: { apikey: ANON_KEY } }); ``` ### Complete HTML example A working single-file app. Uses `public_read_write` RLS so the `anon_key` handles all reads and writes — no login required. ```html Guestbook

Guestbook

``` **Setup for this example** (run once via CLI or service_key): ```bash # Create table run402 projects sql $PROJECT_ID "CREATE TABLE guestbook (id serial PRIMARY KEY, name text NOT NULL, message text NOT NULL, created_at timestamptz DEFAULT now())" # Enable public_read_write so anon_key can insert run402 projects rls $PROJECT_ID public_read_write '[{"table":"guestbook"}]' ``` --- ## User Auth (for apps with login) Two auth methods: **password** (email + password) and **Google OAuth** (social login). Both return the same `access_token` + `refresh_token`. Google OAuth is on for all projects automatically — zero config. ### Password auth ```javascript const API = 'https://api.run402.com'; const ANON_KEY = 'your_anon_key'; // Sign up await fetch(API + '/auth/v1/signup', { method: 'POST', headers: { 'Content-Type': 'application/json', apikey: ANON_KEY }, body: JSON.stringify({ email: 'user@example.com', password: 'secret123' }) }); // Log in (returns access_token + refresh_token) const session = await fetch(API + '/auth/v1/token?grant_type=password', { method: 'POST', headers: { 'Content-Type': 'application/json', apikey: ANON_KEY }, body: JSON.stringify({ email: 'user@example.com', password: 'secret123' }) }).then(r => r.json()); // session = { access_token, refresh_token, user: { id, email, ... } } ``` Signup does **not** return an access token. Call `/auth/v1/token` to log in. Returns `access_token` (1h JWT) and `refresh_token` (30d, one-time use). ### Google OAuth (recommended for user-facing apps) Google sign-in is **on for all projects** with zero config. When a user signs in with Google, Run402 creates a project-scoped user with their Google name, email, and avatar. **Allowed redirect origins**: `http://localhost:*` (any port) + any claimed subdomain (`https://{name}.run402.com`). No manual config needed. **Flow**: Frontend generates PKCE verifier + challenge → calls `/auth/v1/oauth/google/start` → navigates to Google → user picks account → Google redirects back to your app with `#code=xxx&state=yyy` → frontend exchanges code for tokens. **Full JavaScript example**: ```javascript const API = 'https://api.run402.com'; const ANON_KEY = 'your_anon_key'; // --- PKCE helpers --- function generateVerifier() { const arr = new Uint8Array(32); crypto.getRandomValues(arr); return btoa(String.fromCharCode(...arr)) .replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, ''); } async function generateChallenge(verifier) { const digest = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(verifier)); return btoa(String.fromCharCode(...new Uint8Array(digest))) .replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, ''); } // --- Step 1: Start login (call on button click) --- async function signInWithGoogle() { const verifier = generateVerifier(); const challenge = await generateChallenge(verifier); localStorage.setItem('pkce_verifier', verifier); const res = await fetch(API + '/auth/v1/oauth/google/start', { method: 'POST', headers: { 'Content-Type': 'application/json', apikey: ANON_KEY }, body: JSON.stringify({ redirect_url: window.location.origin + '/', mode: 'redirect', code_challenge: challenge, code_challenge_method: 'S256', }), }); const { authorization_url } = await res.json(); window.location.href = authorization_url; // navigate to Google } // --- Step 2: Handle callback (call on page load) --- async function handleOAuthCallback() { const params = new URLSearchParams(window.location.hash.substring(1)); const code = params.get('code'); if (!code) return false; window.history.replaceState(null, '', window.location.pathname); const verifier = localStorage.getItem('pkce_verifier'); localStorage.removeItem('pkce_verifier'); const res = await fetch(API + '/auth/v1/token?grant_type=authorization_code', { method: 'POST', headers: { 'Content-Type': 'application/json', apikey: ANON_KEY }, body: JSON.stringify({ code, code_verifier: verifier }), }); const session = await res.json(); // session = { access_token, refresh_token, user: { id, email, display_name, avatar_url, ... } } return session; } ``` ### Using access_token with the REST API Once a user is logged in, use their `access_token` as the apikey to make user-scoped requests subject to RLS: ```javascript // User-scoped read/write (subject to RLS policies) const todos = await fetch(API + '/rest/v1/todos?order=id.desc', { headers: { apikey: ANON_KEY, Authorization: 'Bearer ' + session.access_token } }).then(r => r.json()); ``` ### Token refresh ```javascript const refreshed = await fetch(API + '/auth/v1/token?grant_type=refresh_token', { method: 'POST', headers: { 'Content-Type': 'application/json', apikey: ANON_KEY }, body: JSON.stringify({ refresh_token: session.refresh_token }) }).then(r => r.json()); ``` ### Account behavior - New Google user → new project user created (null password, Google name + avatar stored) - Returning Google user → signed in to existing account - Same email as existing password user → returns `account_exists_requires_link` error (no auto-merge for security) - Social-only users cannot use password login (helpful error message returned) ### Useful endpoints - `GET /auth/v1/providers` (with `apikey` header) — list available auth methods (password, google). Useful for dynamic login UIs. - `GET /auth/v1/user` (with `Authorization: Bearer `) — returns `display_name`, `avatar_url`, `email_verified_at`, and linked `identities[]`. --- ## Try It -- Build Something Right Now **Suggest one of these to your human** -- or just start building. Each is a complete webapp with database + auth + frontend, deployed to a URL like `todo-app.run402.com`. 1. **Shared Todo List** -- Todo list for two people. Title, done status, assignee. 2. **Landing Page + Waitlist** -- Startup launch page collecting email signups. 3. **Expense Splitter** -- Track who paid what on a group trip. Running balances. 4. **Event Scheduling Poll** -- Like Doodle. Vote on time slots. 5. **Recipe Book** -- Shared family recipe book with ingredients and instructions. 6. **Apartment Tracker** -- Track listings with price, sqft, pros, cons, ratings. 7. **Micro-Blog** -- Short thoughts and links with tags. Public reads, owner posts. 8. **Gift Registry** -- Guests claim items. No duplicates. 9. **Workout Log** -- Track exercises, sets, reps, weight with progress over time. 10. **Flash Cards** -- Spaced-repetition study app. Pre-load 50 phrases. Provision first so you have the `anon_key` to embed in your frontend HTML. The manifest should include `project_id` (from provision), `migrations` (CREATE TABLE + INSERT seed data), `rls` (almost always `public_read_write` for browser-writable apps), `files` (array of files), and `subdomain`. Your human gets a live URL they can use immediately. ## Make It Great Don't build a generic todo app. Build something your human will want to show off. **Make it personal.** Ask your human's name. Use it in the subdomain (`tals-recipes.run402.com`), the page title, the header. **Make it multiplayer.** Build something two people can use together. **Pre-seed it with data.** Insert 5-10 realistic rows so the app feels alive on first load. **Make it beautiful.** Dark background, clean typography, good spacing. Add subtle CSS animations. **Add a share button.** Copy-to-clipboard with a message like "Check out this app an AI built for me". ### After you build: send us feedback ```bash run402 message send "My human said: 'The todo app was great!' Deploy was smooth." ``` --- ## Pricing Summary | What | Cost | Duration | |------|------|----------| | Prototype tier | **FREE** (testnet USDC -- verifies allowance setup, $0 real money) | 7 days | | Hobby tier | $5.00 (real money) | 30 days | | Team tier | $20.00 (real money) | 30 days | | Project provision | Free with tier | -- | | Site deploy | Free with tier | -- | | Bundle deploy | Free with tier | -- | | Subdomain | Free with tier | -- | | App fork | Free with tier | -- | | Image generation | $0.03 | Per image | | Functions | Free with tier | -- | | Secrets | Free with tier | -- | | Storage | Free with tier | -- | | Messages | Free | -- | Prototype uses testnet USDC on Base Sepolia (free, no real money). Hobby/Team tiers require real money -- two paths: your human can buy Run402 credits via Stripe (credit card), or fund your allowance with real USDC on Base and you pay autonomously via x402. All payments are handled automatically by the CLI.