---
title: "Save a new workflow draft"
method: POST
path: "/api/v2/workflows/drafts"
tags: ["workflows"]
---

# Save a new workflow draft

`POST /api/v2/workflows/drafts`

Persist an in-progress workflow as a resumable draft.

A draft is a real workflow row in ``draft`` status: it is private to its
author, never appears in the normal workflow list, never counts against
the plan's workflow cap, and cannot be run, cloned, promoted, or shared.
Validation is deliberately loose so a half-finished workflow (missing
steps, an unconfigured agent, no name) still saves. There is no capacity
gate here on purpose. The cap is enforced only when the draft is
published. No ``WORKFLOW_CREATED`` audit is written for a draft; that is
emitted at publish time when the workflow becomes real.

## Request body

- WorkflowDraftCreate — Create or first-save a draft workflow. Every field is optional. Drops the completeness gates: no required name, no required ``fail_threshold_percent``, no "at least one vision agent step" rule, and steps may be empty. Completeness is only enforced when the draft is published.
  - `name` string — Draft name (may be blank until published).
  - `description` string, nullable — Optional long-form description of the workflow's purpose.
  - `tags` string[] — Tag values (system or org-custom) for filtering/grouping.
  - `allow_step_skip` boolean — Allow completing the job with uncaptured steps.
  - `allow_out_of_order_steps` boolean — Allow capturing steps in any order.
  - `allow_step_failure` boolean — Strict (False) fails the job on any failing step; threshold (True) uses fail_threshold_percent.
  - `fail_threshold_percent` integer — Percentage of steps that can fail before workflow fails (0-100).
  - `allow_adhoc_step` boolean — Allow the operator to append ad-hoc steps during a run.
  - `steps` WorkflowDraftStepCreateOrUpdate[] — Ordered steps (may be empty for a draft; at most 200).
    - `step_key` string, nullable — Stable identity for this logical step. The editor mints this (a uuid) so reference files attached to a not-yet-saved step survive the save. Omit for steps that carry no reference files; the server then assigns one. Reference files themselves are managed through the dedicated reference-file endpoints, never through this payload.
    - `sort_order` integer — Display order of step in the workflow.
    - `name` string — Step name (may be blank in draft).
    - `workflow_note` string, nullable — Optional guidance shown to the operator before they capture the image.
    - `reference_file_paths` string[], nullable — Optional reference files (blob-storage object paths) shown to the operator and fed to the agent.
    - `step_type` 'vision_agent' | 'selection' — Discriminator for a workflow step's strategy. Every registered value has a matching :class:`StepTypeSpec` subclass in :mod:`app.workflow_steps` that owns the strategy's config, input, output, submit payload and handlers. Adding a value here is one of the three edits required to register a new step type; see :mod:`app.workflow_steps.registry` for the full checklist.
    - `step_config` object, nullable — Per-type configuration. For ``vision_agent``: ``{'agent_id': '...', ...agent-params}``. For ``selection``: ``{'options': [...], 'use_selection_as_criteria': bool}``. Legacy clients may omit this and instead send ``agent_id`` + ``agent_parameters`` at the top level; the server will fold them into ``step_config`` for you. Maximum 32,768 bytes when JSON-serialized, nested at most 32 levels deep.
    - `agent_id` string, nullable — Deprecated. Legacy top-level field; new clients put the agent id inside ``step_config``. Folded into ``step_config`` on save when supplied.
    - `agent_parameters` object, nullable — Deprecated. Legacy top-level field; new clients put agent parameters inside ``step_config``. Folded into ``step_config`` on save when supplied.
    - `pass_criteria` PassCriteria
      - `combinator` 'and' | 'or', required — How to aggregate rule outcomes. ``and`` requires every rule to pass; ``or`` requires at least one.
      - `rules` PassCriteriaRule[], required — One or more rules to evaluate against the agent response.
        - `path` string, required — Dotted path into agent response.
        - `op` 'eq' | 'ne' | 'gt' | 'gte' | 'lt' | 'lte' | 'contains' | 'not_contains' | 'in' | 'not_in' | 'exists' | 'not_exists' | 'length_gte' | 'length_lt', required
        - `value` unknown
    - `allow_multiple_images` boolean — Whether a capture step accepts more than one image per submission (up to the server-enforced cap). Meaningful only for ``vision_agent`` steps; ignored for ``selection`` steps, which never capture images.

## Response `201`

Successful Response

