---
title: "Publish the draft as the template’s next version (save-as-template from the draft)"
method: POST
path: "/v1/templates/{slug}/draft/publish"
tags: ["Template drafts"]
---

# Publish the draft as the template’s next version (save-as-template from the draft)

`POST /v1/templates/{slug}/draft/publish`

## Request

- Base URL: `https://api.shardflux.dev`
- URL: `https://api.shardflux.dev/v1/templates/{slug}/draft/publish`
- Auth: HTTP bearer

Builds one new org layer holding every change since the draft base, from `state_id` or the draft’s current state (a fresh capture when running). `settings` (TemplateSettingsInput): each given field replaces that field of the draft base’s settings, each absent one is carried forward (settings.defaults and defaults together: 422 invalid_settings). Refused with 409 draft_stale (details latest_version, draft_base_version) when the template has a version newer than the draft base that this draft did not produce, and 409 build_in_progress while a build from another source is unfinished. Immutable paths: the new version declares the template’s open version’s immutable paths (else the draft base’s) with the content the draft holds there; 422 immutable_paths_unsupported_base when the draft’s base has no guest agent with immutable_paths.v1 (details.base). 202 SaveAsTemplateResponse. Supports Idempotency-Key.

## Path parameters

- `slug` string, required

## Request body

- PublishDraftBody
  - `state_id` string, uuid: UUIDv7, lowercase canonical form.
  - `description` string
  - `defaults` object: Defaults of the new version (omitted fields: persistent lifetime, platform idle timeout, no limits).
    - `lifetime` 'persistent' | 'session': persistent: kept until deleted (default). session: discarded when the session ends (close(), idle timeout or draft discard;).
    - `idle_timeout_seconds` integer, nullable: Sessions: idle timeout (60-86400 s); null = the platform default 600.
    - `limits` object, nullable: Ceilings of every admission of a workspace of the version (clamped, never refused;).
      - `cpu_millis_ceiling` integer, required
      - `memory_mib_ceiling` integer, required
      - `disk_gib` integer, required
    - `egress` object, nullable: Workspace network ceiling: internet = no ceiling (stored as null), allowlist = only allow_hosts (TCP, any port), none = no egress.
      - `mode` 'internet' | 'allowlist' | 'none', required
      - `allow_hosts` string[]
    - `agent_tools` unknown
  - `settings` TemplateSettingsInput: What a workspace of the version gets when it opens (). Omitted fields are empty (recipes do not carry settings forward).
    - `env` object: Template environment for every exec, PTY, start command and service (below text inputs and the call's env).
    - `inputs` object: Open-time inputs. text: a value passed to open({inputs}); secret: a stored secret of the same name, bound to the workspace.
    - `start` object[]: Start commands: create = on a new workspace layer, boot = every cold boot, resume = every memory restore.
      - `name` string, required
      - `when` 'create' | 'boot' | 'resume', required
      - `run` string, required: Shell script run by /bin/bash -euo pipefail -c; 1..65536 bytes, no NUL.
      - `user` string, nullable: A user name or numeric uid of the template (the builder's userRE without a group).
      - `cwd` string, nullable: Absolute, clean (no '.', '..', '//' or trailing '/'), at most 1024 bytes.
      - `timeout_seconds` integer: Default: 300.
    - `services` object: Processes kept running by the guest; ready before open() returns.
    - `defaults` object: Manifest defaults (). agent_tools stays reserved (null).
      - `lifetime` 'persistent' | 'session'
      - `idle_timeout_seconds` integer, nullable
      - `limits` object, nullable: Ceilings enforced at admission (clamped).
        - `cpu_millis_ceiling` integer, required
        - `memory_mib_ceiling` integer, required
        - `disk_gib` integer, required
      - `egress` object, nullable: Workspace network ceiling: internet = no ceiling (stored as null), allowlist = only allow_hosts (TCP, any port), none = no egress.
        - `mode` 'internet' | 'allowlist' | 'none', required
        - `allow_hosts` string[]
      - `agent_tools` unknown
  - `auto_publish` boolean
  - `acknowledged_scan_findings` string[]: Up to 200 absolute paths the credential scan may report without failing the build (recorded in the manifest).

## Response `202`

operation: the capture (layer_snapshot) of a running workspace, null otherwise. build: poll GET …/template-builds/{id} until registration.state is registered.

- SaveAsTemplateResponse: operation: the capture (layer_snapshot) of a running workspace, null otherwise. build: poll GET …/template-builds/{id} until registration.state is registered.
  - `operation` Operation, required
    - `id` string, uuid, required: UUIDv7, lowercase canonical form.
    - `kind` 'open' | 'suspend' | 'resume' | 'fork' | 'snapshot' | 'restore' | 'delete' | 'move' | 'resize' | 'upgrade' | 'volume_create' | 'volume_attach' | 'volume_detach' | 'volume_delete' | 'reset' | 'layer_snapshot', required
    - `state` 'queued' | 'capacity_pending' | 'running' | 'succeeded' | 'failed' | 'canceled', required
    - `workspace_id` string, uuid, nullable, required: UUIDv7, lowercase canonical form.
    - `volume_id` string, uuid, nullable: UUIDv7, lowercase canonical form.
    - `created_at` string, date-time, required: RFC 3339 UTC timestamp with Z.
    - `updated_at` string, date-time, required: RFC 3339 UTC timestamp with Z.
    - `started_at` string, date-time, nullable: RFC 3339 UTC timestamp with Z.
    - `completed_at` string, date-time, nullable: RFC 3339 UTC timestamp with Z.
    - `state_reason` string, nullable: Closed reason code while waiting (e.g. capacity_pending: no_ready_host).
    - `input` object
    - `error` object
    - `result` object
      - `durable` boolean: suspend and fork: whether the capture is in durable storage. false while `durability.state` is pending (the suspend completed on its host) or lost; true once it committed. Absent on other kinds.
      - `suspend_path` string: suspend: `local_commit` when the suspend completed on its host and the durable copy followed in the background.
      - `durability` object
        - `state` 'pending' | 'durable' | 'lost', required: pending: sealed on its host, the durable copy is being written; durable: the copy committed; lost: the copy could not be made and the previous recovery point stays current.
        - `checkpoint_id` string: The checkpoint being made durable.
        - `generation_id` string: Its generation on the host.
        - `local_commit_at` string: RFC 3339: when the suspend (or fork) completed on its host.
        - `durable_by` string: RFC 3339: when the durable copy is expected to have committed (local_commit_at + the deadline).
        - `durable_at` string: RFC 3339 (state durable): when the durable copy committed.
        - `local_commit_to_durable_ms` number: State durable: local_commit_at to durable_at, in milliseconds.
        - `overdue_at` string: RFC 3339: set once durable_by passed while the copy was still pending; it keeps retrying.
        - `reason` string: State lost: why, e.g. host_lost.
        - `code` string: State lost: the error code of the failed copy.
        - `recovery_point_checkpoint_id` string, nullable: State lost: the durable checkpoint that stays the recovery point (null when there is none).
      - `lost_suspend` object
        - `checkpoint_id` string, required: The suspend checkpoint whose durable copy was lost.
        - `generation_id` string: Its generation on the host.
        - `reason` string: Why it was lost, e.g. host_lost.
        - `suspended_at` string: RFC 3339: when that suspend captured the workspace.
        - `restored_checkpoint_id` string: The earlier durable checkpoint this resume restored.
        - `state_as_of` string: RFC 3339: when the restored checkpoint committed; the workspace state is as of then.
      - `host_lost` object
        - `detected_at` string, required: RFC 3339: when the host failure was detected; the workspace was suspended then.
        - `restored_from` 'disk' | 'checkpoint': resume and open: disk when the workspace booted from its disk (files kept, processes and memory not); checkpoint when it was restored from its newest checkpoint (see state_as_of). Absent on a suspend.
        - `restored_checkpoint_id` string: restored_from checkpoint: the checkpoint this resume restored.
        - `state_as_of` string: restored_from checkpoint, RFC 3339: when that checkpoint committed; the workspace state is as of then.
    - `progress` SuspendProgress
      - `operation_id` string, uuid, required: UUIDv7, lowercase canonical form.
      - `state` 'queued' | 'capacity_pending' | 'running' | 'succeeded' | 'failed' | 'canceled', required
      - `workspace_state` 'creating' | 'starting' | 'running' | 'suspending' | 'suspended' | 'resuming' | 'forking' | 'stopping' | 'failed' | 'deleting' | 'deleted', required
      - `phase` string, required: Actual snapshot preparation/capture phase, or an explicitly unavailable control status.
      - `cancelable` boolean, required: True only while the host can acknowledge cancellation before its capture cut.
      - `supported` boolean, required
      - `safe_action` 'wait_for_terminal' | 'resume' | 'retry_tools' | 'inspect_failure', required
      - `phase_started_at` string, date-time: RFC 3339 UTC timestamp with Z.
      - `deadline_at` string, date-time: RFC 3339 UTC timestamp with Z.
      - `accepted` boolean
  - `build` TemplateBuild, required
    - `id` string, uuid, required: UUIDv7, lowercase canonical form.
    - `organization_id` string, uuid, required: UUIDv7, lowercase canonical form.
    - `template` object, required
      - `id` string, uuid, required: UUIDv7, lowercase canonical form.
      - `slug` string, required
      - `name` string, required
    - `state` 'queued' | 'building' | 'testing' | 'publishing' | 'published' | 'succeeded' | 'failed' | 'canceled', required: queued -> building -> testing -> publishing -> published (the cell stored the artifact; the API then registers the version, see `registration`) | failed | canceled. `succeeded` = legacy builds.
    - `target_version` integer, nullable, required: Version number this build produces in its organization template (assigned at request time; never reused, so versions may have gaps). Null only for legacy builds.
    - `auto_publish` boolean, required: Publish the registered version at once (true), or leave it unpublished for the owner/admin publish route.
    - `published_at` string, date-time, nullable, required: RFC 3339 UTC timestamp with Z.
    - `registration` object, required: Creation of the immutable template version from a published build. not_applicable: not published (yet); pending: waiting for the API worker (error = last failed attempt, retried with backoff); registered; failed: retries exhausted (error says why).
      - `state` 'not_applicable' | 'pending' | 'registered' | 'failed', required
      - `registered_at` string, date-time, nullable, required: RFC 3339 UTC timestamp with Z.
      - `error` object, nullable, required
        - `code` string, required
        - `message` string, required
    - `template_version` object, nullable, required: The template version this build produced (once registered).
      - `id` string, uuid, required: UUIDv7, lowercase canonical form.
      - `version` integer, required
      - `published` boolean, required
    - `cancel_requested_at` string, date-time, nullable, required: RFC 3339 UTC timestamp with Z.
    - `created_at` string, date-time, required: RFC 3339 UTC timestamp with Z.
    - `updated_at` string, date-time, required: RFC 3339 UTC timestamp with Z.
    - `started_at` string, date-time, nullable, required: RFC 3339 UTC timestamp with Z.
    - `completed_at` string, date-time, nullable, required: RFC 3339 UTC timestamp with Z.
    - `requested_by` object, required
      - `type` 'user' | 'api_key', required
      - `id` string, uuid, required: UUIDv7, lowercase canonical form.
    - `base` object, required: The base template version, pinned at request time.
      - `ref` string, required
      - `template_id` string, uuid, required: UUIDv7, lowercase canonical form.
      - `template_version_id` string, uuid, required: UUIDv7, lowercase canonical form.
      - `slug` string, required
      - `version` integer, required
      - `artifact_sha256` string, required
    - `architecture` string, required
    - `bounds` object, required
      - `cpu_millis` integer, required
      - `memory_mib` integer, required
      - `disk_gib` integer, required
      - `timeout_seconds` integer, required
    - `requested_bounds` object, required
      - `cpu_millis` integer, nullable, required
      - `memory_mib` integer, nullable, required
      - `disk_gib` integer, nullable, required
      - `timeout_seconds` integer, nullable, required
    - `bound_sources` object, required: request: as asked; plan: clamped to (or defaulted from) the plan’s workspace limit; platform: build timeout default/maximum.
      - `cpu_millis` 'request' | 'plan' | 'platform', required
      - `memory_mib` 'request' | 'plan' | 'platform', required
      - `disk_gib` 'request' | 'plan' | 'platform', required
      - `timeout_seconds` 'request' | 'plan' | 'platform', required
    - `network` object, required: The build network (recipe v2: compiled.network, derived from the recipe for `auto`).
      - `mode` 'none' | 'egress_allowlist', required
      - `allow_hosts` string[], required
    - `denied_hosts` string[], required: Host names the builder’s DNS proxy refused during the build (at most 50). Rebuild with a host in build.network.extra_hosts to allow it.
    - `provenance` object, required
      - `recipe_schema` string, nullable, required: Null for workspace-source builds.
      - `recipe_sha256` string, nullable, required: SHA-256 of the canonical recipe JSON (the build’s source input); null for workspace-source builds.
      - `base_artifact_sha256` string, required
      - `builder_id` string, nullable, required
      - `attempt` integer, required
    - `recipe` union: Only on GET of one build: recipe v1 {dockerfile}, or recipe v2 {schema, build, settings, compiled} (the stored document).
      - object
        - `dockerfile` string, required
      - object
        - `schema` 'shardflux.template-recipe.v2', required
        - `build` object, required
          - `languages` object[], required: Resolved languages in compile order (implied ones included); source base = the base already has it.
            - `id` string, required
            - `version` string, required
            - `source` 'install' | 'base', required
            - `url` string, nullable, required
            - `sha256` string, nullable, required
          - `packages` object, required
            - `apt` string[], required
            - `pip` object, required
              - …
            - `npm` string[], required
          - `files` object[], required
            - `upload` string, required
            - `kind` 'file' | 'tar', required
            - `to` string, required
            - `owner` string, required
            - `mode` string, nullable, required
            - `size` integer, required
          - `steps` object[], required
            - `name` string, required
            - `run` string, required
            - `user` string, required
            - `cwd` string, nullable, required
            - `env` object, required
          - `network` object, required
            - `build` 'auto' | 'none' | 'allowlist', required
            - `extra_hosts` string[], required
            - `allow_hosts` string[], required
        - `settings` TemplateSettings, required: What a workspace of the version gets when it opens, every key present. `env` is the effective template env (a recipe v2 version: its languages’ build env, then its own env).
          - `env` object, required
          - `inputs` object, required
          - `start` TemplateStartCommand[], required
            - `name` string, required
            - `when` 'create' | 'boot' | 'resume', required
            - `run` string, required
            - `user` string, nullable, required: null: the template’s default user.
            - `cwd` string, nullable, required
            - `timeout_seconds` integer, required
          - `services` object, required
          - `defaults` TemplateDefaults, required: Manifest v2 `defaults`. Versions without them report the T1 defaults.
            - `lifetime` 'persistent' | 'session', required: persistent: kept until deleted (default). session: discarded when the session ends (close(), idle timeout or draft discard;).
            - `idle_timeout_seconds` integer, nullable, required
            - `limits` object, nullable, required: Ceilings of every admission of a workspace of the version (clamped, never refused;).
              - …
            - `egress` TemplateEgressDefault, required: The template’s workspace network ceiling: the cell intersects it with the effective egress policy.
              - …
            - `agent_tools` unknown, required
        - `compiled` object, required
          - `compiler` string, required
          - `language_table_sha256` string, required
          - `build_env` object, required
          - `steps` object[], required
          - `tools` object[], required
            - `name` string, required
            - `argv` string[], required
          - `network` object, required
            - `mode` 'none' | 'egress_allowlist', required
            - `allow_hosts` string[], required
          - `objects` object[], required
            - `sha256` string, required
            - `size` integer, required
            - `kind` 'file' | 'tar', required
        - `immutable` string[], required: The effective immutable paths (: the recipe’s, else inherited), in byte order; empty for none.
    - `result` object, required
      - `artifact_sha256` string, nullable, required
      - `scan` object, nullable, required: Scanner result recorded by the builder ({passed, scanner, scanner_version, summary}).
      - `compatibility` object, nullable, required: Guest compatibility test result ({passed, runtime_class, kernel_release, firecracker_version, guest_agent_version, checks}).
      - `produced_version` object, nullable, required
        - `id` string, uuid, required: UUIDv7, lowercase canonical form.
        - `version` integer, required
        - `state` 'unpublished' | 'published' | 'archived', required
        - `published_at` string, date-time, nullable, required: RFC 3339 UTC timestamp with Z.
        - `archived_at` string, date-time, nullable, required: RFC 3339 UTC timestamp with Z.
    - `failure` object, nullable, required
      - `code` string, required: build_error, timeout, resource_limit, scan_failed, compatibility_failed, base_unavailable, network_denied, internal_error, publish_failed, source_unavailable, immutable_path_missing (an immutable path is not a directory in the built filesystem: details.path), immutable_image_too_large (the immutable image exceeds 16 GiB).
      - `message` string, required
      - `details` object: Code-specific details (immutable_path_missing: {path}).
    - `log` object, required: Build log: the tail inline, the full log through a short-lived signed URL (log-url).
      - `state` 'none' | 'available' | 'expired', required
      - `bytes` integer, nullable, required
      - `sha256` string, nullable, required: SHA-256 of the full log object, when the builder recorded it.
      - `expires_at` string, date-time, nullable, required: RFC 3339 UTC timestamp with Z.
      - `downloadable` boolean, required: The full log can be downloaded through GET .../template-builds/{id}/log-url (short-lived signed URL).
      - `tail` string, nullable: Last <= 64 KiB of the build log (GET of one build; until expires_at).
    - `publishable` boolean, required
    - `builder_availability` object, required: From builder heartbeats (app.template_builders, 60 s window). `unavailable`: queued builds wait until a builder runs.
      - `state` 'available' | 'unavailable', required
      - `reason` 'no_builder_registered' | 'no_recent_heartbeat', nullable, required
      - `active_builders` integer, required
      - `last_heartbeat_at` string, date-time, nullable, required: RFC 3339 UTC timestamp with Z.
    - `queue` object: Present while the build waits for one of the plan’s build slots (all are in use): it starts as one frees.
      - `position` integer, required: Builds of the organization ahead of this one for a slot (1 = next).
      - `slots` integer, required: Builds the plan runs at once (concurrent_template_builds).
      - `expected_wait_seconds` integer, required: Estimate from the organization’s recent build times.
    - `progress` object: Present while the build runs (building, testing, publishing, or published before registration), or the last reported phase and counters after failure. Missing counters are unknown.
      - `phase` 'preparing' | 'copying' | 'sealing' | 'indexing' | 'packaging' | 'testing' | 'publishing' | 'registering' | 'building', required: preparing (fetching the source, starting the builder), copying (a save: the files go into the new layer) or building (a recipe runs), sealing (cleanup and platform files), indexing (the file list), packaging (the layer is compacted for storage), testing (the version boots), publishing (stored), registering (becomes a version).
      - `step` integer, required: The phase’s place in the list (1-based).
      - `steps` integer, required: How many phases the build has.
      - `files_done` integer
      - `files_total` integer: Files the phase handles (an estimate from the source filesystem while copying).
      - `bytes_done` integer
      - `bytes_total` integer
      - `phase_started_at` string, date-time
      - `updated_at` string, date-time, required: When the builder last reported.
    - `source_kind` 'recipe' | 'workspace', required: recipe (recipe v1 Dockerfile dialect or recipe v2) or workspace (save-as-template, draft publish).
    - `source_workspace_id` string, uuid, nullable, required: UUIDv7, lowercase canonical form.
    - `source_checkpoint_id` string, nullable, required: The saved checkpoint (filled when the capture finished and the cell claimed the build).
    - `scrub_result` object, nullable, required: sf-scrub.v1 report ({policy, removed_count, removed_bytes, removed_paths (first 1000), emptied, reimposed}); paths only, never contents.
    - `files` object, nullable, required: The produced file list ({key, size, sha256, content_sha256, entries, total_file_bytes}).
    - `produced_layer_id` string, uuid, nullable, required: UUIDv7, lowercase canonical form.
    - `squashed` boolean, nullable, required: The base chain already had 4 org layers: this build squashed them into its one layer.
    - `org_bytes` integer, nullable, required: The produced chain’s org bytes (limited by the plan’s workspace disk limit).
    - `immutable_paths` string[], required: The immutable paths the produced version declares, in byte order: the recipe’s `immutable`, else the list of the template’s open version (a save from a workspace: the target template’s open version’s list, else the workspace’s version’s). Empty for none.

## Other responses

- `4XX`: Default Response
- `5XX`: Default Response

---

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