---
title: "Request a custom template build (queued for the isolated builder)"
method: POST
path: "/v1/organizations/{organization_id}/template-builds"
tags: ["Template builds"]
---

# Request a custom template build (queued for the isolated builder)

`POST /v1/organizations/{organization_id}/template-builds`

## Request

- Base URL: `https://api.shardflux.dev`
- URL: `https://api.shardflux.dev/v1/organizations/{organization_id}/template-builds`
- Auth: HTTP bearer

202 with the queued build. Takes recipe v1 (a Dockerfile) or recipe v2 (: validated against the base, compiled to the builder’s steps, uploads locked). Records the canonical recipe and its SHA-256 (provenance input), pins the base version, clamps resources to the plan and assigns `target_version`. The cell executes it (building -> testing -> publishing -> published) and the API then registers the version (published at once unless auto_publish=false); poll GET .../template-builds/{id} until `registration.state` is registered (or the build failed/was canceled). `builder_availability` says whether a builder is running. Errors: 422 when the recipe is outside the host builder Dockerfile dialect or the slug is not a builder slug (`details.reason`: multi_stage_not_supported, from_not_template_base, stage_names_not_supported, from_flags_not_supported, base_mismatch, run_flags_not_supported, heredoc_not_supported, instruction_not_supported, no_build_context, env_invalid, user_invalid, too_many_steps, recipe_too_large, slug_not_supported_by_builder, platform_template_slug, reserved_template_slug, base_not_found, base_not_published, base_archived, architecture_not_supported, ...; `line` for line errors; recipe v2: invalid_recipe (details.field, details.detail), base_not_layered, language_unavailable, language_conflict, invalid_package, too_many_files, platform_owned_path, invalid_path, upload_required, upload_missing, upload_digest_mismatch, upload_too_large, too_many_steps, recipe_too_large, allowlist_empty, allow_hosts_without_allowlist, extra_hosts_without_auto, too_many_hosts, invalid_host, ip_literal_not_allowed, host_not_allowed, invalid_settings, services_unsupported, too_many_acknowledged_findings; immutable paths: invalid_path (details.field recipe.immutable[<i>]), immutable_path_removed (the recipe drops a path of the template’s open version; details.removed), immutable_paths_unsupported_base (a non-empty list on a base whose guest agent lacks immutable_paths.v1, or a recipe v1 build of a template with immutable paths; details.base {name, version})), 503 dependency_unavailable (uploads_not_configured), 402 entitlement_required, 429 rate_limited (template_build_queue_full: already 10 builds queued past the plan’s concurrent_template_builds; retryable, Retry-After; a build past the plan’s slots is accepted and waits, its `queue` gives the position and expected wait), 409 template_archived. Supports Idempotency-Key.

## Path parameters

- `organization_id` string, uuid, required

## Request body