- WorkflowRead — Full workflow payload including its steps.
  - `id` string, required — Stable family id, unchanged across edits.
  - `name` string, required — Current human-readable workflow name.
  - `description` string, nullable — Optional long-form description of the workflow's purpose.
  - `tags` string[] — Tag values (system or org-custom) for filtering/grouping.
  - `is_system` boolean, required — True when this is a system workflow visible to every org.
  - `system_source` 'registry' | 'user', nullable — Provenance of a system workflow: 'registry' for a built-in seeded into the environment, 'user' for one promoted from an org's workflow. Null for non-system workflows.
  - `is_tiliter_created` boolean — True when this system workflow is Tiliter-authored. Tiliter-created entries sort ahead of community-promoted ones in the picker. Always false for non-system workflows.
  - `is_deleted` boolean — True when the workflow has been soft-deleted.
  - `status` 'active' | 'draft' | 'deleted' — Lifecycle state of a workflow row. Transitions: ``draft`` -> ``active`` -> ``deleted``. Non-draft rows skip to ``active`` on creation.
  - `allow_step_skip` boolean — When True, the job can be completed with steps left uncaptured. When False, every step must be captured first.
  - `allow_out_of_order_steps` boolean — When True, steps may be captured in any order. When False, they must be captured in ascending sort_order.
  - `allow_step_failure` boolean — Selects the job pass policy. When False (strict), any step that fails its pass_criteria or errors makes the job's result FAILED. When True (threshold), failures are tolerated until the percentage of evaluable steps that failed exceeds ``fail_threshold_percent``. Either way the job's lifecycle status is unaffected -- only a technical error makes a job COMPLETED_WITH_ERRORS.
  - `fail_threshold_percent` integer — Maximum percentage (0-100) of evaluable steps that may fail before the job is FAILED when ``allow_step_failure`` is True. Ignored when ``allow_step_failure`` is False.
  - `allow_adhoc_step` boolean — When True, the operator may append ad-hoc steps during a run.
  - `owner_user_id` string, nullable — Owning user id. Null for system workflows.
  - `organization_id` string, nullable — Owning organization id. Null for personal workflows.
  - `parent_workflow_id` string, nullable — Set on an edit draft (a 'draft' row that stages changes to a live workflow): the id of the live workflow these edits will publish to. Null on active workflows and on brand-new-workflow drafts.
  - `unpublished_draft_id` string, nullable — On an active workflow, the id of the caller's own in-progress edit draft for it, if one exists (so the client can offer 'resume editing' and show an 'unpublished changes' indicator). Null when the caller has no edit draft for this workflow.
  - `step_count` integer — Number of steps in the workflow.
  - `active_job_count` integer — Number of non-completed jobs currently running against this workflow.
  - `access_user_count` integer — Number of distinct users who have access to this workflow via group membership or direct ownership. Zero for system workflows.
  - `created_at` string, date-time, required — When the workflow was first created.
  - `updated_at` string, date-time, nullable — When the workflow was last modified.
  - `steps` WorkflowStepRead[] — Ordered list of steps in the workflow.
    - `id` string, required — Stable id of this step. Useful as a React key on edit forms.
    - `step_key` string, nullable — Stable logical-step identity carried across snapshot versions. Reference files attach to this key, so it round-trips: the editor echoes it back on the step-replace save to keep a step's files. Null on job reads, which never edit steps.
    - `sort_order` integer, required — Zero-based position of this step within the workflow.
    - `name` string, required — Human-readable step title shown to the operator.
    - `workflow_note` string, nullable — Optional guidance shown to the operator before they capture the image.
    - `reference_file_paths` string[], nullable — Optional reference files (blob-storage object paths) shown to the operator and fed to the agent. Populated in workflow contexts; null on the job-read endpoint (GET /jobs/{id}), where only ``reference_file_urls`` is returned.
    - `reference_file_urls` string[], nullable — Short-lived signed download URLs for the step's reference files. Best-effort: paths that fail to sign are omitted, so this is not positionally aligned with the underlying paths. Populated only on terminal-status job reads (when the results page renders its report); null in workflow contexts and on in-progress/analysing job polls.
    - `step_type` 'vision_agent' | 'selection' — Discriminator for a workflow step's strategy. Every registered value has a matching :class:`StepTypeSpec` subclass in :mod:`app.workflow_steps` that owns the strategy's config, input, output, submit payload and handlers. Adding a value here is one of the three edits required to register a new step type; see :mod:`app.workflow_steps.registry` for the full checklist.
    - `step_config` object — Per-type configuration for this step. For ``vision_agent`` the blob carries ``agent_id`` and the agent's structured input parameters. For ``selection`` it carries ``options`` and ``use_selection_as_criteria``. Empty ``{}`` when the type has no configuration.
    - `agent_id` string, nullable — Deprecated. Prefer ``step_type`` + ``step_config``. Mirrored from ``step_config.agent_id`` for vision-agent steps; the selection sentinel is emitted for legacy clients on selection steps.
    - `agent_parameters` object, nullable — Deprecated. Prefer ``step_config``. Mirrored from ``step_config`` with the ``agent_id`` key stripped for vision-agent steps.
    - `pass_criteria` PassCriteria
      - `combinator` 'and' | 'or', required — How to aggregate rule outcomes. ``and`` requires every rule to pass; ``or`` requires at least one.
      - `rules` PassCriteriaRule[], required — One or more rules to evaluate against the agent response.
        - `path` string, required — Dotted path into agent response.
        - `op` 'eq' | 'ne' | 'gt' | 'gte' | 'lt' | 'lte' | 'contains' | 'not_contains' | 'in' | 'not_in' | 'exists' | 'not_exists' | 'length_gte' | 'length_lt', required
        - `value` unknown
    - `allow_multiple_images` boolean — Whether a capture step accepts more than one image per submission (up to the server-enforced cap). Meaningful only for ``vision_agent`` steps; ignored for ``selection`` steps, which never capture images.

## Other responses

- `422` — Validation Error

---

[API](https://skmtc.net/tiliter/apis/tiliter-vision-ai-public-api.md) · [All operations](https://skmtc.net/tiliter/apis/tiliter-vision-ai-public-api/llms.txt) · [OpenAPI document](https://skmtc-service-staging.skmtc.workers.dev/v1/apis/tiliter/tiliter-vision-ai-public-api/revisions/30b6f7f148f8/schema)
