---
title: "Commit a v2 deploy plan"
method: POST
path: "/apply/v1/plans/{plan_id}/commit"
tags: ["Apply"]
---

# Commit a v2 deploy plan

`POST /apply/v1/plans/{plan_id}/commit`

## Request

- Base URL: `https://api.run402.com`
- URL: `https://api.run402.com/apply/v1/plans/{plan_id}/commit`
- Auth: API key in header `SIGN-IN-WITH-X`

Drive the deploy state machine: validate → stage → migrate-gate → migrate → schema-settle → activate → ready. Optional required_plan repeats reviewed-plan verification before content mutation, migration execution, function update, activation, or release pointer changes. Returns the operation snapshot promptly, including subdomain_bindings freshness for managed hosts; the SDK polls /operations/:operation_id if non-terminal. Idempotent per plan, even mid-run: only the commit that claims the operation drives it; a commit of the same plan while it runs answers `status: "running"` with a `poll` action and re-runs nothing, a finished one replays its stored result, a paused one resumes. Safe to re-send after a lost response (502/503/504, dropped connection). Idempotency-Key middleware applied.

## Path parameters

- `plan_id` string, required

## Headers

- `SIGN-IN-WITH-X` string, required

## Request body

- DeployCommitRequest
  - `required_plan` ReviewedPlanRequirement
    - `plan_id` string, required: Reviewed plan id returned by POST /apply/v1/plans with mode=reviewed_plan.
    - `plan_fingerprint` string: Optional reviewed fingerprint returned with the reviewed plan. Send it when available for an exact client-visible identity check.
  - `vault` VaultCommitInput: Commit-time vault block. Supply EITHER `activation_token_id` (the vault-gated path) OR `allow_unvaulted` + `override_reason` (the audited override). The override needs `gitvault.override_unvaulted` — owner + step-up, or a project grant carrying that power explicitly; deploy authority alone is refused `403 OVERRIDE_NOT_AUTHORIZED`.
    - `activation_token_id` string: Minted for THIS operation by `POST /vaults/v1/{vault_id}/activation-tokens`; consumed exactly once inside the activation transaction.
    - `allow_unvaulted` boolean: Activate without a consumed token. Journaled in full and stamps a doctor-visible advisory that persists until the capture is reported at `POST /vaults/v1/{vault_id}/override-completions`.
    - `override_reason` string: Required with `allow_unvaulted`; journaled verbatim.

## Response `200`

Operation snapshot. Status: ready | running | schema_settling | activation_pending | needs_repair.

