# Run402

**Category:** 🗄️ Databases  
**Repository:** https://github.com/kychee-com/run402  
**Views:** 0  
**Installs:** 0  
**Upvotes:** 0  
**Directory Page:** https://allmcps.com/mcp/run402

## Description
x402 pay-per-call infra for agents: $0.03 image generation, Postgres, auth, storage, functions.

## Claude Desktop Quick Installation
Heuristic fallback — verify the package name and runner against the repository README before running it. Uses `npx` (confidence: low):

```json
"mcpServers": {
  "run402": {
    "command": "npx",
    "args": ["-y","run402"]
  }
}
```

## Documentation & README

<p align="center">
  <img src=".github/logo.svg" width="120" alt="run402 logo">
</p>

<h1 align="center">run402: Postgres, storage & deploys for AI agents</h1>

[![Tests](https://github.com/kychee-com/run402/actions/workflows/test.yml/badge.svg)](https://github.com/kychee-com/run402/actions/workflows/test.yml)
[![CodeQL](https://github.com/kychee-com/run402/actions/workflows/codeql.yml/badge.svg)](https://github.com/kychee-com/run402/actions/workflows/codeql.yml)
[![npm: @run402/sdk](https://img.shields.io/npm/v/@run402/sdk?label=%40run402%2Fsdk)](https://www.npmjs.com/package/@run402/sdk)
[![npm: run402](https://img.shields.io/npm/v/run402?label=run402)](https://www.npmjs.com/package/run402)
[![npm: run402-mcp](https://img.shields.io/npm/v/run402-mcp?label=run402-mcp)](https://www.npmjs.com/package/run402-mcp)
[![npm: @run402/functions](https://img.shields.io/npm/v/@run402/functions?label=%40run402%2Ffunctions)](https://www.npmjs.com/package/@run402/functions)
[![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](./LICENSE)

This is the backend Kychee's open products run on. We needed a layer an agent can drive end to end, with room for whatever each app turns out to need, and nothing off the shelf had all of it, so we built it and opened it the same way we open the apps: this repo holds the agent surfaces (MIT), [`run402-core`](https://github.com/kychee-com/run402-core) holds the full backend (Apache-2.0), and [kysigned](https://github.com/kychee-com/kysigned) is the first product running on it.

One call to [run402](https://run402.com) gives an agent a full Postgres database, REST API, user auth, content-addressed file storage, static site hosting, serverless functions, and image generation, paid with x402 USDC on Base (or Stripe credits). The prototype tier is free on testnet.

**Run402 is agent-first because agents are first-class participants, not because people disappear.** A person or agent acts through its own Run402 principal and authenticator, and its actions remain attributable. Identity answers who acted; memberships, roles, grants, delegates, freshness, and spend policy determine what that principal may do.

An autonomous agent may remain the legitimate owner of the org-of-one it creates. People may join through explicit co-ownership. Agents entering somebody else's organization receive bounded authority instead of borrowing a human account. Different keys. Equal standing. Explicit authority.

This monorepo ships every surface an agent can pick up:

| Surface | Use when… |
|---------|-----------|
| [`@run402/sdk`](./sdk/) | Calling run402 from TypeScript: typed kernel, isomorphic (Node 22 / Deno / Bun / V8 isolates) with a Node entry that auto-loads the local keystore + allowance + x402 fetch |
| [`run402` CLI](./cli/) | Terminal, scripts, CI, agent-controlled shells: JSON in, JSON out, exit code on failure |
| [`run402-mcp`](./src/) | Claude Desktop, Cursor, Cline, Claude Code: core run402 operations as MCP tools |
| [OpenClaw skill](./openclaw/) | OpenClaw agents (no MCP server required) |
| [Run402 for Buzz](./buzz/) | Buzz people and agents: install from run402.com, preflight/link one agent's dedicated identities, deploy a contextual demo, then offer human co-ownership through a normal HTTPS/passkey handoff; Buzz remains unchanged |
| [`@run402/functions`](https://www.npmjs.com/package/@run402/functions) | Imported _inside_ deployed functions (`db(req?)`, `adminDb()`, `auth.user()`, `email`, `ai`, `assets`) and for TypeScript autocomplete in your editor. Source lives in the public [`run402-core`](https://github.com/kychee-com/run402-core) repo under `packages/functions`; run402 Cloud consumes the published npm package when it bundles function zips. |
| [`@run402/astro`](./astro/) | Astro integration for SSR, ISR cache, hosted auth components, and image variants |

These interfaces share a single typed kernel where appropriate: `@run402/sdk`. MCP tools, CLI subcommands, and OpenClaw scripts are thin shims over SDK calls. `@run402/functions` is the in-function helper that runs inside deployed code; the npm package on the registry is the artifact Cloud bundles. `@run402/astro` layers the SDK and functions runtime into Astro's build and SSR flow. Pick whichever interface fits your runtime.

## 30-second start

```bash
npm install -g run402@latest
run402 up --name my-app -y                           # bootstrap allowance/tier/project/link, then deploy manifest
run402 up verify                                     # rerun app HTTP verification without deploying
run402 up --verify                                   # deploy, then wait for gateway/edge coherence
run402 subdomains claim my-app                       # → https://my-app.run402.com
```

That's a real Postgres database + a deployed static site, paid for autonomously with testnet USDC.

Buy from any x402 seller with the same allowance and a default $0.10 ceiling:

```bash
run402 pay https://seller.example/translate --method POST \
  --body '{"text":"hello"}' --max-usd 0.05 \
  --idempotency-key translation:1 --require-receipt
```

The SDK equivalent is
`r.pay.fetch(url, init, { maxUsdMicros, idempotencyKey, requireReceipt })`;
MCP callers use `pay_url` with `require_receipt: true`. All three return the
same `x402-commerce-result.v1` settlement, movement/replay, delivery, offer,
merchant-receipt, signer-relationship, policy, and raw-evidence fields and pass
unpriced URLs through with `payment: null`. Requiring a receipt rejects before
payment when no wallet-rooted offer is eligible. If a promised receipt cannot
be verified after settlement, `PaymentPolicyError` retains the upstream
response and paid result and tells the caller to reconcile—never to pay again.
For a
trusted Run402 `PAYMENT_INTENT_PENDING`, all three surfaces prescribe one
recovery path: wait for `Retry-After`, then repeat the same request with the
same payer and key. Never replace the key. The SDK and MCP can also re-present
an ambiguous proof while their process remains alive; custom/arbitrary sellers
remain ambiguous and require reconciliation.

Prefer `run402 up` when a repo has `run402.deploy.json` or `app.json`. The CLI stays a thin shim over the Node SDK action runner (`r.actions.run(...)` / `r.up(...)`): it validates the manifest first, then recursively performs only the missing prerequisites. Project resolution is `--project`, `.run402/project.json`, manifest `project_id`, approved creation from `--name`, then approved active-project fallback. `--name` is project creation/link metadata only; it is not part of the deploy manifest and never renames an existing project. Use `--check` for local validation and `--plan` for gateway-reviewed intent before applying.

If an app manifest defines `verify.http[]`, `run402 up` verifies those URLs after deploy. Fresh run402 edge sentinel misses are reported as `propagation_pending` rather than permanent failures while the binding is still converging; tune that wait with `--propagation-budget-s` (default 120) or return immediately with `--no-propagation-wait`. `run402 up verify` reruns the same HTTP checks without uploading, deploying, creating projects, or mutating resources.

The CLI checks for newer `run402` releases opportunistically and fail-open. Success stdout stays the command result; stale-version notices are advisory JSON on stderr, or `cli.update_available` NDJSON events in `--json-stream`. `run402 doctor --refresh` is the explicit live npm check and reports the install context plus the safest upgrade command for local, global, or ephemeral installs.

Typed deploy configs use the same commands. Executable configs are trusted local code, so v1 only runs them when passed explicitly:

```bash
run402 up --manifest run402.deploy.ts --check
run402 up --manifest run402.deploy.ts --plan
run402 up --manifest run402.deploy.ts --require-plan plan_...
```

`--check` and `--print-spec` are local-only. `--plan` asks the gateway for a reviewed plan with `plan_id`, `plan_fingerprint`, warnings, diff, and one next action. `--require-plan` reapplies only if the normalized spec and reviewed gateway facts still match.

```ts
import { defineConfig, dir, nodeFunction, sqlFile } from "@run402/sdk/config";

export default defineConfig(({ env }) => ({
  project: env.required("RUN402_PROJECT_ID"),
  database: { migrations: [sqlFile("db/001_init.sql")] },
  site: { replace: dir("dist"), public_paths: { mode: "implicit" } },
  functions: { replace: { api: nodeFunction("dist/functions/api.js") } },
  secrets: { require: ["OPENAI_API_KEY"] },
}));
```

Helpers normalize to the same `ReleaseSpec` as JSON manifests. `dir()` walks deterministically and rejects unsafe files unless explicitly allowed, `sqlFile()` derives the migration id from the filename unless supplied, and `nodeFunction()` currently expects JavaScript output; point TypeScript functions at built `.js` files.

## The patterns

### Paste-and-go assets: content-addressed URLs with SRI

`assets.put()` returns an `AssetRef` whose `scriptTag()` / `linkTag()` / `imgTag()` emitters produce HTML with the URL, the SRI integrity hash, and modern best-practice attributes (`defer`, `loading="lazy"`, `decoding="async"`, `crossorigin`) already wired. The URL is content-addressed (`pr-<public_id>.run402.com/_blob/<key>-<8hex>.<ext>`), served through the v1.33 CDN, and never needs invalidation:

```ts
import { run402 } from "@run402/sdk/node";
const r = run402();
const p = await r.project(projectId);

const logo  = await p.assets.put("logo.png", { bytes: pngBytes });
const app   = await p.assets.put("app.js",   { content: jsSource });
const style = await p.assets.put("app.css",  { content: css });

const html = `
<!doctype html>
<html>
  <head>${style.linkTag()}${app.scriptTag({ type: "module" })}</head>
  <body>${logo.imgTag("Company logo")}</body>
</html>
`;
```

Binary files must enter the SDK as bytes. In Node, use `readFile(path)` without
an encoding; in browsers, use `File.arrayBuffer()`. Never read PNG, WASM,
fonts, audio, video, archives, or other binary formats as UTF-8 and then hash
or re-encode the resulting string: CAS can verify only the bytes it receives.
The SDK rejects string sources for known binary keys/MIME types with
`BINARY_CONTENT_REQUIRES_BYTES` before making a request. Directory helpers
such as `fileSetFromDir`, `dir`, and `assets.uploadDir` are byte-safe by
construction.

`immutable: true` is the default: the SDK computes the SHA-256 client-side, the gateway returns a content-hashed URL, and the browser refuses execution on byte mismatch. No cache-invalidation choreography, no waiting, no integrity-attribute construction.

### Dark-by-default tables + the expose manifest

Tables you create are unreachable via `/rest/v1/*` until you declare them in a manifest. That closes the "agent created a table, forgot to set RLS, data leaked" footgun. The manifest is convergent: applying it twice is a no-op; items removed between applies have their policies, grants, triggers, and views dropped.

```bash
cat > manifest.json <<'EOF'
{
  "$schema": "https://run402.com/schemas/manifest.v1.json",
  "version": "1",
  "tables": [
    { "name": "items",  "expose": true,  "policy": "user_owns_rows",
      "owner_column": "user_id", "force_owner_on_insert": true },
    { "name": "audit",  "expose": false }
  ],
  "views": [
    { "name": "leaderboard", "base": "items", "select": ["user_id", "score"], "expose": true }
  ],
  "rpcs": [
    { "name": "compute_streak", "signature": "(user_id uuid)", "grant_to": ["authenticated"] }
  ]
}
EOF

run402 projects validate-expose <project_id> --file manifest.json
run402 projects apply-expose    <project_id> --file manifest.json
run402 projects get-expose   <project_id>
```

Built-in policies: `user_owns_rows` (rows where `owner_column = auth.uid()`; with `force_owner_on_insert: true` a BEFORE INSERT trigger sets it), `public_read_authenticated_write` (anyone reads, any authenticated user writes), `public_read_write_UNRESTRICTED` (fully open; requires `i_understand_this_is_unrestricted: true`), and `custom` (escape hatch: your own `CREATE POLICY` SQL).

Use `run402 projects validate-expose` or the MCP `validate_manifest` tool for a non-mutating feedback loop before applying. Optional migration SQL is used only to check manifest references; it is not executed as a PostgreSQL dry run, and this does not validate deploy manifests.

**Auth-as-SDLC:** put the same JSON under `database.expose` in your v2 `ReleaseSpec`. The gateway validates it against your migration SQL during deploy and rejects mismatches with a structured `errors` array listing every violation.

### Slick deploys: `deployDir` + plan/commit + progress

`deployDir` walks a local directory, hashes every file client-side, asks the gateway _which_ bytes it doesn't already have, and PUTs only those. Re-deploying an unchanged tree returns immediately with `bytes_uploaded: 0`.

```ts
import { run402 } from "@run402/sdk/node";

const r = run402();
const { url, bytes_uploaded, bytes_total } = await r.sites.deployDir({
  project: projectId,
  dir: "./dist",
  onEvent: (e) => process.stderr.write(JSON.stringify(e) + "\n"),
});
```

Progress events stream over `onEvent` (or stderr from the CLI) as unified
`DeployEvent` JSON objects from the v2 deploy primitive.

CLI:

```bash
run402 sites deploy-dir ./dist --project prj_… > result.json 2> events.log
```

### Same-origin web routes: static site + function ingress

Apply-v1 routes and static public paths are release resources: they activate atomically with the site, functions, migrations, secrets, and subdomains in the same `deploy apply`. Release static asset paths such as `events.html` are distinct from browser-visible public static paths such as `/events`. Use `site.public_paths` for ordinary clean static URLs; keep routes for function ingress and exact, method-aware static aliases.

```json
{
  "project_id": "prj_...",
  "site": {
    "replace": {
      "index.html": { "data": "<!doctype html><main id='app'></main><script>fetch('/api/hello')</script>" },
      "events.html": { "data": "<!doctype html><h1>Events</h1>" }
    },
    "public_paths": {
      "mode": "explicit",
      "replace": {
        "/events": { "asset": "events.html", "cache_class": "html" }
      }
    }
  },
  "functions": {
    "replace": {
      "api": {
        "runtime": "node22",
        "source": {
          "data": "export default async function handler(req) { const url = new URL(req.url); return Response.json({ ok: true, path: url.pathname }); }"
        }
      },
      "login": {
        "runtime": "node22",
        "source": { "data": "export default async function handler(req) { return Response.json({ ok: true }); }" }
      }
    }
  },
  "routes": {
    "replace": [
      { "pattern": "/api/*", "methods": ["GET", "POST", "OPTIONS"], "target": { "type": "function", "name": "api" } },
      { "pattern": "/login", "methods": ["POST"], "target": { "type": "function", "name": "login" } }
    ]
  }
}
```

`site.public_paths.mode: "explicit"` means only the complete `public_paths.replace` table is directly reachable as static URLs. In the example, `/events` serves the release asset `events.html`, while `/events.html` is not public unless separately declared. `mode: "implicit"` restores filename-derived public reachability and can widen access, so review gateway warnings before confirming it.

Omit `routes` or pass `routes: null` to carry forward base routes. Use `routes: { "replace": [] }` to clear the route table. Route entries are an ordered `replace` list, not a path-keyed map. Function targets use `{ "type": "function", "name": "<materialized function name>" }`. Static route targets use exact patterns only, methods `["GET"]` or `["GET","HEAD"]`, and `{ "pattern": "/events", "methods": ["GET","HEAD"], "target": { "type": "static", "file": "events.html" } }` where `file` is a release static asset path, not a public path, URL, CAS hash, rewrite, or redirect. Use static route targets for method-aware aliases such as static `GET /login` plus function `POST /login`; in explicit public path mode the backing asset can stay private by filename. Direct `/functions/v1/:name` calls remain API-key protected; browser-routed paths are public same-origin ingress.

Function routes can charge a fixed tenant x402 price before the handler runs by adding `pricing: { "mode": "always", "amount_usd_micros": 250000, "pay_to": "org_default_payout" }` to the route entry. `250000` is $0.25 per matching action. The portable ReleaseSpec contract also accepts `receipt: "on_fulfillment"` on a priced function route; a compatible host then requires the function to return `payment.fulfilled(response)` before it authors a receipt. Run402-hosted advertising remains gated off until the standard delegated-signer carrier is available—receipt intent never silently downgrades. Omit `networks` for production mainnet only; include `"testnet"` explicitly for testnet acceptance. Static aliases cannot be priced, direct function invocation is not monetized, and service/admin keys do not bypass a priced browser route. The owning org must have a resolvable payout wallet: set it with `r.org(orgId).setPayoutWallet({ walletAddress })`, `run402 org payout-wallet <org_id> <wallet_address>`, or MCP `set_org_payout_wallet`. Conditional credit systems should expose one fixed-price route such as `POST /api/credits`, then keep the rest of the app behind unpriced routes and app-local authorization.

Matching is exact or final-prefix-wildcard only. `/admin` and `/admin/` are exact trailing-slash equivalents; `/admin/*` matches children but not `/admin`, `/admin/`, `/admin.css`, or `/administrator`, so deploy both `/admin` and `/admin/*` for a routed section root. Query strings are ignored for matching and preserved in the handler's full public `req.url`. Exact routes beat prefix routes; longest prefix wins; method-compatible dynamic routes beat static assets. A `POST /login` route can coexist with static `GET /login` HTML. Unsafe method mismatch returns `405`, and matched dynamic route failures fail closed instead of falling back to static files.

Routed functions use the Node 22 Fetch Request -> Response contract: `export default async function handler(req) { ... }`. `req.method` is the browser method, and `req.url` is the full public URL on managed subdomains, deployment hosts, and verified custom domains. Derive OAuth callbacks from it, for example `new URL("/admin/oauth/google/callback", new URL(req.url).origin)`. Append multiple cookies with `headers.append("Set-Cookie", value)`; redirects, cookies, and query strings are preserved. On priced routes, import `getRoutedPaymentContext` from `@run402/functions`, read `const paymentContext = getRoutedPaymentContext(req)`, and key app-side idempotency by `paymentContext.paymentId`. For a receipt-enabled route, return `payment.fulfilled(response)` only after the response represents completed delivery; the helper fails closed outside a settled, current, receipt-enabled routed invocation. The context helper reads gateway-confirmed `x-run402-payment-*` headers and returns `null` for unpriced or direct calls. The raw `run402.routed_http.v1` envelope is internal; do not write route handlers against it.

**Recipe: static home page + SPA shell.** A SPA site ships `index.html` as the shell serving every unmatched route (match `spa_fallback`), so by default `GET /` serves the shell too. To serve a real static home page at `/` while keeping the shell for app routes, ship `home.html` at the site root alongside `index.html` and add an exact root static route alias: `"routes": { "replace": [ { "pattern": "/", "target": { "type": "static", "file": "home.html" } } ] }`. Route matching runs before all static resolution (including the implicit `/` -> `index.html` root mapping), and SPA-fallback derivation is independent of the route table, so `GET /` serves `home.html` (`route_static_alias`), unmatched app routes such as `/dashboard` still serve the shell (`spa_fallback`), and named static pages keep serving unchanged (`static_exact`). Expect two non-blocking plan lints: `STATIC_ALIAS_SHADOWS_STATIC_PATH` (warn: the alias overrides what `/` would otherwise serve; accurate and expected here) and `STATIC_ALIAS_DUPLICATE_CANONICAL_URL` (info: `/home.html` stays directly reachable in implicit public-path mode; add `<link rel="canonical">` to `home.html` if duplicate-content SEO matters). Omitting `routes` on later deploys carries the alias forward; `routes.replace` is total, so a pipeline that sends it must include the alias every time. Verify with `run402 deploy resolve --url https://<your-site>/ --method GET` or `deploy_diagnose_url` and confirm `match: "route_static_alias"` with `target_file: "home.html"`.

Avoid routing every static file, broad method lists by default, wildcard static route targets, leading-slash static files, directory shorthand, and one-static-route-target-per-page tables that exhaust route limits. Also watch wildcard function routes that shadow direct public static paths. Warning codes to handle include `STATIC_ALIAS_SHADOWS_STATIC_PATH`, `STATIC_ALIAS_RELATIVE_ASSET_RISK`, `STATIC_ALIAS_DUPLICATE_CANONICAL_URL`, `STATIC_ALIAS_EXTENSIONLESS_NON_HTML`, and `STATIC_ALIAS_TABLE_NEAR_LIMIT`; inspect active routes, `static_public_paths`, and resolve diagnostics to distinguish the route pattern from the backing `asset_path`.

Diagnose public URLs with the URL-first CLI or MCP/SDK equivalents:

```bash
run402 deploy diagnose --project prj_123 https://example.com/events --method GET
run402 deploy resolve --project prj_123 --url https://example.com/events?utm=x#hero --method GET
run402 deploy resolve --project prj_123 --host example.com --path /events --method GET
```

`deploy_diagnose_url` and `r.project(id).apply.resolve({ url, method: "GET" })` return `would_serve`, `diagnostic_status`, `match`, normalized request data, warnings, full resolution JSON, `edge_propagation`, and next steps. When returned, `asset_path`, `reachability_authority`, and `direct` explain which release asset backs the public URL and whether reachability came from implicit file-path mode, explicit `site.public_paths`, or a route-only static alias. Stable-host diagnostics may also include `authorization_result`, `cas_object` (`sha256`, `exists`, `expected_size`, `actual_size`), hostname-specific `response_variant`, route/static fields such as `allow`, `route_pattern`, `target_type`, `target_name`, and `target_file`, and `edge_propagation` (`settled`, `propagating`, or `sync_pending`). Known `match` literals are `host_missing`, `manifest_missing`, `active_release_missing`, `unsupported_manifest_version`, `path_error`, `none`, `static_exact`, `static_index`, `spa_fallback`, `spa_fallback_missing`, `route_function`, `route_static_alias`, and `route_method_miss`; preserve unknown future strings. Known `authorization_result` values include `authorized`, `not_public`, `not_applicable`, `manifest_missing`, `target_missing`, `active_release_missing`, `unsupported_manifest_version`, `path_error`, `missing_cas_object`, `unfinalized_or_deleting_cas_object`, `size_mismatch`, and `unauthorized_cas_object`. Known `fallback_state` values include `active_release_missing`, `unsupported_manifest_version`, and `negative_cache_hit`; preserve unknown future strings. `result` is the diagnostic body status, not the HTTP status of the SDK call, so host misses can still be successful CLI/MCP/SDK calls with `would_serve: false`. Do not treat resolve/diagnose as a fetch, cache purge, or cache-policy oracle; route method misses should inspect `allow`, CAS authorization/health failures should inspect or redeploy the affected static asset, and fresh host misses should inspect `edge_propagation` or rerun `run402 up verify`. Branch on structured JSON fields such as `cache_class` and preserve unknown cache classes.

Release observability exposes stable asset identity and public reachability. Inventories include `release_generation`, `static_manifest_sha256`, nullable `static_manifest_metadata` (`file_count`, `total_bytes`, `cache_classes`, `cache_class_sources`, `spa_fallback`), and `static_public_paths[]` when returned. `site.paths` lists release static assets; `static_public_paths[]` lists browser-visible public paths with `public_path`, `asset_path`, `reachability_authority`, `direct`, cache class, and content type. Plan and release diffs expose `static_assets` counters: unchanged/changed/added/removed, `newly_uploaded_cas_bytes`, `reused_cas_bytes`, `deployment_copy_bytes_eliminated`, `legacy_immutable_warnings`, `previous_immutable_failures`, and `cas_authorization_failures`.

Runtime route failure codes to branch on: `ROUTE_MANIFEST_LOAD_FAILED` (manifest/propagation), `ROUTED_INVOKE_WORKER_SECRET_MISSING` (custom-domain Worker secret), `ROUTED_INVOKE_AUTH_FAILED` (internal invoke signature), `ROUTED_ROUTE_STALE` (selected route failed release revalidation), `ROUTE_METHOD_NOT_ALLOWED` (method mismatch), `PAYOUT_WALLET_REQUIRED` / `PAYOUT_WALLET_AMBIGUOUS` / `PAYOUT_WALLET_UNRESOLVED` (priced-route payout setup), `PAYMENT_PROOF_MISMATCH` (stale or wrong x402 proof), and `ROUTED_RESPONSE_TOO_LARGE` (body over 6 MiB).

### GitHub Actions OIDC deploys: link once, deploy with the same CLI

For repo-driven deploys, run402 does not need service keys or allowance files in GitHub secrets. Run a local link command once:

```bash
run402 ci link github --project prj_... --manifest run402.deploy.json
# Optional route authority for CI route declarations:
run402 ci link github --project prj_... --manifest run402.deploy.json --route-scope /admin --route-scope /api/*
```

That creates a deploy-scoped `/ci/v1/*` binding and writes a workflow that grants `id-token: write`, checks out the repo, and runs the existing deploy primitive:

```yaml
permissions:
  contents: read
  id-token: write

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Deploy to run402
        run: npx --yes run402@3.7.5 deploy apply --manifest 'run402.deploy.json' --project 'prj_...'
```

CI deploys are intentionally narrow: `site`, `functions`, `database`, absent/current `base`, and route declarations only when the binding has covering `--route-scope` patterns. Without route scopes, CI cannot ship `routes`. Keep secrets, domains, subdomains, checks, non-current base, and broader trust changes in a local allowance-backed deploy. If the gateway returns `CI_ROUTE_SCOPE_DENIED`, re-link with exact scopes like `/admin` or final-wildcard scopes like `/api/*`, or deploy locally. Manage bindings with `run402 ci list` and `run402 ci revoke`.

### In-function helpers: caller-context vs BYPASSRLS

Inside a deployed function, import from `@run402/functions`. Two distinct DB clients keep RLS clean:

```ts
import { db, adminDb, auth, email, ai } from "@run402/functions";

export default async (req: Request) => {
  const user = await auth.requireUser();

  // Caller-context: db() mints a 60s actor JWT so run402.current_user_id() resolves in RLS.
  // No .eq("user_id", user.id) needed: RLS already binds the visitor's rows; the redundant
  // filter is a deploy-fail (R402_AUTH_REDUNDANT_USER_FILTER) under @run402/functions v3.0+.
  const mine = await db().from("items").select("*");

  // BYPASSRLS: for platform-authored writes (audit logs, cron cleanup, webhook handlers).
  await adminDb().from("audit").insert({ event: "items_read", user_id: user.id });

  // Send mail from the configured default outbound mailbox.
  if (mine.length === 0) {
    await email.send({ to: user.email, subject: "Welcome", html: "<h1>hi</h1>" });
  }

  return Response.json(mine);
};
```

`adminDb().sql(query, params?)` runs raw parameterized SQL and always bypasses RLS. It returns a flat `Promise<Record<string, unknown>[]>` (just the rows, no envelope):

```ts
import { adminDb, auth } from "@run402/functions";

export default async (req: Request) => {
  const user = await auth.requireUser();

  const rows = await adminDb().sql(
    "SELECT count(*)::int AS n FROM items WHERE user_id = $1",
    [user.id],
  );
  const n = (rows[0]?.n as number | undefined) ?? 0;
  return Response.json({ count: n });
};
```

`@run402/functions` is auto-bundled into deployed code; install it in your editor for full TypeScript autocomplete (also works at build time for static-site generation with `RUN402_SERVICE_KEY` + `RUN402_PROJECT_ID` set).

`ai.generateImage({ prompt, aspect? })` is available inside deployed functions for live app flows such as generated avatars or OG images. It calls the project runtime image endpoint with `RUN402_SERVICE_KEY`, so deployed functions do not need allowance wallets or x402 signing code. Aspects are `square`, `landscape`, and `portrait`; the result is `{ image, content_type, aspect }` with base64 image bytes. Runtime image generation is billed, rate-limited, and spend-capped against the project organization; public routed functions should authenticate/rate-limit their users before calling it.

`assets.put(key, source, opts?)` uploads bytes from inside a deployed function through the same CAS-backed apply substrate as deploy-time assets. It uses `RUN402_SERVICE_KEY`, accepts a string, `Uint8Array`, or `{ content | bytes }`, and returns an SDK-compatible `AssetRef` with mutable and immutable URLs.

**Calling from outside a function entirely** (raw `curl`/`fetch` from CI scripts, bash bootstrappers, non-TS runtimes): service-key writes go to `/admin/v1/rest/<table>`, not `/rest/v1/*`. The gateway 403s service-role tokens on `/rest/v1/*` so a leaked key can't silently bypass RLS, which means `curl ... > /dev/null` against the wrong path looks like success but writes nothing. SQL-shaped admin work uses `POST /projects/v1/admin/:id/sql` (or `run402 projects sql`).

```bash
curl -X POST https://api.run402.com/admin/v1/rest/audit \
  -H "Authorization: Bearer $RUN402_SERVICE_KEY" \
  -H "Content-Type: application/json" \
  -d '{"event":"seed","ts":"2026-04-30"}'
```

## SDK: `@run402/sdk`

```bash
npm install @run402/sdk
```

Two entry points:

- **`@run402/sdk`**: isomorphic. Bring your own `CredentialsProvider` (a session-token shim, a remote vault, anything that resolves project keys + auth headers). Works in Node 22, Deno, Bun, V8 isolates.
- **`@run402/sdk/node`**: Node-only convenience. Reads local profile state plus the project-key credential cache (`credentials/project-keys.v1.json`) and signs x402 payments from one deterministic source: an explicit opaque `paymentSigner`, explicit `allowancePath`, the supplied provider's `readAllowance()`, or the default active-profile allowance. Auth and payer may intentionally differ; a selected payment source never falls back to an ambient wallet. `r.paymentPayer()` reports only safe public payer/source provenance. Also exposes `sites.deployDir(...)`, `fileSetFromDir(...)`, typed deploy-manifest helpers (`loadDeployManifest`, `normalizeDeployManifest`), and `resolveRun402TargetProfile()` for app build scripts that need the same Core/Cloud target the CLI uses.

```ts
import { run402 } from "@run402/sdk/node";

const r = run402();
const project = await r.projects.provision({ tier: "prototype" });
const p = await r.project(project.project_id);
await p.assets.put("hello.txt", { content: "hi" });
```

The SDK is organised into focused namespaces: `actions` (Node recursive action runner), `pay` (bounded arbitrary-URL x402 buyer), `projects`, `snapshots`, `branches`, `archives`, `assets`, `cache`, `ci`, `sites`, `functions`, `jobs`, `secrets`, `subdomains`, `domains`, `email` (+ `webhooks`), `senderDomain`, `auth`, `apps`, `tier`, `billing`, `contracts`, `ai`, `allowance`, `service`, `admin`, `operator` (the human/email operator session: browser-delegated `login` + `overview` across every wallet that verified your email), `wallets` (signed server-side wallet label), `orgs` (org-owned control plane + `r.org(id)` sub-client), `grants` (per-project capability grants), and `identityLinks` (public, protocol-discriminated human/agent Nostr attribution), plus the `r.project(id).apply` hero for atomic mixed writes (release slices + assets slice via `/apply/v1/*`). Every operation throws a typed `Run402Error` subclass on failure: `PaymentRequired`, `PaymentBuyerError`, `ProjectNotFound`, `Unauthorized`, `ApiError`, `NetworkError`, `LocalError`, `Run402DeployError`. `apply()` automatically re-plans safe current-base `BASE_RELEASE_CONFLICT` races and emits `apply.retry` progress events. See [`sdk/README.md`](./sdk/README.md).

## Buzz/Nostr identity links

Humans and agents can publicly attribute separately held Buzz/Nostr identities to their Run402 principal. Agent links use the EOA-plus-kind-1 protocol; human links use a normal browser, fresh passkey, and released Buzz consent ceremony at <https://console.run402.com/identity-links/connect>. Both produce the same public `idlnk_…` resource shape with a discriminating `proof_protocol`. One principal may have several active Nostr subjects, while one active Nostr subject belongs to only one principal. This is attribution only: Nostr identities never authenticate, authorize, pay, deploy, or receive transfers. Run402 never accepts or derives from an `nsec`, Nostr private key, mnemonic, seed, passkey, session credential, or derivation path.

The human-facing install is a Buzz message—no terminal required:

```text
Please install the run402.com skill.
```

That is the entire human instruction. In a managed Buzz context, first-party discovery routes it to `run402-buzz`; the agent reads the apex install router and installs the self-contained skill into its workspace (normally the user-home `.buzz` directory). The request means install and connect: after verifying the inert files, the agent loads the installed skill directly and continues through preflight, setup, and identity linking in the same turn. It does not stop at “available next turn” or ask a second setup question. For a Codex runtime, prefer supplying the working directory and environment separately to the agent's command runner:

```text
working_directory: <user-home>/.buzz
environment: { "DO_NOT_TRACK": "1" }
command: npx --yes skills@latest add https://run402.com -s run402-buzz -a codex -y
```

Shell-only POSIX environments use:

```sh
cd "$HOME/.buzz"
DO_NOT_TRACK=1 npx --yes skills@latest add https://run402.com -s run402-buzz -a codex -y
```

Windows PowerShell uses:

```powershell
Set-Location (Join-Path $HOME '.buzz')
$env:DO_NOT_TRACK = '1'
npx --yes skills@latest add https://run402.com -s run402-buzz -a codex -y
```

Claude Code uses `-a claude-code`, Goose uses `-a goose`, a confirmed `.agents/skills` consumer may use `-a universal`, and Claude Code plus Codex uses `-a claude-code codex`. `universal` is the shared path, not all runtimes; do not use the invalid explicit target `-a claude`. The skill bytes come from immutable digest-verified artifacts at `run402.com`; first-run `npx` can still require npm. GitHub is the one availability-only fallback, while any integrity failure stops before setup. Success reports the observed first-party digest and exact managed-workspace path; a GitHub source or global runtime path is never mislabeled first-party.

The file installation stage is inert. Continuing onboarding publishes a durable public kind-1 Nostr event and durable Run402 proof connecting the two public identities; revocation does not erase their history, and a Buzz-managed event may also expose its owner's public NIP-OA attestation. The agent initializes only if needed, creates or reuses the link, independently verifies it, and immediately offers one context-relevant quick test or demo with `Deployment: none` retained in the expanded receipt. On Windows the setup helper runs npm's and Run402's JavaScript entrypoints through the exact managed Node runtime with `shell: false`, avoiding `.cmd` process-boundary failures. It waits for explicit approval before building or deploying. After independently verifying the live app, it creates an inert durable offer and posts a normal HTTPS “Become an owner” handoff. The browser owns human login/passkey and the existing Buzz six-digit consent callback; no human terminal command or Buzz change is required.

See the [`buzz/` guide](./buzz/README.md) for prerequisites, the no-secret signer model, released-client fixtures, migration guidance, and the full workflow, or inspect the exact [`run402-buzz` listing on skills.sh](https://skills.sh/kychee-com/run402/run402-buzz). The low-level CLI commands remain available for debugging, but they are not a competing onboarding path.

The community control plane keeps four concepts separate: installing the skill is inert shared capability; installing a community associates a Buzz relay community with a Run402 organization after dual consent; human adoption records a terminal consent receipt, creates the human's public Buzz identity link, and adds an ordinary owner membership without demoting the founder agent; agent enrollment gives each later agent principal only bounded, expiring grants to named existing projects. The completed receipt, public attribution, and membership remain independent: revoking the link does not remove org authority, and removing the membership does not revoke the link or rewrite the receipt. Buzz itself remains unchanged: approval uses already-shipped browser-fragment/kind-1 behavior plus released NIP-11/NIP-43 evidence, while Run402 owns offers, organizations, descriptor discovery, and lifecycle. `run402 buzz status` capability-detects older gateways; MCP only renders exact HTTPS/CLI handoffs. See the [Fizz/Honey workflow](./buzz/references/community-control-plane.md).

**Astro SSR + ISR cache (v1.52+).** For Astro apps, use `@run402/astro` 1.0+: `export default run402();` in `astro.config.mjs` returns an `AstroUserConfig` composing the SSR adapter (Lambda + SnapStart + ISR cache + AsyncLocalStorage request-context), image integration, and build-time detectors. Functions opt into the SSR class via `FunctionSpec.class: "ssr"` in `ReleaseSpec`; the gateway provisions SnapStart and caches HTML responses keyed by `(host, path, search, method, locale, release_id)`. Cache is bypass-by-default (no-store unless `Cache-Control` explicitly allows it AND no `Set-Cookie` AND no auth-taint flag from `auth.*` helpers / payment primitives). Invalidate from in-function code or out-of-band: `r.cache.invalidate(url)` / `r.cache.invalidatePrefix({ host, prefix })` / `r.cache.invalidateAll({ host })` (SDK), `run402 cache invalidate <url>` (CLI). Inspect cached state with `r.cache.inspect(url)` / `run402 cache inspect <url>`. Agent DX helpers also in the CLI: `run402 doctor` (5 health checks), `run402 dev` (Astro dev with `.env.local`), `run402 logs --request-id req_...` (correlate across functions). Full reference at [`astro/README.md`](./astro/README.md) and [`cli/llms-cli.txt`](./cli/llms-cli.txt) (R402_* SSR Runtime Error Codes section).

## CLI: `run402`

```bash
npm install -g run402@latest
```

Every subcommand prints JSON to stdout, JSON errors to stderr, exits 0 on success and 1 on failure: designed for an agent shell, not a human. Full reference: [`cli/llms-cli.txt`](./cli/llms-cli.txt) (also at <https://docs.run402.com/llms-cli.txt>).

```bash
run402 up --name my-app -y                # recursive SDK action runner: init/tier/project/link/deploy
run402 up verify                          # rerun app HTTP verification without a deploy
run402 init                              # one-shot allowance + faucet + tier check
run402 pay https://seller.example/resource --max-usd 0.05 --require-receipt
run402 status                            # organization snapshot (wallet, rail, balances, tier, projects)
run402 projects provision --name my-app
run402 projects sql <id> "CREATE TABLE …"
run402 projects validate-expose <id> --file manifest.json
run402 projects apply-expose <id> --file manifest.json
run402 sites deploy-dir ./dist
run402 deploy verify op_... --project <id> --wait  # confirm gateway/edge release coherence
run402 deploy release active --project <id>  # inspect current-live release inventory
run402 deploy diagnose --project <id> https://example.com/events --method GET
run402 apply --manifest app.json --rehearse --json
run402 snapshots list prj_...
run402 branches create prj_... --ttl-days 7 --json
run402 functions deploy <id> <name> --file fn.ts
run402 functions runs create <id> <name> --event-type reminder.send --idempotency-key reminder:123 --delay 10m
run402 ci link github --project <id>       # GitHub Actions OIDC deploy binding (--route-scope for CI routes)
run402 assets put ./asset.png --immutable
run402 assets diagnose <url>             # inspect live CDN state for a public URL
run402 cdn wait-fresh <url> --sha <hex>  # poll until a mutable URL serves the new SHA
```

`up` is the only compound CLI command: it calls the SDK action runner, emits `steps[]`, and writes `.run402/project.json` when it needs to remember the workspace project. Against run402 Core it skips Cloud allowance/tier prerequisites and fails closed if no Core project is selected.

For database-bearing deploys, rehearse before commit. `run402 apply --manifest app.json --rehearse --json` plans, uploads missing CAS bytes, creates a contained branch, runs migrations/checks there, and exits nonzero on a failed rehearsal. If you already have a persisted plan id, use `run402 deploy rehearse <plan_id> --project <id> --json`. Manual restore points live under `run402 snapshots create|list|get|restore|delete`; restore is a two-step plan/confirm flow. Branch projects live under `run402 branches create|list|renew|delete`, default to a 7-day TTL, use sandboxed email by default, and are marked noindex.

Portable archives export the supported run402 Core runtime slice of a Cloud project for local Core import. This is the no-lock-in trust path, separate from allowance/spend-cap financial-risk controls.

```bash
run402 cloud archives create <project_id> --scope portable-runtime-v1 --auth stubs --consistency pause-writes --wait --output ./project.r402ar --json
run402 archives verify ./project.r402ar --json
run402 core projects import ./project.r402ar --name imported-project --env-file ./required.env --json
run402 projects export <project_id> --output ./project.r402ar --json
run402 core projects apply ./project.r402ar --name imported-project --env-file ./required.env --json
```

`projects export` is an alias for the Cloud archive export flow; `core projects apply` is an alias for Core archive import. Archive v1 excludes secret values, auth credentials, logs, billing/allowance state, Cloud operations metadata, Cloud import, and existing-project merge import. Verify is local/offline and checks integrity plus compatibility; archives remain untrusted input until Core import verifies and stages them.

The active project is sticky: `run402 projects use <id>` server-validates `<id>` and stores it as the default for subsequent `<id>`-taking subcommands, so most commands work without it. Local key material is managed separately under `run402 credentials project-keys ...`; that cache is never project inventory.

## MCP server: `run402-mcp`

```bash
npx -y run402-mcp                        # standalone test
```

### Buying only? Load 6 tools instead of 198

The full server registers **198 tools (~43,000 tokens)** before you do anything. If your agent only wants to *buy* — generate an image for $0.03 and nothing else — that is a fifth to a third of a context window spent on 192 tools it will never call.

```bash
RUN402_MCP_PROFILE=buyer npx -y run402-mcp
```

| profile | tools | approx. tokens |
|---|---:|---:|
| *(unset — default)* | 198 | ~43,200 |
| `buyer` | **7** | **~740** |

The seven: `generate_image` · `init` · `check_balance` · `allowance_status` · `allowance_export` · `request_faucet` · `redeem_voucher` — enough to bootstrap a wallet, fund it (from the faucet or a promo code), check it, and buy. **Local, so it can actually pay:** an x402 payment needs a signing key, so a wallet-less remote server cannot make one.

Default is unchanged when the variable is unset. An unknown profile name **exits 1** with the known-profile list rather than silently serving the full surface or nothing.

### Remote endpoint (no install)

A hosted streamable-HTTP MCP server runs at **`https://mcp.run402.com/mcp`** with free discovery tools only: `run402_quickstart`, `x402_price_check` (decode any URL's x402 challenge, unpaid), and `experiment_scoreboard`. It never handles funds — paid capabilities (image generation, deploys, payments) require the local server below, which holds *your* wallet. Registry entry `com.run402/mcp` lists both (`packages[]` npm + `remotes[]`). The remote itself runs as a run402 function — the platform hosting its own MCP server.

Stdio MCP transports must keep stdout reserved for JSON-RPC. Use the package bin (`npx -y run402-mcp`) or `node dist/index.js` from a built checkout. If a host insists on `npm start`, set `npm_config_loglevel=silent`; npm's lifecycle banner is stdout and otherwise appears as non-JSON prelude. The repo `.npmrc` and Docker image set this for source/container hosts.

### Claude Desktop

Add to `~/Library/Application Support/Claude/claude_desktop_config.json`:

```json
{
  "mcpServers": {
    "run402": { "command": "npx", "args": ["-y", "run402-mcp"] }
  }
}
```

### Cursor

Add to `.cursor/mcp.json`:

```json
{
  "mcpServers": {
    "run402": { "command": "npx", "args": ["-y", "run402-mcp"] }
  }
}
```

### Cline

Add to your Cline MCP settings:

```json
{
  "mcpServers": {
    "run402": { "command": "npx", "args": ["-y", "run402-mcp"] }
  }
}
```

### Claude Code

```bash
claude mcp add run402 -- npx -y run402-mcp
```

## OpenClaw skill

```bash
cp -r openclaw ~/.openclaw/skills/run402
cd ~/.openclaw/skills/run402/scripts && npm install
```

Each script re-exports from `cli/lib/*.mjs`: the OpenClaw command surface is identical to the CLI command surface by construction. See [`openclaw/README.md`](./openclaw/README.md).

## MCP tools

The full MCP surface: every tool is a thin shim over an SDK call.

### Database

| Tool | Description |
|------|-------------|
| `provision_postgres_project` | Provision a new database. Auto-handles x402 payment. |
| `run_sql` | Execute SQL (DDL or queries). Returns a markdown table. |
| `rest_query` | Query/mutate via PostgREST. |
| `apply_expose` | Apply the declarative authorization manifest (tables, views, RPCs). Convergent: drops items removed between applies. |
| `validate_manifest` | Validate the auth/expose manifest without applying it. Accepts manifest object/string, optional `migration_sql`, optional `project_id`. |
| `get_expose` | Return the current manifest. `source` is either `applied` (from the tracking table) or `introspected` (regenerated from live DB state). |
| `get_schema` | Introspect tables, columns, types, constraints, RLS policies. |
| `get_usage` | Per-project usage report (API calls, storage, lease expiry). |
| `promote_user` / `demote_user` | Manage `project_admin` role on a project user. |
| `delete_project` | Cascade purge: schema, Lambdas, S3 site files, deployments, secrets, published versions. Irreversible. |

### Asset storage (content-addressed CDN)

| Tool | Description |
|------|-------------|
| `assets_put` | Upload an asset (any size, up to 5 TiB) via direct-to-S3 presigned URLs. Returns an `AssetRef` with `scriptTag()` / `linkTag()` / `imgTag()` emitters. |
| `assets_get` | Download an asset to a local file. |
| `assets_ls` | Keyset-paginated list with prefix filter. |
| `assets_rm` | Delete an asset. |
| `assets_sign` | Time-boxed presigned GET URL for a private asset. |
| `diagnose_public_url` | Live CDN state for a public URL: expected vs observed SHA, cache headers, invalidation status. |
| `wait_for_cdn_freshness` | Poll a mutable URL until it serves the expected SHA-256. |

### Sites & subdomains

| Tool | Description |
|------|-------------|
| `deploy_site` | Deploy a static site from inline file bytes. |
| `deploy_site_dir` | Deploy a static site from a local directory. Routes through the unified apply primitive (CAS-backed); only uploads bytes the gateway doesn't have. |
| `claim_subdomain` | Claim `<name>.run402.com` (idempotent; reassigns to latest deployment on subsequent deploys). |
| `list_subdomains` / `delete_subdomain` | Manage subdomains. |
| `domains_ensure` / `domains_get` / `domains_list` / `domains_check` | Manage project-scoped web/email ProjectDomain desired state and health checks. |
| `domains_apply` / `domains_repair` / `domains_test_receive` / `domains_activate` / `domains_disconnect` | Apply safe provider actions, repair run402-owned routing, verify inbound receive, activate mailbox addresses, or disconnect a domain. |
| `deploy` / `deploy_resume` / `deploy_rehearse` / `deploy_list` / `deploy_events` / `deploy_verify_edge` | Apply, resume, rehearse persisted plans on contained branches, list, inspect deploy operations, and verify gateway/edge coherence. |
| `deploy_release_get` / `deploy_release_active` / `deploy_release_diff` | Inspect release inventory and release-to-release diffs without starting a new deploy mutation. |
| `deploy_diagnose_url` | URL-first deploy resolver diagnostics. Params: `project_id`, either `url` or `host`/`path`, optional `method`; returns `would_serve`, `diagnostic_status`, `match`, warnings, next steps, and fenced JSON. |

### Snapshots & branches

| Tool | Description |
|------|-------------|
| `create_project_snapshot` / `list_project_snapshots` / `get_project_snapshot` | Create and inspect manual project restore points. Snapshot artifacts are internal and are never downloadable as portable archives. |
| `restore_project_snapshot` | Two-step restore: first returns a restore plan plus confirm token; second call with `confirm` flips the project to the materialized snapshot and reports next actions. |
| `delete_project_snapshot` | Delete a manual snapshot and release its CAS references. |
| `create_project_branch` / `list_project_branches` / `renew_project_branch` / `delete_project_branch` | Create contained, expiring data branches from a snapshot or live project, extend their TTL, or clean them up. |

### CI/OIDC bindings

| Tool | Description |
|------|-------------|
| `ci_create_binding` | Create a GitHub Actions CI deploy binding from a locally signed delegation. Optional `route_scopes` delegate exact paths like `/admin` or final wildcards like `/api/*`; omitted means no CI route authority. |
| `ci_list_bindings` / `ci_get_binding` / `ci_revoke_binding` | Inspect and revoke CI bindings, including returned `route_scopes`. |

### Functions & secrets

| Tool | Description |
|------|-------------|
| `deploy_function` | Deploy a Node 22 serverless function; use ReleaseSpec `triggers[]` for schedule or email event durable runs. |
| `invoke_function` | Invoke a deployed function over the direct API-key-protected path. Paid calls require `idempotency_key` and may return a pollable `run_id`; set `wait` to replay the retained result. |
| `get_function_logs` | Recent logs (CloudWatch), filterable by `since` and routed `request_id`. |
| `update_function` | Update timeout / memory without redeploying code; use ReleaseSpec `triggers[]` for new schedule/email triggers. |
| `list_functions` / `delete_function` | List / remove functions. |
| `create_function_run` / `list_function_runs` / `get_function_run` | Durable function requests with idempotency, delay/run_at, retry, and polling. |
| `get_function_run_logs` / `cancel_function_run` / `redrive_function_run` | Inspect, stop, and redrive durable function runs. |
| `set_secret` / `list_secrets` / `delete_secret` | Manage `process.env` secrets injected into all functions. Values are write-only; list returns keys and timestamps only. |
| `jobs_submit` / `jobs_get` / `jobs_logs` / `jobs_cancel` / `jobs_purge` | Submit, inspect, cancel, and purge platform-managed jobs. Requests use the gateway jobs shape; the SDK supplies the required idempotency header. |

### Auth & email

| Tool | Description |
|------|-------------|
| `request_magic_link` | Request link, code, or both passwordless email credentials; code modes return an opaque challenge handle. |
| `verify_magic_link` | Exchange either a link token or challenge handle + six-digit code for `access_token` + `refresh_token`. |
| `create_auth_user` / `invite_auth_user` | Create/update auth users and send trusted service-key invites. |
| `set_user_password` | Change, reset, or set a user's password. |
| `auth_settings` | Configure password set, preferred sign-in method, public signup policy, and project-admin passkey enforcement. |
| `passkey_register_options` / `passkey_register_verify` | Create and verify WebAuthn passkey registration ceremonies. |
| `passkey_login_options` / `passkey_login_verify` | Create and verify WebAuthn passkey login ceremonies. |
| `list_passkeys` / `delete_passkey` | List or delete the authenticated user's passkeys. |
| `create_mailbox` / `get_mailbox` / `update_mailbox` / `delete_mailbox` | Per-project mailbox local parts. The managed address is returned as `managed_address` (`<slug>@<project-mail-host>.mail.run402.com`); the same slug may exist in another project. Create is not idempotent. `update_mailbox` currently sets `footer_policy` (`run402_transparency` or `none`; `none` requires hobby/team, prototype is locked). |
| `list_mailboxes` / `set_mailbox_defaults` | Inspect mailbox `address`/`managed_address`, default-role/readiness/footer-policy metadata, and set `default_outbound_mailbox_id` / `auth_sender_mailbox_id` explicitly. |
| `send_email` | Template (`project_invite`, `magic_link`, `notification`) or raw HTML. Single recipient. Omitting `mailbox` uses the configured outbound default; result echoes `mailbox_id` and `from_address` when returned. |
| `list_emails` / `get_email` / `get_email_raw` | Read messages. `get_email_raw` returns RFC-822 bytes for DKIM / zk-email verification. |
| `register_mailbox_webhook` / `list_mailbox_webhooks` / `get_mailbox_webhook` / `update_mailbox_webhook` / `delete_mailbox_webhook` | Email-event webhooks (delivery, bounced, complained, reply_received). |
| `domains_ensure` / `domains_check` / `domains_repair` / `domains_test_receive` | Use ProjectDomain for custom email sending and inbound receive instead of the retired sender-domain workflow. |

### AI helpers

| Tool | Description |
|------|-------------|
| `generate_image` | Text-to-PNG via x402 ($0.03 / image). |
| `ai_translate` | Translate text. Metered per project. |
| `ai_moderate` | Moderate text (free). |
| `ai_usage` | Translation quota (used / included / remaining). |

### External x402 buyer

| Tool | Description |
|------|-------------|
| `pay_url` | Call an arbitrary HTTP(S) URL, satisfy a supported exact x402 challenge up to `max_usd_micros` (default 100000), optionally require verified merchant evidence with `require_receipt`, and return `x402-commerce-result.v1`. |

### Apps marketplace

| Tool | Description |
|------|-------------|
| `browse_apps` | Browse public forkable apps. |
| `get_app` | Inspect an app, including expected `bootstrap_variables`. |
| `fork_app` | Clone schema + site + functions into a new project. Runs the app's `bootstrap` function with provided variables. |
| `publish_app` | Publish a project as a forkable app. |
| `list_versions` / `update_version` / `delete_version` | Manage published versions. |

### Tier & billing

| Tool | Description |
|------|-------------|
| `set_tier` | Subscribe / renew / upgrade a tier (auto-detects action). x402 payment. |
| `tier_status` | Current tier, lease expiry, usage, and function authoring caps when returned. |
| `get_quote` | Tier pricing (free, no auth). |
| `create_email_organization` / `link_wallet_to_organization` | Email-based organizations; hybrid Stripe + x402. |
| `create_checkout` | Org checkout for balance top-ups, tiers, or email packs. |
| `billing_history` | Ledger history. |
| `set_auto_recharge` | Auto-buy email packs when credits run low. |

### KMS signers (on-chain signing)

| Tool | Description |
|------|-------------|
| `provision_signer` | AWS KMS-backed Ethereum signer. $0.04/day rental + $0.000005 per call. Private keys never leave KMS. |
| `get_signer` / `list_signers` | Metadata + live native balance. |
| `set_recovery_address` / `set_low_balance_alert` | Optional safety nets. |
| `contract_call` | Submit a write call (chain gas at-cost + KMS sign fee). |
| `contract_read` | Read-only call (free). |
| `get_contract_call_status` | Lifecycle, gas, receipt. |
| `drain_signer` | Drain native balance (works on suspended signers, the safety valve). |
| `delete_signer` | Schedule KMS key deletion (refused if balance ≥ dust). |

### Allowance & organization

| Tool | Description |
|------|-------------|
| `init` | One-shot setup: allowance + faucet + tier check + project list. |
| `status` | Full organization snapshot (allowance, balance, tier, projects). |
| `allowance_status` / `allowance_create` / `allowance_export` | Local allowance management. |
| `request_faucet` | Request testnet USDC. |
| `redeem_voucher` | Redeem a promo code for run402 prepaid credit. |
| `check_balance` | USDC balance for an allowance address. |
| `list_projects` | Named, domain-aware project inventory (name, site_url, custom_domains, org). Membership-scoped; supports `org_id` filter, `all` cross-wallet read, and pagination. |
| `list_tenant_payments` | Redacted tenant x402 payment history for priced function routes on a project. |
| `rename_project` | Rename a project to fix an auto-generated name (org admin / `project:write` grant). |
| `set_org_payout_wallet` | Set/clear the org default payout wallet for priced routes; admin/owner + step-up gated. |
| `project_get` / `project_use` | Server project detail and active-project selection. `project_use` validates through the server, then stores only an active id pointer. |
| `project_key_cache_status` / `project_key_cache_export` | Explicit local project-key cache tools. `status` is redacted; `export` requires `reveal: true` and emits cached secret key material. |
| `create_checkout` | Org checkout for balance top-ups, tiers, or email packs. |
| `send_message` | Send feedback to the run402 team. |
| `set_agent_contact` / `get_agent_contact_status` / `verify_agent_contact_email` | Register agent contact info, read assurance status, and start the operator email reply challenge. |
| `start_operator_passkey_enrollment` | Email a run402 operator passkey enrollment link to the verified contact email. |
| `get_operator_status` | Compact operator-health snapshot: contact assurance state, critical items, skipped notifications, organizations, projects, active thresholds. Read via `run402 doctor` or directly. |
| `get_notification_preferences` / `set_notification_preferences` | Read/update operator notification preferences (cadence, channels, per-class toggles, locale, timezone). Cross-wallet effects require `email_verified`; webhook URL changes require `operator_passkey`. |
| `list_notifications` | Per-delivery-attempt audit log. Paginated, filterable by event_type / since. |
| `test_notification` | Fire a real test notification through the full worker pipeline. Audit row marked `is_test=true`. Rate-limited per wallet at 1/min. |
| `rotate_webhook_secret` | Generate a new HMAC signing secret for the operator webhook (returned exactly once). Previous secret remains valid for 24h. Requires `operator_passkey`. |
| `list_project_events` | Cursored project events feed — catch up on deploy activations, suspensions, transfers, lifecycle cliffs since your stored cursor. Also reads the org-wide union via `org_id`. Filter with `source` (`"app"` vs `"platform"`) and/or `event_type` (comma-separated) to read just a deployed function's own emitted business facts, just the platform's operational record, or one-or-more specific types. |
| `errors_list` | Grouped error fingerprints + a release-baselined promote/revert verdict. Poll with `new_in` after a promote to gate on new error identities; pass `fingerprint_id` for one identity's full detail. |

### Agent messaging (coordination rooms)

| Tool | Description |
|------|-------------|
| `join_room` | Arrive in a project's coordination room: register this session's presence (`requested_name` honored-or-suffixed, `Opus` → `Opus-2`) and see who else is live, what they're working on, and what they've claimed. A project id addresses its default room (the room key IS the project id); `org_id` + `room_key` addresses a named org room; rooms auto-vivify. |
| `send_room_message` | Durable room-visible message (markdown, ≤32 KiB). `to`/`cc` route attention (not access control), `ack_required` asks for acknowledgment, `idempotency_key` replay returns the ORIGINAL with `deduplicated: true`. Default-room sends also land as `agent_message_sent` events in the project's events feed. |
| `read_room_messages` | Cursored catch-up on what the other agents said (opaque `mcr_…` cursor; stale cursor → `reset: true` + `earliest_cursor`, never an error), `unread`-only filtering, thread filtering, or one FULL message by `message_id`. |
| `ack_room_message` | Acknowledge a message addressed to you — the sender sees your `acked_at`. Recipients only; idempotent. |
| `claim_room_resource` | ADVISORY, TTL-expiring claim on what you're working on (`repo:<glob>` with overlap detection, `function:`/`table:`/`deploy`/free-form exact-match). Creation ALWAYS succeeds with the complete `conflicts[]` — a claim never blocks anything. |
| `release_room_claim` | Release a claim you hold (idempotent; holder only). Pair with a `send_room_message` handoff note. |

### Service status (no auth)

| Tool | Description |
|------|-------------|
| `service_status` | Public availability report: 24h/7d/30d uptime per capability, operator, deployment topology. |
| `service_health` | Liveness probe with per-dependency results. |

## Configuration

| Variable | Default | Purpose |
|----------|---------|---------|
| `RUN402_API_BASE`        | `https://api.run402.com`         | API base URL (override for staging) |
| `RUN402_CONFIG_DIR`      | `~/.config/run402`               | Local credential storage base directory (named wallets live under `profiles/<name>/`) |
| `RUN402_WALLET`          | `default`                        | Active named wallet (profile). Overridden by `--wallet <name>` and per-directory `.run402.json`; `RUN402_PROFILE` is an alias. See `run402 wallets`. |
| `RUN402_ALLOWANCE_PATH`  | `{config_dir}/allowance.json`    | Custom allowance file path |
| `RUN402_MCP_PROFILE`     | *(unset — all 198 tools)*        | `run402-mcp` only. `buyer` registers just the 6 tools a buy-only agent needs (~660 tokens instead of ~43,200). Unknown name exits 1. |

Local state lives at:

- profile `state.json`: active project pointer and profile state
- profile `credentials/project-keys.v1.json` (`0600`): local anon/service key cache for explicit credential-required operations
- `~/.config/run402/allowance.json` (`0600`): wallet for x402 signing

Legacy `projects.json` files are one-way migration input only. `anon_key` and `service_key` have no expiry; lease enforcement happens server-side. Inspect cache state with `run402 credentials project-keys status --project <id>` and export secrets only with `run402 credentials project-keys export --project <id> --reveal`.

## Development

```bash
npm run build           # builds core/, sdk/, then the MCP server
npm test                # SKILL + sync + unit tests
npm run test:e2e        # builds generated CLI SDK mirrors, then runs CLI end-to-end tests
npm run test:sync       # checks MCP/CLI/OpenClaw/SDK stay in sync
npm run test:skill      # validates SKILL.md frontmatter + body
```

Architecture: every tool / subcommand / skill script is a thin shim over an `@run402/sdk` call. `core/` holds Node-only filesystem primitives (keystore, allowance, SIWE signing) wrapped by the SDK's Node provider. See [`CLAUDE.md`](./CLAUDE.md) for the full layout.

## Links

- Web: <https://run402.com>
- Self-host backend (run402 Core): <https://github.com/kychee-com/run402-core>
- API docs (HTTP): <https://run402.com/llms.txt> · <https://run402.com/openapi.json>
- CLI docs: <https://docs.run402.com/llms-cli.txt>
- Status: <https://api.run402.com/status>
- Health: <https://api.run402.com/health>

## License

MIT for this repo (the agent surfaces: SDK, CLI, MCP server, Astro integration, OpenClaw skill). The full backend, [`run402-core`](https://github.com/kychee-com/run402-core), is Apache-2.0.

