run402: full-stack backend infrastructure for AI agents
Postgres, auth, storage, serverless functions and atomic deploys — provisioned, operated and paid for by an agent through a typed SDK, a CLI or an MCP server. No cloud console, no signup. Open source.
Run402 is open-source backend infrastructure for AI agents and coding agents — a backend-as-a-service addressed to a machine rather than to a person. An autonomous agent provisions a Postgres database, user auth, file storage, serverless functions and site hosting, ships them in one atomic deploy, and pays for the usage itself. Comparable in surface to Supabase, Firebase or Vercel; different in that there is no dashboard to sign into and no human-issued API key to copy.
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 holds the full backend (Apache-2.0), and kysigned is the first product running on it.
One call to run402 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 | 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 | Terminal, scripts, CI, agent-controlled shells: JSON in, JSON out, exit code on failure |
run402-mcp | Claude Desktop, Cursor, Cline, Claude Code: core run402 operations as MCP tools |
| OpenClaw skill | OpenClaw agents (no MCP server required) |
| Run402 for 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 | 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 repo under packages/functions; run402 Cloud consumes the published npm package when it bundles function zips. |
@run402/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
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:
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:
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.
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:
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.
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.
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:
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.
{
"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:
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:
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:
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:
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):
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).
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"}'
repos: your repository history, encrypted before it leaves the machine
run402 repos is a Git remote whose contents are encrypted on your own machine and stored as a chain of signed, admitted heads. It exists so your repository history outlives the machine it was written on — without that outliving requiring you to hand Run402 the plaintext. The wire protocol is r402s/v0. One noun, thirteen verbs (repo singular resolves identically): run402 gitvault <verb> answers COMMAND_MOVED/COMMAND_REMOVED, and r.gitvault is the SDK's name (gitvault stays the protocol/infrastructure name; repos is what you type).
Three claims, three different strengths. These are the entire approved claims vocabulary for this feature:
- Run402 cannot decrypt your gitvault or repository history. Deployment artifacts remain a disclosed plaintext custody boundary. Cryptographic, against Run402 itself: in the vault lane, source payload and repository-history content are ciphertext-only; the substrate retains only enumerated plaintext metadata and holds zero vault keys. The deploy lane is separate and disclosed — the platform custodially holds the plaintext artifacts of every deploy. It can read what you deployed; it cannot read what you did not.
- Activation requires vault admission by default; an explicit, audited override can bypass it. An operational platform invariant, enforced and auditable — not cryptographic against the platform that enforces it.
- Retention is an operational promise of the platform, not a cryptographic guarantee against it (the host controls timestamps and bytes).
The vault-only track — three lines, muscle memory intact, nothing to pay:
run402 init # once per machine
run402 repos create my-notes # project + vault + origin remote, one free call
git push -u origin main # publishes, encrypted before it leaves the machine
origin is claimed additively: when the directory has no origin yet, the scaffold names ours origin — git push origin main just works, no side-remote name to remember. An existing origin is never touched; the fallback is run402 instead, and the response says which happened and why. The free path is the whole path — no slug, no fee, no ceremony. When you want pretty run402::<org-slug>/<name> addresses (clone-by-name, push-to-create), claim an org slug once — the optional named-address upgrade described below.
Named addressing. run402::<org-slug>/<name> works alongside the id-form run402::<org_id>/<project_id> in the same slot — pick an org slug once (run402 org slug <slug>, owner-only, a small one-time claim fee), and every repo under it is run402::<slug>/<name>. Pushing a name that doesn't exist yet push-to-creates it: the project and vault are allocated atomically, and a losing concurrent pusher resolves cleanly to the winner's repo instead of erroring — its work is not lost, it just wasn't the creator. The first time a named remote resolves on a checkout, the resolved id is pinned into that checkout's local git config — every later push/fetch follows the pin directly, so a later rename of the org slug or repo name never breaks an existing clone. The id-form address needs no pin (a project id never changes) and stays the cold-restart path: an agent that lost its local state but still holds authority on the project can always fall back to run402::<org_id>/<project_id>.
One thing to know up front: V0 is single-principal. Exactly one machine (this keystore) can open the vault until human envelopes ship. State it plainly, don't bury it — see "If you lose the keystore" below.
The explicit, ceremonial form still works, and allocates the SAME way git push does lazily on first use — useful for scripts, or for the receipt to land in JSON stdout instead of stderr:
# 1. Provision. Inside a repository that already exists, this adds the origin
# remote (run402::<org_id>/<project_id>). Not a repository yet?
# `run402 init --git-remote` creates one first. It needs a project selected
# (`run402 projects use <project_id>`, or RUN402_PROJECT_ID).
run402 init
# 2. Allocate the repo's vault explicitly. Separate from `run402 init` on
# purpose: this is the step that mints key material on this machine and
# prints a one-shot recovery receipt. Idempotent — an existing vault comes
# back deduplicated. (Skip this step and git push / repos snapshot
# against an unallocated project allocates the SAME way, lazily, on
# first use — the two paths don't stack; this one just does it now,
# explicitly, so the receipt lands in JSON stdout instead of stderr.)
run402 repos create --project <id>
# 3. Snapshot — capture the working tree, encrypt it, publish a signed head.
run402 repos snapshot --message "wip: refactor the parser"
git push origin main # ...or push your own branches, via git-remote-run402
# 4. View, then fsck: walk the head chain from your authenticated pin.
run402 repos view
run402 repos fsck --budget 500
# Restore anywhere, with plain git.
git clone run402::<org_id>/<project_id> restored
Cloning needs a Run402 principal on this machine — a wallet with an allowance and a keystore holding an envelope for this vault — this is encrypted git, not a shareable link.
A fresh clone installs local refs/r402/retain/<oid> refs for every retained deploy-capture tip no branch reaches, so a plain git fsck is silent — git for-each-ref refs/r402/ lists what is retained. Clones made by a client older than this one (or a checkout whose ref write degraded) may still show dangling commits under git fsck; harmless, not corruption — one run402 repos fsck run installs the missing refs. A retained ref locally pins that history against git gc until the vault prunes the capture, at which point the next fetch retracts it.
repos snapshot is the CAPTURE lane — the protocol deploy ref plus the HEAD target — because a dirty tree captures as a synthetic commit that sits on no branch. Your own branches and tags reach the vault through git push origin <branch>.
Allocating a vault does NOT gate the project's deploys. gitvault_policy stays unset until you set it — a vault created by a first git push or repos create never silently changes how you deploy. A deploy against a vaulted, ungated project proceeds ungated and its result carries a typed next_actions entry offering run402 repos policy required; every later ungated deploy carries a warnings[] entry naming the drift, until the policy is set either way — never a block, never an interactive prompt. Once gitvault_policy is required, a deploy must present a vaulted capture at commit — run402 deploy apply produces one automatically on any machine holding the keystore; un-gate with run402 repos policy grandfathered --reason "<why>" (owner + step-up, audited, reversible with run402 repos policy required). Vaulting your source is never gated on a deploy, either way. run402 doctor reports the policy, whether this machine can satisfy it, and where the keystore lives.
Before snapshot reports that anything landed, the client compares every finalization receipt against its local expected manifest and reads the admitted head back from storage — a 200 alone is never enough. Maintenance is one verb, run402 repos gc — git gc's own two halves (checkpoint publication + prune planning), never described as "exactly git gc" since the deletion ceremony is stricter: it plans locally, and submits only when handed both verifier receipts — one from this CLI, one from the independent r402s-verify — with --submit --intent-core <path> --verifier-receipt <path>; only the control-plane-signed completion says what was deleted.
From the SDK, with identical semantics — vault reads run anywhere, and the verbs that touch a git working tree or the on-disk keystore are Node-only:
import { run402 } from "@run402/sdk/node";
const r = run402();
const vault = await r.gitvault.forProject(projectId); // cold restart: no local state needed
const pushed = await r.gitvault.push({ project_id: projectId, snapshot: { message: "wip" } });
const state = await r.gitvault.verify({ project_id: projectId });
The encrypted second remote — the zero-migration pattern. Keep GitHub/GitLab as the primary (collaboration, CI, reviews, unchanged) and add GitVault as the second remote: git remote add gitvault run402::<org_id>/<project_id> + git push gitvault --all, and a complete, continuously updated copy of your history exists that the storage provider itself cannot read. The reason this matters, said plainly and as capability rather than accusation: a host that can READ private repositories can — under a future policy, an acquisition, a training pipeline, a subpoena, or a breach — index them, train models on them, or hand them to someone who will. Run402 cannot decrypt your gitvault or repository history. Deployment artifacts remain a disclosed plaintext custody boundary.
A vault-only project is first-class. run402 init (or run402 repos create <name>), then git push origin …, then gc / fsck / access, and never a deploy — a supported shape, not a degraded one. One consequence is worth stating plainly: a vault-only project has no deploy lane, so the disclosed plaintext custody boundary is empty and there is consequently no custodial restore path.
If you lose the keystore. The vault protects source history from host-side loss while a principal keystore survives. The "while" clause is load-bearing: in V0-A, whole-machine or whole-keystore loss is terminal for vault history until human envelopes ship, and run402 repos view prints that sentence verbatim. Back up the keystore directory run402 repos view reports as keystore.root and prints under the terminal-loss statement — ~/.config/run402/gitvault for the default wallet, ~/.config/run402/profiles/<wallet>/gitvault for a named one. The recovery receipt is an integrity anchor, not a decryption key — it proves the vault you are served is the one you created, and it decrypts nothing. It is not a secret; the more copies the better. The reminder gets louder as the vault gets more valuable: quiet at genesis, a STANDING run402 doctor warning once the vault crosses any of ≥10 generations / ≥10 MB / ≥14 days since genesis — cleared only by adding a second principal, never by an attestation, because V0 cannot verify one is true.
The exit ramp: mirror your own copy. run402 repos mirror <destination> [--profile <name> | --ambient] (S3 or a plain directory) configures a second, customer-owned copy of the vault's ciphertext — the destination and credential name live in a config file beside the keystore, never in run402.config.json, never a raw secret. Once set, every snapshot dual-pushes to it automatically, reported as a separate mirror_push field beside the vault result; a mirror failure never blocks, slows, or changes the actual publish. run402 repos mirror --backfill (idempotent, resumable) catches it up on demand; run402 repos fsck --mirror is a KEYLESS integrity probe — it reports the recoverable generation without touching key material. run402 repos recover <source> --out <dir> needs no server at all: it reads the mirror, verifies the chain, and decrypts with the local keystore alone. Named recover rather than restore, which already means something else in git. Two things to know before you rely on it: it proves validity, never freshness — an older mirror looks identical to a genuinely short history — and a mirror without the keystore or an equivalent key recovers nothing, since mirroring ciphertext does not create a second key; the V0 terminal-loss statement above is unchanged.
The human backup path (gitvault-recovery-custody). A human org member who completed source enrollment at console.run402.com/account holds an equivalent key with no keystore at all: their member key lives as sealed wrappers (passkey PRF and/or a source recovery code), and run402 repos recovery-bundle downloads the versioned recovery bundle (key identity + wrapper ciphertexts — still nothing the platform can open). Kept with a vault mirror — copy it to member-recovery-bundles/<name>.json under the mirrored prefix — that bundle + the source recovery code + the vault's recovery receipt recover the repository with no run402 server and no keystore: run402 repos recover <source> --out <dir> --receipt <pin.json> (the code is prompted with hidden input; --bundle <file> if the bundle isn't in the mirror). A raw passkey PRF output is deliberately NOT a recovery input — the no-server path for a human is the recovery code. run402 doctor's recovery_posture check tells you whether each vault-owning org actually has this backstop configured.
run402 repos mirror s3://acme-vault-mirror --profile acme --region us-east-1
run402 repos mirror --backfill
run402 repos recover s3://acme-vault-mirror --out ./restored --repo src_1a2b3c
run402 repos recovery-bundle --out ./bundle.json # the member's no-keystore recovery half
run402 repos recover ./mirror-copy --out ./restored --receipt ./recovery-receipt.json --bundle ./bundle.json
Verify it without trusting our client. r402s-verify is an independent-lineage verifier for the same protocol — a separate language, separate authorship, and a separate primitive stack, deliberately sharing no implementation code with the SDK. That non-sharing is the point: a differential verifier that reuses the code it is checking verifies nothing. It lives on the r402s-verify branch of this repository with its own workflow, ships prebuilt release binaries, and also builds with cargo build --release. The full protocol specification and threat model it verifies against are published in docs/gitvault/, and the frozen conformance vectors in test-vectors/r402s-v0/.
Cost. There is no separate repos price — a vault's bytes count against the same organization-pooled storage budget your projects already share, charged once per unique object with a 4 KiB per-object accounting floor and a 1 MiB per-vault minimum.
SDK: @run402/sdk
npm install @run402/sdk
Two entry points:
@run402/sdk: isomorphic. Bring your ownCredentialsProvider(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 opaquepaymentSigner, explicitallowancePath, the supplied provider'sreadAllowance(), 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 exposessites.deployDir(...),fileSetFromDir(...), typed deploy-manifest helpers (loadDeployManifest,normalizeDeployManifest), andresolveRun402TargetProfile()for app build scripts that need the same Core/Cloud target the CLI uses.
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), 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.
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:
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:
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:
cd "$HOME/.buzz"
DO_NOT_TRACK=1 npx --yes skills@latest add https://run402.com -s run402-buzz -a codex -y
Windows PowerShell uses:
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 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. 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.
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 and cli/llms-cli.txt (R402_* SSR Runtime Error Codes section).
CLI: run402
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 (also at https://docs.run402.com/llms-cli.txt).
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.
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
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.
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:
{
"mcpServers": {
"run402": { "command": "npx", "args": ["-y", "run402-mcp"] }
}
}
Cursor
Add to .cursor/mcp.json:
{
"mcpServers": {
"run402": { "command": "npx", "args": ["-y", "run402-mcp"] }
}
}
Cline
Add to your Cline MCP settings:
{
"mcpServers": {
"run402": { "command": "npx", "args": ["-y", "run402-mcp"] }
}
}
Claude Code
claude mcp add run402 -- npx -y run402-mcp
OpenClaw skill
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.
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 fro |