- DeployCommitResponse
  - `static_continuity` StaticContinuity: Automatic one-hour origin availability for previously direct-public non-HTML paths absent from the active release. Cached copies may outlive the deadline. Plans omit the deadline until supersession.
    - `mode` 'absent_public_paths', required
    - `origin_retention_seconds` 3600, required
    - `scope` 'previously_public_non_html', required
    - `source_release_id` string, nullable, required
    - `source_release_generation` integer, nullable, required
    - `retained_path_count` integer, required
    - `origin_available_until` string, date-time, nullable: Omitted for candidate supersessions; null for an active release.
  - `operation_id` string, required
  - `release_id` string, nullable, required
  - `status` 'planning' | 'uploading' | 'committing' | 'staging' | 'gating' | 'migrating' | 'schema_settling' | 'activating' | 'activation_pending' | 'needs_repair' | 'ready' | 'failed' | 'rolled_back', required
  - `actor` OperationActorSnapshot
    - `schema_version` 1, required
    - `principal` PrincipalSnapshot, required
      - `principal_id` string, required
      - `principal_type` string, required
      - `display_name_at_capture` string, nullable, required
      - `linked_identities_at_capture` LinkedIdentitySnapshot[], required
        - `identity_link_id` string, required
        - `kind` string, required
        - `public_subject` string, required
        - `display_subject` string, required
        - `verified_at` string, date-time, required
        - `status_at_capture` 'active', required
    - `authenticator` AuthenticatorSnapshot, required
      - `authenticator_id` string, required
      - `kind` string, required
      - `public_subject` string, required
    - `authority` AuthoritySnapshot, required: Discriminated immutable action-time authority: organization_membership, project_grant, grant_key, ci, system, legacy, or a preserved future kind. A link is never an authority kind.
      - `kind` string, required
      - `org_id` string, nullable
      - `project_id` string, nullable
      - `membership_id` string, nullable
      - `grant_id` string, nullable
      - `grant_key_id` string, nullable
      - `credential_id` string, nullable
      - `role` string, nullable
      - `scope` string[]
      - `reason_code` string, nullable
    - `captured_at` string, date-time, required
  - `urls` object: `site` is the project's public URL, present when the project owns at least one subdomain. `console` is the project's page in the console (`https://console.run402.com/orgs/{org_id}/projects/{project_id}`), present exactly when `site` is present; a function-only project with no site carries no `console` key either.
  - `edge` EdgeBlock
    - `checked_at` string, date-time: When the activation-time edge summary was computed.
    - `state` 'converging' | 'coherent' | 'unknown' | 'not_applicable', required
    - `expected_max_lag_seconds` integer, nullable
    - `pointer_updates` EdgePointerUpdates, required: Map keyed by edge pointer target. Missing keys mean no outbox row exists for that target.
    - `verify_url` string, nullable
  - `error` DeployErrorEnvelope
    - `code` string, required
    - `phase` string
    - `resource` string
    - `message` string, required
    - `retryable` boolean
    - `operation_id` string
    - `plan_id` string
    - `fix` object
    - `logs` string[]
  - `next_actions` NextAction[]: On `status: "ready"` this includes a `poll` entry into the project events feed, and, when `urls.site` is present and the project has no recorded promotion consent yet, one `hand_to_member` entry: show the human `urls.site` and `urls.console`, tell them Run402 would like to promote what they built on @run402com for free, and ask yes or no. Its `body` is a ready-to-send `POST /feedback/v1` template (`project_id`, `message: "promote: yes"`, and the optional `handle` and `return_address` the agent asks the human for); its `credited_as` is the authenticated principal's display name or null, with `credit_source: principal.display_name` and attribution guidance; detected client and room presence are not credit authority. Absent once a `feedback_sent` fact with `promotion_consent: true` exists for the project. When Run402 holds no verified email for the project's organization, a `register_contact` entry (`POST /agent/v1/contact`) follows it.
    - `type` string, required: Advisory machine-readable action type such as retry, authenticate, submit_payment, fund_wallet, renew_tier, check_usage, resume_deploy, edit_request, edit_migration, contact_support, upgrade_client, poll (poll the project events feed — appended when the envelope carries a correlated_platform_incident), or hand_to_member (on a commit/promote response that just activated a public site: show the human urls.site and urls.console, relay Run402's offer to promote the build on @run402com, and ask yes or no — see `credited_as` below).
    - `method` string
    - `path` string
    - `auth` string
    - `why` string
    - `credited_as` string, nullable: `hand_to_member` only. The caller's own live presence name in the project's default room, else that room's most recently active live presence, else `null` — in which case `why` tells the agent that joining the room with a name (`run402 rooms join --name ...`) is how the promotion credits it.
    - `body` object: `hand_to_member` only. A ready-to-send `POST /feedback/v1` template: `{ project_id, message: "promote: yes", handle? }`.
  - `subdomain_bindings` SubdomainBindingFreshness[], required: Managed subdomains serving this project, including claim time and confirmed KVS source-write time.
    - `host` string, required
    - `claimed_at` string, date-time, required
    - `kvs_synced_at` string, date-time, nullable, required: When the edge-store source write was confirmed. Null means the write has not been confirmed yet.
  - `restore_point` CommitRestorePoint
    - `snapshot_id` string, required
    - `restore_url` string, required
    - `command` string, required
  - `snapshot_skipped_reason` string, nullable: Present when pre-migration snapshotting was intentionally skipped or downgraded, for example because the project exceeded the snapshot size threshold.
  - `vault` VaultResult: Vault provenance for one apply operation — ABSENT for a vaultless project's operation, so pre-vault responses are unchanged. Carried on the commit response, on an operation snapshot, and on the operations list.
    - `policy` 'required' | 'grandfathered', nullable
    - `outcome` 'DEPLOYED_AND_VAULTED' | 'DEPLOY_BLOCKED_PUSH_FAILED' | 'DEPLOY_FAILED_VAULTED' | 'DEPLOY_FAILED_UNVAULTED' | 'DEPLOYED_UNVAULTED_OVERRIDE', nullable: The protocol's CLOSED five-outcome deploy enum, derived from the operation row; `null` while the operation is still in flight. `DEPLOYED_AND_VAULTED` — activated with a consumed activation token: app live and source captured. `DEPLOY_BLOCKED_PUSH_FAILED` — refused at the gate (no consumable token and no authorized override); nothing activated. `DEPLOY_FAILED_VAULTED` — the push succeeded, so the source IS captured, but the deploy failed afterwards. `DEPLOY_FAILED_UNVAULTED` — failed under an override, so nothing was captured either. `DEPLOYED_UNVAULTED_OVERRIDE` — activated through the audited override; the advisory persists until the capture is reported.
    - `vault_generation` string, nullable: 16 lowercase hex — the admitted generation this deploy activated from.
    - `capture_id` string, nullable
    - `activation_token_id` string, nullable
    - `activation_token_consumed` boolean
    - `admission_receipt` object, nullable: The consumed `capture_receipt`, exactly as journaled.
    - `policy_version_at_activation` integer, nullable
    - `unvaulted_override` boolean
    - `unvaulted_override_reason` string, nullable
    - `unvaulted_override_actor_principal_id` string, nullable
    - `unvaulted_override_journal_state` 'open' | 'completed', nullable: `open` until the matching capture receipt is accepted at `POST /vaults/v1/{vault_id}/override-completions`.

## Other responses

- `403`: Wallet does not own this plan's project, OR project is past_due/frozen/dormant (lifecycleGate). Envelope `code` distinguishes — `FORBIDDEN` for ownership, `PROJECT_PAST_DUE` / `PROJECT_FROZEN` / `PROJECT_DORMANT` for lifecycle. On a `vault_policy: required` project a commit carrying `vault.allow_unvaulted` without `gitvault.override_unvaulted` (owner + step-up, or an explicit grant) is `OVERRIDE_NOT_AUTHORIZED`.
- `404`: Plan not found
- `409`: Reviewed-plan mismatch, expired reviewed plan, already committed plan, base drift, or unsupported planner semantics. No release mutation has run. On a `vault_policy: required` project: `VAULT_CLIENT_UPGRADE_REQUIRED` when the client spoke no vault at all (no plan-time capture and no commit `vault` block) — its two next_actions are `upgrade_client` and `grandfather_policy`; `DEPLOY_BLOCKED_PUSH_FAILED` when the gate found no consumable token; `AUTHORIZATION_EPOCH_STALE` when the token's epoch is no longer the vault's installed one; `REPAIR_FENCE_PENDING` while a repair admission is unresolved. All four carry `details.outcome` from the closed five-outcome enum.
- `422`: Run402DeployError envelope (MIGRATION_FAILED, MIGRATION_CHECKSUM_MISMATCH, etc.)

---

[API](https://skmtc.dev/run402/apis/run402-api.md) · [All operations](https://skmtc.dev/run402/apis/run402-api/llms.txt) · [OpenAPI document](https://skmtc.dev/run402/apis/run402-api/revisions/893607557f50?raw)