- object
  - `template_slug` string, required: Organization template to build into (created by the first build). Platform slugs and the reserved slugs new and edit are refused for new templates.
  - `auto_publish` boolean: Publish the produced version as soon as it is registered (new workspaces of the slug then use it). false: it stays unpublished until an owner/admin publishes it.
  - `display_name` string: Template name when this build creates the template.
  - `recipe` union, required: Recipe v1 (TemplateRecipeV1: a Dockerfile, no `schema` field) or recipe v2 (TemplateRecipeV2, `schema: "shardflux.template-recipe.v2"`: languages, packages, uploaded files, build steps, auto network and settings;).
    - TemplateRecipeV1: Recipe v1: a Dockerfile in the host builder’s dialect (no `schema` field).
      - `base` string, required: `<template slug>@<version>`: a published, non-archived version this organization can use (its own templates first, then platform). Referenced as `FROM shardflux-base`.
      - `dockerfile` string, required: Dockerfile in the builder dialect (<= 64 KiB UTF-8): exactly one `FROM shardflux-base` (or `FROM shardflux-base:<recipe.base>`) first (ARG may precede it), then RUN (shell or JSON exec form, no flags or heredocs), ENV, ARG, WORKDIR, USER; LABEL, EXPOSE, CMD, ENTRYPOINT, MAINTAINER, STOPSIGNAL are recorded only. No COPY/ADD (no build context), no multi-stage or external images, at most 128 steps. Refusals are 422 with details.reason and line.
      - `architecture` 'x86_64': x86_64 (the default; the only one).
      - `resources` object: Builder VM bounds (the host builder’s ranges); clamped to the plan’s per-workspace limits (absent = the plan limit); timeout default 1800 s, maximum 3600 s.
        - `cpu_millis` integer: Builder range 500-16000.
        - `memory_mib` integer: Builder range 512-32768, even.
        - `disk_gib` integer: Builder maximum 200.
        - `timeout_seconds` integer
      - `network` object: Build network: none (default) or egress to the listed DNS names (example.com, *.example.com).
        - `mode` 'none' | 'egress_allowlist', required
        - `allow_hosts` string[]
    - TemplateRecipeV2: A template without a Dockerfile (docs/INTEGRATION_CONTRACTS.md): `build` changes the filesystem through the host builder's structured steps, `settings` goes into the manifest and applies when a workspace opens. This document is the body field `recipe` of POST …/template-builds, the `recipe` of GET …/templates/{slug}/versions/{v}/recipe (export), and the content of template.yaml (the same document in YAML; only clients parse YAML). In template.yaml a file entry may name a local path (`from`) instead of an upload; clients upload it and send `upload` (the API refuses `from`). Rules JSON Schema cannot express, enforced by the API with 422 validation_failed and details.reason (): input names are unique across settings.env and settings.inputs; a secret input has no default; a required input has no default; start command names are unique; `to` is not platform-owned; build.network.allow_hosts only with build "allowlist" and extra_hosts only with "auto"; the sum of every run/command script is at most 262144 bytes; start timeout_seconds sum to at most 3600; languages are in the API's language table for the base; services need a base whose guest agent has services.v1; immutable paths are not platform-owned, do not contain a platform-owned path and do not nest.
      - `schema` 'shardflux.template-recipe.v2', required
      - `base` string, required: `<template slug>@<version>`: a published, non-archived, layered-capable version this organization can use (its own templates first, then platform), e.g. ubuntu-24.04@1.
      - `build` object, required
        - `languages` object[]: Languages from the API's language table. version omitted = the table's default for the base. Compiled first, in the table's order (not this array's).
          - `id` 'python' | 'node' | 'go' | 'rust' | 'java', required
          - `version` string
        - `packages` object
          - `apt` string[]
          - `pip` object: Installed into /opt/venv (implies the python language).
            - `packages` string[]
            - `requirements` string[]: Absolute paths of requirements files inside the template (usually from files[]), installed with pip -r.
          - `npm` string[]: npm install -g (implies the node language unless the base has node).
        - `files` object[]: Uploaded files and folders, applied in this order (a later entry overwrites an earlier one).
          - `upload` string: An upload of this organization: sha256:<64 lower-case hex>.
          - `from` string: template.yaml only: a local file (kind file) or directory (kind tar) the client uploads. Refused by the API.
          - `kind` 'file' | 'tar', required
          - `to` string, required: kind file: the file's path; kind tar: the directory the archive is extracted into. Platform-owned paths are refused.
          - `owner` string: Default root.
          - `mode` string, nullable: Octal permission bits of a file (default 0644); absent or null for kind tar.
        - `steps` object[]: Named build scripts, run in this order after languages, apt, files, pip and npm.
          - `name` string, required
          - `run` string, required: Shell script run by /bin/bash -euo pipefail -c; 1..65536 bytes, no NUL.
          - `user` string: Default root.
          - `cwd` string, nullable: Absolute, clean (no '.', '..', '//' or trailing '/'), at most 1024 bytes.
          - `env` object
        - `network` object: Build network. auto (default): hosts derived from the recipe plus extra_hosts; none: no egress; allowlist: exactly allow_hosts.
          - `build` 'auto' | 'none' | 'allowlist'
          - `extra_hosts` string[]
          - `allow_hosts` string[]
      - `settings` object, required: 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
      - `immutable` string[]: Directories every workspace of the template mounts read-only from the template's newest published version: workspaces pick up a newer version when they cold-boot or resume. Not `/`, not equal to or under /proc, /sys, /dev, /run, /tmp, /lost+found or a platform-owned path, not containing a platform-owned path, none equal to or containing another (422 invalid_path). Omitted: the list of the template's newest published version. A version keeps every path of that list (422 immutable_path_removed) and a non-empty list needs a base whose guest agent has immutable_paths.v1 (422 immutable_paths_unsupported_base). Each path is a directory in the built filesystem.
      - `resources` object: Builder VM bounds, exactly as recipe v1 `resources` (not part of recipe_sha256).
        - `cpu_millis` integer
        - `memory_mib` integer
        - `disk_gib` integer
        - `timeout_seconds` integer
  - `description` string: The version description (manifest `description`).
  - `acknowledged_scan_findings` string[]: Recipe v2 only: up to 200 absolute paths the credential scan may report without failing the build (recorded in the manifest).

## Response `202`

Default Response

- object
  - `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
            - `packages` string[], required
            - `requirements` string[], 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;).
            - `cpu_millis_ceiling` integer, required
            - `memory_mib_ceiling` integer, required
            - `disk_gib` integer, required
          - `egress` TemplateEgressDefault, required: The template’s workspace network ceiling: the cell intersects it with the effective egress policy.
            - `mode` 'allowlist' | 'none', required
            - `allow_hosts` string[], required: Normalized (lowercase, sorted); empty for none.
          - `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)
