---
title: "Capture a deployed function into a signed snapshot blob"
method: POST
path: "/snapshot/save"
tags: ["snapshot"]
---

# Capture a deployed function into a signed snapshot blob

`POST /snapshot/save`

Captures the deployed Wasm module bytes of the named function into
a zstd-compressed, HMAC-SHA256-signed snapshot blob and returns it
base64-encoded. The blob round-trips through `POST
/snapshot/restore`.

Authorization: protected stack (bearer auth + tenant scope). The
handler runs `authorize_tenant` against the bearer token's scope
(an out-of-scope token gets `403 tenant_scope_denied` before any
lookup) and then a per-resource owner check against the function's
`tenant_id` (a wildcard-scoped caller from another tenant cannot
snapshot tenant A's function).

Signing uses `TENSOR_WASM_API_SNAPSHOT_HMAC_KEY`; when it is unset
the route returns `503 snapshot_signing_not_configured` rather than
emitting an unsigned blob.

NOTE: this captures the function's *deployed module bytes* — the
working save/restore-of-bytes layer with end-to-end HMAC signing.
Capturing a *live running instance's* linear / GPU memory needs an
executor capture hook that does not exist yet; requesting that
capability surfaces `501 not_implemented`. See
crates/tensor-wasm-api/src/routes.rs.

## Headers

- `X-TensorWasm-Tenant` integer

## Request body

- SnapshotSaveRequest — Body of `POST /snapshot/save`. Mirrors the `tensor_wasm_api::routes::SnapshotSaveRequest` Rust struct.
  - `function_id` string, uuid, required — Server-assigned function identifier (UUIDv4).

## Response `200`

Snapshot captured and signed; blob returned base64.

- SnapshotSaveResponse — Body of a successful `POST /snapshot/save`. Carries the full HMAC-SHA256-signed snapshot blob, base64-encoded.
  - `function_id` string, uuid, required — Server-assigned function identifier (UUIDv4).
  - `snapshot_b64` string, required — Base64-encoded signed snapshot blob (standard alphabet, padded). The exact bytes to hand back to `POST /snapshot/restore`; the HMAC trailer is part of these bytes.
  - `signed` boolean, required — Whether the blob carries an HMAC signature. Always `true` on this path — the route is only reachable with a key configured.

## Other responses

- `400` — Validation failure
- `401` — Missing or unrecognised bearer token
- `403` — Bearer token is not scoped to the requested tenant
- `404` — Function or job id is unknown
- `413` — Request body exceeded the 64 MiB cap enforced by axum's `DefaultBodyLimit::max`. Often rendered as a bare 413 with no body when the limit is hit during streaming.
- `429` — Per-token QPS + burst exceeded. The `Retry-After` header carries the wait in integer seconds (RFC 9110 §10.2.3).
- `500` — Underlying wasmtime or host failure
- `501` — `not_implemented` — the requested capability needs executor support that is not wired yet (full live-instance snapshot / restore of linear + GPU memory). The HMAC envelope layer of the `/snapshot/*` routes is fully wired; only the live-instance reconstitution returns this. Clients should NOT retry.
- `503` — `snapshot_signing_not_configured` — the `/snapshot/*` routes are mounted but `TENSOR_WASM_API_SNAPSHOT_HMAC_KEY` is unset, so the gateway has no key to sign or verify snapshot blobs. The failure mode is configuration, not load; a client should NOT retry but surface the error to an operator who can set the key.

---

[API](https://skmtc.dev/craton-co/apis/tensorwasm-http-api.md) · [All operations](https://skmtc.dev/craton-co/apis/tensorwasm-http-api/llms.txt) · [OpenAPI document](https://skmtc-service-production.skmtc.workers.dev/v1/apis/craton-co/tensorwasm-http-api/revisions/d363e13d8640/schema)
