---
title: "Plan or confirm a project snapshot restore"
method: POST
path: "/projects/v1/{project_id}/snapshots/{snapshot_id}/restore"
tags: ["Project Snapshots"]
---

# Plan or confirm a project snapshot restore

`POST /projects/v1/{project_id}/snapshots/{snapshot_id}/restore`

## Request

- Base URL: `https://api.run402.com`
- URL: `https://api.run402.com/projects/v1/{project_id}/snapshots/{snapshot_id}/restore`
- Auth: one of:
  - API key in header `SIGN-IN-WITH-X`
  - HTTP bearer
  - HTTP bearer

Two-step restore. A request without `confirm` returns `restore_plan` with data-loss/auth/release details and a short-lived confirm token. A request with `confirm` materializes the snapshot into a spare slot, captures a pre_restore undo snapshot, atomically flips the project slot, optionally restores auth identities when `include:["auth"]`, and returns next actions. Also accepts the project's own service key (`serviceKey`), scoped to that project: a key for another project is refused 403 for every id, and a service key plus a SIWX header is refused 400 `AUTH_CREDENTIALS_AMBIGUOUS`. `release` and `include` must match between plan and confirm. A service key restores app data only: `include:["auth"]` is refused 403 SNAPSHOT_AUTH_RESTORE_REQUIRES_PRINCIPAL. `wait:false` answers 202 with a handle to poll.

## Path parameters

- `project_id` string, required: Run402 project ID. Public IDs are not UUIDs.
- `snapshot_id` string, required

## Request body

- ProjectSnapshotRestoreRequest
  - `confirm` string: Confirm token returned in restore_plan.confirm.token. Omit to receive the restore plan.
  - `include` string[]: Include auth identity replacement on confirmed restore. Sessions/tokens are never restored.
  - `release` 'keep' | 'snapshot': `snapshot` re-activates the capture-time release in the restore's flip transaction (live release pointer, static site, routes, subdomains), then re-deploys every function whose code differs to its capture-time source.
  - `wait` boolean: false on a confirmed restore answers 202 with a pollable handle instead of waiting.

## Response `200`

Restore plan or confirmed restore result.

- union
  - ProjectSnapshotRestorePlanEnvelope
    - `restore_plan` ProjectSnapshotRestorePlan, required
      - `snapshot_id` string, required
      - `project_id` string, required: Run402 project ID. Public IDs are not UUIDs.
      - `snapshot_at` string, date-time, required
      - `data_loss_statement` string, required
      - `auth` object, required
        - `mode` 'not_restored' | 'restore_on_confirm', required
        - `users` integer, required
        - `passkeys` integer, required
        - `message` string, required
      - `release` object, required
        - `mode` 'keep' | 'snapshot', required: `snapshot` re-activates the capture-time release in the restore's flip transaction (live release pointer, static site, routes, subdomains), then re-deploys every function whose code differs to its capture-time source.
        - `snapshot_live_release_id` string, nullable, required
        - `current_live_release_id` string, nullable, required
        - `restorable` boolean, required: Whether release:"snapshot" can re-activate the capture-time release.
        - `reason` 'restorable' | 'already_live' | 'no_capture_release' | 'release_not_found' | 'release_not_promotable' | 'deployment_unservable' | 'no_live_release', required
        - `warnings` object[], required: FUNCTION_VERSION_MISMATCH names functions whose capture-time source is no longer stored; they keep their current code.
        - `message` string, required
        - `functions_to_redeploy` string[], required: Functions re-deployed to the capture-time code right after the flip.
      - `target` object, required
        - `current_schema_slot` string
        - `behavior` 'offline_materialize_then_atomic_flip'
      - `confirm` object, required
        - `token` string, required
        - `expires_at` string, date-time, required
      - `next_actions` NextAction[], required
        - `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? }`.
  - ProjectSnapshotRestoreResult
    - `operation_id` string, required
    - `project_id` string, required: Run402 project ID. Public IDs are not UUIDs.
    - `snapshot_id` string, required
    - `pre_restore_snapshot_id` string, required
    - `old_schema_slot` string, required
    - `new_schema_slot` string, required
    - `migration_registry_rows` integer, required
    - `invalidated_plan_count` integer, required: Open uncommitted apply plans expired atomically by the restore flip.
    - `message` string, required: Restore narrative noting that open plans were invalidated and must be re-created.
    - `status` 'ready', required
    - `next_actions` NextAction[], required
      - `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? }`.
    - `restore_id` string, required: Same value as operation_id.
    - `release_mode` 'keep' | 'snapshot', required: `snapshot` re-activates the capture-time release in the restore's flip transaction (live release pointer, static site, routes, subdomains), then re-deploys every function whose code differs to its capture-time source.
    - `live_release_id` string, nullable, required: The project's live release after the restore.
    - `edge` object: Edge propagation for a re-activated release; absent when the release was kept.
    - `functions` object: Functions re-deployed to the capture-time code (release:"snapshot" only).
      - `redeployed` string[], required: Functions now running the target release's code.
      - `failed` object[], required: Functions that keep the code they had before (also a FUNCTION_REDEPLOY_FAILED warning).
        - `name` string, required
        - `code` 'FUNCTION_SOURCE_NOT_FOUND' | 'FUNCTION_REDEPLOY_FAILED', required
        - `message` string, required
      - `pending` string[], required: Still being re-deployed; the status read can say ready while this is non-empty.
    - `warnings` object[]: Non-blocking issues found after the flip, e.g. FUNCTION_REDEPLOY_FAILED.

## Other responses

- `202`: Confirmed with wait:false; the restore runs in the background. Poll the restore status route.
- `403`: SNAPSHOT_AUTH_RESTORE_REQUIRES_PRINCIPAL, or not authorized.
- `409`: STALE_RESTORE_CONFIRMATION (slot, live release, release mode or include drift; expired token), SNAPSHOT_RESTORE_IN_PROGRESS (another restore is running; next_actions polls it), RESTORE_INTERRUPTED, or a materialization conflict.
- `422`: SNAPSHOT_RELEASE_NOT_RESTORABLE: release:"snapshot" against a capture-time release that can no longer be activated. Nothing was mutated.

---

[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)
