CLI reference
CLI:
npm install -g run402@latestSDK:npm install @run402/sdk(typed TS client; same namespaces; Node/Deno/Bun/V8 isolates) Docs URL: https://docs.run402.com/llms-cli.txt HTTP API reference: https://run402.com/llms-full.txt Operated by: Kychee, Inc. Terms: https://run402.com/humans/terms.html Contact:run402 feedback send "your message"(requires active tier)
Run402 = Postgres + REST + Auth + Storage + static & Astro-SSR site hosting + same-origin routes + Node 22 functions + email + image generation behind one CLI.
Run402 is agent-first because agents are first-class participants, not because people disappear. Use your own principal and authenticator rather than a borrowed human account. Identity records who invoked the command; organization roles, grants, grant keys, freshness, and spend policy determine what that principal may do. A founder agent may remain owner of its org-of-one.
Agent-critical events:
- Shared full-stack workflow:
run402 upcoordinates planning, staging, migrations, activation and verification. Failures can leave resumable or partial work; promoting an earlier release does not undo migrations. Inspect returned operation state and next actions.deployis the advanced apply primitive. - No platform token: the local wallet (
~/.config/run402/wallet.json) signs requests. Per-projectanon_key/service_keyare runtime data-plane keys (PostgREST/Storage/Functions), permanent, and embeddable/server-side respectively. - Agent-paid usage: x402 USDC on Base, MPP pathUSD on Tempo, or sats over Bitcoin Lightning (MPP) from the agent’s platform-minted wallet, signed by the local wallet. The organization’s allowance (card, Lightning, or voucher funded) settles first when it covers the price; CLI behavior is unchanged.
run402 wallets <current|fund|balance>—currentreports the active wallet’s address,rail, file path, and faucet use;balancereads on-chain funds plus the organization’sallowance_usd_micros;run402 wallets lightning statusreports a Lightning profile’slightningblock (wallet id, status, custodyrun402_hub, address, budget, starter,balance_sats,budget_remaining_sats; never the pairing).
Install + deploy — the only first-deploy path (the front door at https://run402.com/llms.txt is the same three lines with a full manifest):
npm install -g run402@latestrun402 up --name "my-app" -y # wallet, tier, project, your name, rehearsal when warranted, deployrun402 redeem <code> # only if you were given a promo code — see Promo codes belowup is a thin CLI shim over the SDK action runner; the SDK owns manifest validation, project resolution, recursive prerequisites, idempotency-key derivation, and deploy. A static page never needs a key copied into it: every Run402 host serves /_run402/config.js (window.RUN402 = { project_id, api_base, anon_key }) — see “Runtime config on every host” below. prototype is free with the testnet faucet; use hobby / team for mainnet.
Rehearsal is up’s decision, not a flag: a migration-bearing plan against a project with a live release is rehearsed on a contained branch and committed only on a passing report; a first deploy has nothing to protect and commits directly — see “Protecting a live database” below. run402 deploy, run402 projects provision, and run402 deploy rehearse are the primitives underneath up: up does any missing setup and then runs the same deploy run402 deploy runs on its own.
CLI update awareness is advisory and fail-open. Normal commands never wait for npm; they use cached update state and keep success stdout as the command payload. Stale notices appear as structured JSON on stderr, or as {"type":"cli.update_available",...} in --json-stream. RUN402_NO_UPDATE_CHECK=1 suppresses notices/checks; CI skips live checks unless RUN402_UPDATE_CHECK=1; run402 doctor --refresh is the explicit bounded live check.
App build scripts should read the same target/profile store through resolveRun402TargetProfile() from @run402/sdk/node, not by parsing target.json or local project-key cache files themselves.
Core events
Section titled “Core events”- Wallet:
~/.config/run402/wallet.json(0600); active project state: profilestate.json; local project-key cache: profilecredentials/project-keys.v1.json(0600). Legacyprojects.jsonis migration input only. - Project keys are cached automatically after provision or fork for operations that truly need anon/service keys. They are not project inventory.
<project_id>in commands =project_idfromrun402 projects list- Output: JSON stdout on success; JSON stderr on failure; exit 0 success, non-zero error. See Output Contract.
- CLI handles x402 / MPP signing; do not request private keys or payment libraries.
run402 upis the only compound CLI command. It emits natural JSON withsteps[](no top-level successstatus). Use--checkfor local-only validation,--planfor gateway-reviewed intent, and--require-planfor exact reviewed apply.- GitHub Actions deploys use OIDC: link once with
run402 ci link github; generated workflow callsrun402 deploywithpermissions: id-token: write. - Projects, sites, subdomains, forks, functions, secrets, asset storage: free with active tier. Only image generation ($0.03) is per-call
- Env overrides:
RUN402_API_BASE(overrides stored target; defaulthttps://api.run402.com),RUN402_CONFIG_DIR(base credential dir, default~/.config/run402),RUN402_WALLET(active named wallet/profile, defaultdefault; aliasRUN402_PROFILE),RUN402_WALLET_PATH(custom wallet file path, default{config_dir}/wallet.json),RUN402_GRANT_KEY(a grant-key bearer fromrun402 grants create --key; when set it is the only credential sent, so a process with no wallet can deploy),RUN402_AGENT_NAME(the name this agent runtime declares for itself;run402 upsets it as an agent principal’s display name, overriding an existing agent name; human/unknown principals are preserved — reported asidentity.source: "explicit"; promotion credit and room presence use it; equivalent torun402 whoami --set-name <name>),RUN402_CLIENT(declares the coding-agent client whenuphas no environment marker for it, e.g.RUN402_CLIENT=grok; checked before every marker; it names an unnamed agent principal but never overrides an existing name — useRUN402_AGENT_NAMEfor that),RUN402_PROGRAM/RUN402_MODEL(room-presenceprogram/modellabels;programis otherwise inferred asclaude-code,codex,cursor, orgrok),RUN402_TRACE(any non-empty value: one stderr trace line per SDK request — see Observability below),RUN402_VAULT_TRACE(debug-only,1: one stderr line per vault transport operation — see Observability below).run402 init --api-base=<url>persists the active target in{config_dir}/target.jsonor{config_dir}/profiles/<name>/target.json. - Wallets:
run402 walletsmanages named profiles. Select via--wallet <name>(--profile),RUN402_WALLET, or nearest.run402.jsonbinding (commit-safe name only). Precedence: flag > env >.run402.json/.run402.local.json>wallets usedefault >default. Env/binding conflict hard-fails unless flag passed.defaultstays at config root; named wallets live under{base}/profiles/<name>/. Non-default active wallet is echoed on stderr and shown instatus/wallets current.
Output Contract
Section titled “Output Contract”Uniform contract:
- Success: stdout emits the natural payload, never wrapped; no top-level
status. - Reads/lists: resource directly, e.g.
projects get->{ project_id, public_id, name, ... },projects list->{ projects: [...], scope?, has_more?, next_cursor? },credentials project-keys status-> local-cache provenance. - Mutations without natural payload: affected ids + boolean action field, e.g.
{ key, project_id, set: true },{ name, project_id, deleted: true },{ domain, project_id, released: true }; never{}. - Local-state reads (
status,wallets current): nullable typed fields, e.g.{ wallet: null, hint: "Run: run402 init" }orconfigured: false; absence exits 0. - Raw/text stdout is opt-in only (
functions invoke --raw, file-output commands, help/version/dev human surfaces). Machine-readable command defaults emit parseable JSON; for example,wallets currentemits{ "local_label": "default", "address": "0x...", ... }. - Failure: stderr JSON envelope with top-level
status: "error"+ non-zero exit. That sentinel appears on stderr only. - Validation commands may exit 0 with payload issues, e.g.
validate-exposeprintshas_errors: true; branch on payload fields. - Payload-internal
statusfields are not envelopes, e.g.doctor.checks[].status. - JSON is ALWAYS the default on stdout.
--jsonis a universally-accepted NO-OP: every command takes it, and passing it never changes stdout. Never pass it to “get JSON” — you already have JSON. The one exception isassets put --json, a deprecated alias for--stream(NDJSON progress). - Human-readable rendering is an explicit opt-out, spelled
--human(run402 up,run402 errors list).--humancombined with--jsonis aBAD_USAGEerror. cli-output-contract.test.mjsandcli-json-noop-contract.test.mjsguard this; violations are regressions.- v3.0 breaking change: success wrapper
{ status: "ok", ...payload }removed; gate on exit code. Stderr error envelope unchanged.
Observability
Section titled “Observability”The SDK’s request kernel (sdk/src/kernel.ts — the one place that touches fetch) carries two always-available diagnostics, for every SDK caller (CLI, MCP, git-remote-run402):
RUN402_TRACE— set it to any non-empty value and every request writes one line to stderr:r402 <METHOD> <path> -> <status> <ms>ms attempt=<n>.<path>never carries its query string, and the line never carries headers, bodies, or tokens (the same redaction posture as the payment-attempt journal).<status>isERRfor a request that never got a response (network failure).- Per-instance stats — every SDK instance accumulates
round_trips,wire_ms(summed),bytes_up, andbytes_down(Content-Lengthwhen present, measured otherwise), monotonic for the instance’s lifetime, read viasdk.stats(). Everyrun402 repos <verb>result andrun402 deploy’s final result carry this as astats: { round_trips, wire_ms, bytes_up, bytes_down }block, always — no flag needed. It reflects only the calls that one command’s own SDK instance made; a helper that resolves its own SDK internally (e.g. org/wallet context resolution shared across command families) is not reflected in that command’sstats. -v/--verbose— onrepos <verb>anddeploy, prints one extra stderr summary line with the same numbers (stats: round_trips=… wire_ms=… bytes_up=… bytes_down=…). Coexists with--human.
MCP tool output is markdown, not the stats envelope field — this is a CLI/SDK-edge feature by design.
RUN402_VAULT_TRACE — debug-only, vault-specific, and out of scope of the two kernel diagnostics above: set it to 1 and every git push/git fetch/run402 repos <verb> call against a vault writes one stderr line per vault transport OPERATION (vault-trace: <op-kind> [path=… | paths=N | objects=N | gen=… | repo=…] [bytes=N] <ms>ms), plus a session summary at process exit (op count, time spent in the vault transport, wall-clock). This is the one place presigned-object PUT/GET traffic is visible at all — RUN402_TRACE and sdk.stats() above are scoped to the request kernel’s own apiBase-relative calls, so they never see the direct-to-bucket presigned reads/writes the vault’s carrier, WAL, and checkpoint objects ride. A traced GET for one of the five edge-eligible kinds (wal_pack, ref_state, retention_roots, checkpoint_manifest, checkpoint_pack) may in event be answered by the platform’s CDN edge rather than the bucket directly when the gateway offers one for that read (vault-read-edge-cache, design D5) — automatic, verified byte-for-byte identically either way, and falling back silently to the direct presigned URL on any edge failure; the trace line reports the same op regardless of which one actually served it. Not a canonical surface, not what any round-trip budget is measured against — it is a debugging aid for understanding WHERE a slow git push/fetch spent its round trips.
run402 up (SDK action runner)
Section titled “run402 up (SDK action runner)”run402 up [repo-or-path] [--name <name>] [--project <project_id>] [--manifest <path>] [--dir <path>] [--tier <prototype|hobby|team>] [-y|--yes] [--check|--print-spec|--print-manifest|--plan|--require-plan <plan_id>] [--no-rehearse] [--verify] [--propagation-budget-s <seconds>] [--no-propagation-wait] [--json|--json-stream|--human] [--quiet] [--allow-warning <code> ...] [--allow-warnings]
run402 up verify [repo-or-path] [--project <project_id>] [--manifest <path>] [--dir <path>] [--propagation-budget-s <seconds>] [--no-propagation-wait] [--json|--json-stream|--human] [--quiet]
Use up for a repo-level app deploy when the workspace has a deploy manifest. It discovers run402.json, run402.deploy.json, then app.json under --dir / cwd; when none is there it looks one directory down (immediate children, skipping node_modules and dot-directories) and reports what it finds in UP_MANIFEST_REQUIRED details.nearby_manifests[] with a leading run_in_directory next action (run402 up --check --dir <dir>) — up never walks into subdirectories on its own. --manifest <path> naming a file that does not exist fails with MANIFEST_NOT_FOUND (details.path is the resolved absolute path) and a create_manifest next action, for .json manifests, run402.json, and executable .ts/.js configs alike. A run402.json with the app schema or app-specific markers uses the app-install graph; a release-shaped run402.json uses the same ReleaseSpec normalization accepted by deploy. Malformed app manifests return APP_SPEC_INVALID with the failing field instead of an internal JavaScript exception. Validation and filesystem-reference checks finish before any mutation, then the SDK action plan executes: every local file the manifest references — migration sql_path/sql_file, function source.path and files, site { path } entries and dir() targets, assets.put[].source — is checked to exist, and a missing one fails with MANIFEST_FILE_MISSING (exit 1) whose details.missing[] lists { field_path, path, kind } (migration_sql | function_source | site_file | site_dir | asset) and whose next_actions[] carries one create_file entry per file. The same check runs in every mode, before any gateway call.
Project resolution order:
- explicit
--project - workspace link
.run402/project.json(schema_version: "run402.workspace-project.v1",project_id, optionalname,target) - manifest
project_id - approved project creation from
--name
Conflicting selectors fail before wallet, tier, identity, link or Git mutations. Missing intent returns UP_PROJECT_REQUIRED with one select_project action. -y approves work only after the target is known. On an interactive terminal without -y, enter an existing project ID or new <name>; there is no default project choice. Use RUN402_PROJECT_ID as an explicit CLI environment selector.
--name is only project creation/link metadata. It is not part of the deploy manifest, does not select a project when another selector already resolved one, and never renames an existing project. The workspace link is a local convenience file; it is written atomically and skipped in local check / reviewed-plan modes.
Approval and recursion:
- Non-interactive recursive mutations require
-y/--yes; without it the command fails before mutating and returns a structured approval-required error. - If wallet/tier/project/workspace link are already configured, plain
run402 upruns the requested deploy without-y. - In a TTY, the CLI prompts for SDK-planned mutations. In SDK code, pass
{ approval: "yes" },{ approval: "never" }, or an interactive approval callback. --checkreturns local validationsteps[]without wallet creation, faucet request, tier payment, project creation, workspace-link write, upload, gateway plan, or deploy commit. It is not a file-exists check: it normalizes the manifest and verifies every referenced file (see above), so a missingsql_path, function source, or site file fails here withMANIFEST_FILE_MISSINGinstead of at upload. For arun402.jsonapp manifest that declares nobuild.commands, the release slice’s references are verified before any build; a declared build may produce those files, so the check is deferred until after the build runs (the step saysfile_references: "deferred_to_post_build").--print-specperforms the same local validation and prints advanced SDK-nativeReleaseSpecJSON. Use--print-manifestfor reloadable snake_case authoring JSON, relative to the original manifest directory. Unsupported dynamic/secret values fail explicitly.result.preflightcarries nullable target selection, local evidence andgateway_validated: false; see the deploy slice for deferred checks.--plancalls the gateway reviewed-plan mode without upload or commit; it does not provision projects or write workspace links. The response includes a require-ableplan_id,plan_fingerprint, expiration, warnings, diff, andnext_actions[].--require-plan <plan_id>applies only if the reviewed plan still matches; optional--plan-fingerprint <fingerprint>tightens the check.- Run402 Cloud
upcan create/fund a local wallet, ensure a prototype tier by default, create a project from--name, write the workspace link, make sure this principal has a display name, join the project room under it, then apply the manifest. - Identity:
result.identity.principalrecords authenticated principal ID/type/name;result.identity.clientrecords detected client and declared agent name separately. Human and unknown principals are preserved even withRUN402_AGENT_NAME. For agent principals only, that explicit name wins; otherwise a detected client names an unnamed principal. Detection never overwrites an existing name.detection.reason: principal_identity_preservedexplains human/unknown cases. Failed identity lookup reports unavailable and does not rename. Promotion credit uses the principal name, never room presence or client detection. An intentional principal rename usesrun402 whoami --set-name <name>;up --nameis project metadata. - Rehearsal:
result.deploy.rehearsalis{ status: "passed", report, … }when the plan was rehearsed on a contained branch first, or{ status: "skipped", reason: "no_live_release" | "no_migrations" | "migrations_unchanged" | "ci_session_unsupported" | "disabled" | "reviewed_plan" | "unsupported" }(migrations_unchanged: every migration in the manifest is already applied with an identical checksum, so a page-only redeploy that still carriesschema/seedships in seconds instead of spending a branch;ci_session_unsupported: a GitHub Actions deploy, which commits directly because CI sessions cannot rehearse). A failed rehearsal exits non-zero withREHEARSAL_FAILEDand the report; nothing is committed.--no-rehearseskips it. - Git: on a local directory
upscaffolds git on the APP ROOT only (the manifest’s directory):git initwhen it is not a repository, arun402remote (originis never claimed), and a first vault push. A repositoryupjust created is all-untracked by definition, so that first push captures it as a synthetic commit (first_push.captured_dirty: truewith themodified_captured/untracked_captureddisclosure); an existing repository keeps the clean-tree rule and aSNAPSHOT_DIRTY_TREErefusal is reported underfirst_push_error, never overridden. An app root that lies inside ANOTHER repository (a monorepo workspace — how most agent workspaces look) is left untouched by default:result.repois{ status: "skipped", reason: "inside_other_repository", toplevel, next_actions: [{ type: "create_nested_repo", command: "run402 up --nested" }] }, and--humanprints the same line.--nestedgives that app its own nested repository with the encrypted remote:git init -b mainthere, therun402remote and first push from there, and exactly one line (/<relative path>/) appended to the enclosing repository’s local.git/info/exclude— nothing else in the enclosing repository is touched (no.gitignore, index, or submodule), it never shows the app as untracked noise, and the flag is a no-op when the app root is already its own repository. A nested scaffold reportsresult.repo.vault.nested: true,enclosing_toplevel, andexcluded_in_enclosing.projects provisionnever touches git. - Run402 Core
upskips Cloud wallet/tier prerequisites and fails closed if no Core project is selected by--project, workspace link, or manifest. - The SDK derives child idempotency keys for recursive gateway mutations from the root action key; pass
--idempotency-keywhen you need a stable external key. - Deploy warnings use the same review surface as
deploy: prefer repeatable--allow-warning <code>and reserve broad--allow-warningsfor reviewed exceptional cases. - App manifests AND deploy manifests can define
verify.http[]. After apply,upfetches those URLs and records per-check status inresult.app_result.verification.http[](app manifests) orresult.verification.http[]+ aresult.verifyrollup (deploy manifests; a hard verify failure exits 1). Fresh managed-subdomain or custom-domain misses that carry Run402 edge sentinels (x-run402-edgeor JSON codes such asSUBDOMAIN_NOT_CONFIGURED) are treated as propagation, not as permanent verify failure, while the deploy binding is fresh ordeploy resolvereportsedge_propagation.status !== "settled". --propagation-budget-scontrols the wall-clock wait for those fresh edge misses (default 120 seconds).--no-propagation-waitreturns immediately with appstatus: "propagation_pending"andverify.status: "propagation_pending"; the result includespropagation_wait_ms, warnings,next_action, and diagnosticedge_propagation/resolvepayloads when available.--verifywaits after a successful deploy for gateway/edge release coherence and attachesresult.edge_coherenceplusresult.deploy.edge_coherence. It uses the same--propagation-budget-sbudget (default 120 seconds), emitsdeploy.verify.pollprogress events only in explicit stream/human mode, and exits 2 if the report is valid but still not coherent.run402 up verifyreruns the manifest HTTP verification (app or deploy manifest) without resource mutation, upload, deploy, or project creation. It resolves the project from--project,.run402/project.json, the manifest project id, then active project, and is the recovery command to run after propagation settles. A manifest withoutverify.http[]failsVERIFY_CHECKS_REQUIRED.
Output: stdout is the action result, e.g. { "action": "up", "dry_run": false, "target": "cloud", "steps": [...], "result": { "project_id": "prj_...", "manifest_path": "...", "deploy": {...} } }. Stderr carries JSON action-step events unless --quiet.
Protecting a live database (rehearsal is automatic)
Section titled “Protecting a live database (rehearsal is automatic)”When a plan carries migrations and the target project already has a live release, run402 up and run402 deploy rehearse it before committing: a snapshot of the project becomes a contained branch, the candidate plan is applied there, built-in checks run (static 200s, function invocability, migration registry) plus any checks the spec declares, and only a passed report is committed — bound to the report’s required_plan, so the plan that was rehearsed is the plan that ships. A first deploy has no live release to protect, so nothing is rehearsed and the result says rehearsal: { status: "skipped", reason: "no_live_release" }. A redeploy whose migrations are all checksum-identical noops (the plan’s migrations.new is empty) has nothing to rehearse either: rehearsal: { status: "skipped", reason: "migrations_unchanged" } — keep schema/seed in the manifest and iterate on pages in seconds. --no-rehearse opts out for speed.
The gateway decides, in the plan response: rehearsal: { available, rehearse_url, reason: null | "no_migrations" | "no_live_release" | "migrations_unchanged" | "ci_session_unsupported", next_actions? }; a CI (GitHub OIDC) session always gets available: false with ci_session_unsupported and commits directly. The SDK honours that verdict and, against an older gateway that still advertises rehearsal for an all-noop plan, derives migrations_unchanged from the plan’s own migrations: { new: [], noop: [...] } buckets — the only client-side re-derivation, and it can only ever skip work.
Primitives, for when you want the pieces: run402 deploy rehearse <plan_id> rehearses an already-persisted plan without committing (a project with no live release rehearses on an EMPTY branch — never a refusal); run402 snapshots … are manual restore points; run402 branches … are contained, expiring data copies.
Runtime config on every host (/_run402/config.js)
Section titled “Runtime config on every host (/_run402/config.js)”Every host that serves a project’s site — managed subdomain, branch’s host, custom domain — answers GET /_run402/config.js with window.RUN402 = { project_id, api_base, anon_key }; and GET /_run402/config.json with the same object, for the project that host resolves to at request time. A branch’s host answers for the branch; a transferred project answers with its rotated anon key; the HTML never changes. Cache-Control: public, max-age=60. The service key is never served.
<script src="/_run402/config.js"></script><script> const { api_base, anon_key } = window.RUN402; fetch(`${api_base}/rest/v1/items`, { headers: { apikey: anon_key } });</script>/_run402/ is reserved: a route pattern, static alias, public path, or site file under it is rejected at plan time.
Error JSON and Safe Retry
Section titled “Error JSON and Safe Retry”CLI errors: JSON stderr with outer "status": "error". Run402 JSON bodies may merge into the envelope. Branch on code, not message/legacy error.
Canonical fields:
code: stable machine-readable reason, e.g.PROJECT_FROZEN,PAYMENT_REQUIRED,MIGRATION_FAILED,MIGRATE_GATE_ACTIVE. Client-side validation failures (missing flag, malformed JSON) default toBAD_USAGE; specific client-side cases use richer codes e.g.UNKNOWN_FLAG,BAD_FLAG,PROJECT_CREDENTIAL_NOT_FOUND(withdetails.source: "local_cache"),NO_DEPLOYMENT,NO_WALLET,BAD_JSON_FLAG,CONFIRMATION_REQUIRED.retryable: the same request may succeed latersafe_to_retry: repeating the same request should not duplicate or corrupt a mutationmutation_state: one ofnone,not_started,committed,rolled_back,partial,unknowntrace_id: include this when reporting the issuerequest_id: routed/function handle; diagnose withrun402 functions logs <project_id> <name> --request-id <req_...>. Distinct from gatewaytrace_id.details: structured route-specific contextnext_actions: advisory typed suggestions e.g.authenticate,submit_payment,renew_tier,check_usage,retry,resume_deploy,edit_request,edit_migration,create_project,initialize_wallet,deploy,deploy_site_first, orpoll. CLI-resolvable entries carry a literalcommand, e.g.{ "type": "create_project", "command": "run402 projects provision" }. Do not execute route-like suggestions without validating method/path/auth/safety.correlated_platform_incident: present ONLY while an OPEN platform incident correlates with this error’scode—{ id: "inc_…", subsystem, status: "ongoing" | "resolved" }, with apollentry appended tonext_actions. It is a CORRELATION, not an exoneration: the platform states it was degraded when your call failed and lets you judge (an app can still cause its own throttling). Poll the events feed (run402 events list) and checkplatform_statusbefore debugging your own code; when the incident resolves, the matchingplatform_incidentfeed event carries your project’s real failed-invocation count. Absent when no open incident correlates — never a false confession.- Cold-start chain: a fresh agent that knows only
run402 deployis walked to a deployed result by followingnext_actions— no local wallet ->run402 init, no tier ->run402 tier set prototype, no project ->run402 projects provision— each step idempotent, then retry the deploy. You do not need to memorize the sequence; follow what each failure hands back. - Prefer
run402 upwhen starting from a local repo: it plans and runs that same cold-start chain through the SDK instead of executing advisorynext_actions[].commandstrings.
Retry policy:
- Retry same request only when
retryable: trueandsafe_to_retry: true; reuse idempotency key for mutations when available. safe_to_retry: truealone means duplicate-safe, not likely-to-succeed. Lifecycle-gated writes, auth token exchanges, and passkey verifies need the indicated action first.run402 deployalready handles safeBASE_RELEASE_CONFLICTrelease races for omitted/current-base deploy specs, and the transientOPERATION_INTERRUPTED/IMAGE_ENCODE_TIMEOUT/TOO_MANY_ENCODES_QUEUED/EXPORT_CONSISTENCY_UNAVAILABLEcodes the gateway markssafe_to_retry: true: it re-plans, emitsdeploy.retryevents on stderr, and stops after its bounded SDK retry budget. Exhausted deploy retries includeattempts,max_retries, andlast_retry_codein the error envelope. Do not hand-roll this specific retry loop around the CLI unless you intentionally disabled SDK retries upstream.- For mutating 5xx with
safe_to_retry: false, ormutation_stateincommitted|partial|unknown, inspect/poll/reconcile before retry. For deploys preferdeploy events/deploy resumeover duplicate apply. - Lifecycle/payment:
PROJECT_FROZEN/PROJECT_DORMANT/PROJECT_PAST_DUE->projects usage <project_id>ortier set <tier>;PAYMENT_REQUIRED/INSUFFICIENT_FUNDS-> top up the allowance, redeem a voucher, or fund the wallet. NOT_AUTHORIZED(HTTP 403) is an org-owned-control-plane authorization denial, distinct from auth or payment: the wallet authenticated, but its resolved principal lacks the org role or per-project grant the action needs.detailscarriesrequired_role/required_capability/reason. Not retryable without obtaining a covering org membership/role or grant; high-stakes ops (delete, transfer-of-ownership, membership change) require an activeownermembership. The gateway returns 403 even when the project does not exist (so existence isn’t leaked) — re-check the id too. The CLI envelope adds an actionablehint.STEP_UP_REQUIRED(HTTP 403) is a freshness/provenance demand for a high-stakes control-plane op: the session is valid but not fresh enough, or was minted by a read/device-flow path that can’t satisfy a passkey step-up.detailscarriesrequired_amr/max_age_seconds/challenge_url/reason, plusnext_actions[]. The SDK raises a typedStepUpRequiredError(isStepUpRequired()guard). Resolve by signing in again withrun402 login(a fresh passkey) on the same client, then retry. Arun402 login --devicesession and a write approval never satisfy it. Distinct fromNOT_AUTHORIZED(a role/grant gap, not a freshness gap).WRITE_APPROVAL_REQUIRED/WRITE_APPROVAL_SCOPE_MISMATCH/WRITE_APPROVAL_BINDING_MISMATCH(HTTP 403) andWRITE_APPROVAL_SESSION_INVALID(HTTP 401) — a person signed in without a wallet needs a passkey write approval scoped to this(action, target)(the SIWX wallet path never hits this). The SDK raises a typedWriteApprovalRequiredError(isWriteApprovalRequired()guard) carryingcapability,target, and a fully-resolvedapproveCommand/approve_writenext action (e.g.run402 approve --action project.deploy --project prj_x).SCOPE_MISMATCH= the attached approval covers another action or target;BINDING_MISMATCH= it belongs to another sign-in session;SESSION_INVALID= it expired or was revoked. Resolve by running the surfacedrun402 approvecommand (or let an interactiveprovision/deployauto-approve).SESSION_READ_ONLY(HTTP 403) — the sign-in session came fromrun402 login --deviceand can only read. Runrun402 login(the browser passkey sign-in) to write.- Client-side
BAD_JSON_FLAGerrors includedetails.flag(the offending flag, e.g.--abi) anddetails.value_preview(truncated value) so callers know which flag value to fix. - CLI commands reject unknown flags and missing flag values locally with
UNKNOWN_FLAGorBAD_FLAGbefore network work. Numeric and wei-like flags are strict decimal integers: malformed, fractional, negative, and scientific-notation values fail locally instead of being forwarded to the API. - Commands with fixed positional shapes also reject extra positional arguments locally. This includes deploy resume/list/events/release subcommands, functions list/delete, and blob get/rm/sign/diagnose.
Examples:
{ "status": "error", "http": 403, "message": "Project is frozen.", "code": "PROJECT_FROZEN", "category": "lifecycle", "retryable": false, "safe_to_retry": true, "mutation_state": "none", "next_actions": [{ "type": "renew_tier" }, { "type": "check_usage" }] }{ "status": "error", "http": 402, "message": "Payment required.", "code": "PAYMENT_REQUIRED", "category": "payment", "retryable": true, "safe_to_retry": true, "next_actions": [{ "type": "submit_payment" }] }{ "status": "error", "message": "Migration failed.", "code": "MIGRATION_FAILED", "category": "deploy", "retryable": false, "safe_to_retry": true, "mutation_state": "rolled_back", "trace_id": "trc_...", "details": { "operation_id": "op_...", "phase": "migrate" }, "next_actions": [{ "type": "edit_migration" }] }Step 1: Install
Section titled “Step 1: Install”npm install -g run402Step 2: Set Up the Wallet and Funding
Section titled “Step 2: Set Up the Wallet and Funding”run402 init # creates the wallet if absent; checks balance; faucets testnet USDC if zero; shows tier/projectsrun402 wallets current # The active wallet: address, rail, file path, faucet userun402 wallets fund # Get free testnet USDC (Base Sepolia)run402 wallets balance # On-chain USDC (mainnet + testnet) plus the organization's allowanceThe wallet lives at ~/.config/run402/wallet.json (0600). CLI signs x402 / MPP payments automatically; never handle private keys/payment libs manually.
For a self-hosted Run402 Core Gateway, skip Cloud wallet setup and configure the target instead:
run402 init --api-base=http://my-core:4020After that, the same run402 projects provision and run402 deploy commands target Core.
Promo codes (vouchers)
Section titled “Promo codes (vouchers)”If you were given a promo code — a string like R402-K8F3-Q2W9, sometimes
called a voucher or a gift/starter code — redeem it. It adds to your
organization’s allowance, and the allowance pays for the next step (a tier)
with no on-chain payment.
run402 redeem R402-K8F3-Q2W9 # any time: before or after `run402 init`run402 init --voucher R402-K8F3-Q2W9 # same redemption, folded into setup- Order does not matter. Redeeming works as your very first authenticated
call (the organization is created on demand) or long after
run402 init. - Codes are forgiving. Case-insensitive, hyphens optional —
R402-K8F3-Q2W9andr402k8f3q2w9are the same code. Send it verbatim; the server normalizes. - Retrying is safe. A repeat by the same organization returns the original
result with
already_redeemed: trueand never adds the amount twice. - A bad code never blocks setup.
init --voucherwarns, recordsvoucher_errorin its JSON summary, and finishes normally. - Failures:
404 VOUCHER_NOT_FOUND(unknown or malformed),410 VOUCHER_EXPIRED,409 VOUCHER_ALREADY_REDEEMED(a different organization used it),403 PROMO_LIMIT_REACHED(this org is at its lifetime ceiling for that code’s issuer; a code from another issuer has its own).
Minting codes is not an agent operation — it needs an issuer key no tenant holds.
Step 3: Set a Tier
Section titled “Step 3: Set a Tier”run402 tier set prototype # FREE on testnet — faucet USDC verifies your x402 setup ($0 real money); free tier, no leaserun402 tier set hobby # $5 for 30 days (real money)run402 tier set team # $20 for 30 days (real money)The allowance pays first. A promo code (run402 redeem <code>) or a top-up adds to the
organization’s allowance, and tier set settles from it ahead of the payment paywall: no 402,
no signed authorization, no USDC in the wallet, receipt paid_with: "allowance" with
allowance_used_usd_micros and allowance_remaining_usd_micros. Only an allowance that falls
short goes to x402 / MPP, and that X402_INSUFFICIENT_FUNDS error carries details.allowance
(allowance_usd_micros, price_usd_micros, shortfall_usd_micros) with redeem_voucher /
top_up next actions.
Tier is organization-scoped. A start/renew/upgrade applies to every project in the org; api_calls / storage_bytes quota is org-pooled across linked wallets (billing link-wallet). Quota errors include details.scope: "organization" | "project" (project = orphan fallback after org purge before cascade). tier set refetches status and returns status_after with refreshed pool usage.
Retry-safety: tier set and projects provision accept --idempotency-key <key> so a retried start/renew/create collapses onto one charge instead of double-billing. provision auto-derives the key from --name when omitted (re-running provision --name X returns the same project); tier set is caller-supplied only — use a fresh key for a deliberate second renewal.
Server action detection (reported as action: start | renew | upgrade):
- No tier or expired -> start
- Same tier, active -> renew (extends from current expiry)
- Higher tier -> upgrade (prorated refund to the allowance)
- Lower tier, active -> downgrade (prorated refund if usage fits)
run402 tier statustier status pool_usage sums api_calls and storage_bytes across every project on the organization (across every linked wallet), not the requesting wallet’s projects.
With active tier: unlimited projects/sites/forks/functions/secrets/storage subject to org-pooled api_calls/storage_bytes; only image generation is per-call ($0.03/image).