Portable project archives
Portable project archives
Section titled “Portable project archives”Portable archives let an agent export the supported Run402 Core runtime slice of a Cloud project, verify it locally, and import it into a new local Core project. This is the vendor-lock-in trust claim: Cloud is the easiest place to start, not the only place the supported application can run. It is separate from allowance/spend-cap financial-risk controls.
From MCP the happy path is one run snippet per step (r.archives on the Node SDK), or the CLI’s run402 archives create|download|inspect|verify|import when the archive should land in a directory the person chooses:
const exported = await r.archives.export("prj_…", { scope: "portable-runtime-v1", auth: "stubs", consistency: "pause-writes" });exported.created.archive_id- Export with
r.archives.export(projectId, { scope, auth, consistency }); it waits for readiness and downloads. - Inspect with
r.archives.inspect(archivePath). - Verify with
r.archives.verify(archivePath). - Fill the required secret values from the archive’s
secrets/required.env.templateor therequired_secretslist. - Import with
r.archives.importToCore({ archivePath, name, envFile })(orsecretValues), withrequireRunnable: trueto insist on a runnable result.
Results carry the same agent fields as the CLI: code, severity, resource_type, resource_id, message, next_action, retryable, and safe context. Verification is offline and checks integrity and compatibility only; archives remain untrusted input. Import verifies before mutation, creates a new Core project only, and never imports secret values, auth credentials, logs, billing/allowance state, or managed Cloud operations from Cloud export.
Project credentials
Section titled “Project credentials”When up (or run402 projects provision) creates a project, two keys are saved to the local key cache and reused by every later call; the SDK never hands them to a run snippet:
anon_key— read-only by default; safe in browser HTML. RLS policies apply.service_key— server-side admin. Never embed in browser code. CORS is intentionally open for x402 clients, so a leaked service_key is exploitable from any origin. Use only inside functions; the SDK attaches it for you.
Neither key expires. await r.credentials.projectKeys.status(projectId) reports what the cache holds without revealing it; await r.projects.use(projectId) sets the active project. Exporting a key is run402 credentials project-keys export --project <project_id> --reveal, in the CLI only.
Error envelopes and safe retry
Section titled “Error envelopes and safe retry”Run402 JSON errors carry a canonical envelope. Branch on code, not English message.
Important fields:
code— stable machine-readable reason:PROJECT_FROZEN,PAYMENT_REQUIRED,MIGRATION_FAILED,MIGRATE_GATE_ACTIVE,RATE_LIMITED,INSUFFICIENT_FUNDS,CI_ROUTE_SCOPE_DENIEDretryable— the same request may succeed latersafe_to_retry— repeating the same request will not duplicate or corrupt a mutationmutation_state—none/not_started/committed/rolled_back/partial/unknowntrace_id— include this when reporting an issuerequest_id— routed/function failure handle.await r.functions.logsByRequestId(projectId, requestId)in arunsnippet reads the matching log lines across the project’s functions; it is distinct from gatewaytrace_id.details— structured route-specific contextnext_actions—authenticate,submit_payment,renew_tier,check_usage,retry,resume_deploy,edit_request,edit_migration,pollcorrelated_platform_incident— present ONLY while an OPEN platform incident correlates with this error’scode:{ id: "inc_…", subsystem, status: "ongoing" | "resolved" }, with apollappended tonext_actions. A CORRELATION, not an exoneration — the platform states it was degraded when the call failed and leaves the judgment to you (an app can still cause its own throttling). Poll the events feed (await r.events.list(projectId)) and checkplatform_statusbefore debugging your own code; the follow-upplatform_incidentfeed event carries the project’s real failed-invocation count once the incident resolves. Absent when no open incident correlates.
Safe retry policy:
retryable: true+safe_to_retry: true→ retry, ideally with the same idempotency key for mutationssafe_to_retry: truealone is not a retry signal; it means duplicate-safe, not likely-to-succeed. Lifecycle-gated writes, auth token exchanges, and passkey verifies need the indicated action before retrying.- The
deploytool uses SDKapply, which already re-plans and retries safeBASE_RELEASE_CONFLICTraces for omitted/current-base specs. A handled retry appears as adeploy.retryprogress event; exhausted retries includeattempts,max_retries, andlast_retry_code. Static activation/config failures reported fromactivation_pendingthrow promptly with gateway metadata instead of polling until timeout. Do not hand-roll this specific deploy race loop around MCP calls. - 5xx with
safe_to_retry: false, ormutation_stateiscommitted/partial/unknown→ inspect or poll state before retrying. For deploys, resume withawait r.project(projectId).apply.resume(operationId)in arunsnippet. - Lifecycle / payment errors → take the action, don’t blind-retry.
PROJECT_FROZEN→await r.tier.set("<tier>");PAYMENT_REQUIRED→ submit payment, then retry. - A snippet’s
runresult reports an SDK error with the SDK’s owncode,message, andnext_actions, and marks the failing call incalls[]; branch on those, exactly as above. - Every tool’s error result carries the same fields under
structuredContent.error(see the Structured results section of the reference); a host reads them there instead of parsing text.