> ## Documentation Index
> Fetch the complete documentation index at: https://formbricks.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Archive a workflow

> Soft-deletes a workflow by moving it from any non-archived state to `archived`. Archived
workflows stop responding to trigger events immediately, become read-only, and are excluded
from list responses unless requested with `filter[status][in]=archived`. Their runs, version
history, and definition are preserved, and the workflow can be restored with
`POST /api/v3/workflows/{workflowId}/unarchive`. Archiving an already archived workflow
returns **422** with code `invalid_workflow_state`.




## OpenAPI

````yaml /api-v3-reference/openapi.yml post /api/v3/workflows/{workflowId}/archive
openapi: 3.1.1
info:
  title: Formbricks API v3
  description: >
    **Surveys**: **GET /api/v3/surveys**, **POST /api/v3/surveys**, **POST
    /api/v3/surveys/generate**, **POST /api/v3/surveys/validate**, **GET
    /api/v3/surveys/{surveyId}**, **PATCH /api/v3/surveys/{surveyId}**, and
    **DELETE /api/v3/surveys/{surveyId}**.


    **Workflows extension**: **GET /api/v3/workflows**, **POST
    /api/v3/workflows**, **GET /api/v3/workflows/{workflowId}**, **PATCH
    /api/v3/workflows/{workflowId}**, **DELETE /api/v3/workflows/{workflowId}**,
    **POST /api/v3/workflows/{workflowId}/duplicate**, **POST
    /api/v3/workflows/{workflowId}/enable**, **POST
    /api/v3/workflows/{workflowId}/disable**, **POST
    /api/v3/workflows/{workflowId}/archive**, **POST
    /api/v3/workflows/{workflowId}/unarchive**, **POST
    /api/v3/workflows/{workflowId}/test**, **GET /api/v3/workflows/runs**, and
    **GET /api/v3/workflows/runs/{runId}**.


    Authenticate all management endpoints with **session cookie** or
    **`x-api-key`** (management key with access to the workspace).


    **Spec location:** source of truth is the multi-file tree at
    `docs/api-v3-reference/src/` (root `openapi.yml` plus one file per path and
    component); `docs/api-v3-reference/openapi.yml` is the generated single-file
    bundle (alongside v2 at `docs/api-v2-reference/openapi.yml`).


    **workspaceId**

    `workspaceId` is the canonical container identifier for this API. It is a
    **required query parameter on collection read endpoints** (`GET
    /api/v3/surveys`, `GET /api/v3/action-classes`, `GET
    /api/v3/contact-attribute-keys`) because those operate within a workspace.
    **`POST /api/v3/surveys` instead requires `workspaceId` in the request
    body**, not as a query parameter. Single-resource endpoints
    (`GET`/`PATCH`/`DELETE /api/v3/surveys/{surveyId}`) locate the resource by
    its globally-unique id and resolve the workspace from it, so they do **not**
    accept a `workspaceId` query parameter — sending one returns **400**
    (`Unrecognized key: "workspaceId"`).


    **Auth**

    Authenticate with either a session cookie or **`x-api-key`**. In dual-auth
    mode, V3 checks the API key first when the header is present, otherwise it
    uses the session path. Unauthenticated callers get **401** before query
    validation.


    **Pagination**

    Cursor-based pagination with **limit** + opaque **cursor** token. Responses
    return `meta.nextCursor`; pass that value back as `cursor` to fetch the next
    page. Survey list responses also include `meta.totalCount`, the total number
    of surveys matching the current filters across all pages. Workflow list
    responses do not require total counts in Scope 1. There is no `offset` in
    this contract.


    **Filtering**

    Filters use explicit operator-style query parameters under the
    **`filter[...]` family**. The survey list supports `filter[name][contains]`,
    `filter[status][in]`, and `filter[type][in]`. The workflow list supports
    `filter[name][contains]` and `filter[status][in]`; the workflow run list
    supports `filter[status][in]` and `filter[isDryRun][eq]`. Resource-scoping
    identifiers (`workspaceId`, `workflowId`, `responseId`) stay plain query
    parameters. Multi-value filters use repeated keys or comma-separated values
    (e.g. `filter[status][in]=draft&filter[status][in]=inProgress` or
    `filter[status][in]=draft,inProgress`). Sorting remains a flat `sortBy`
    query parameter.


    **Security**

    Missing/forbidden workspace returns **403** with a generic message (not
    **404**) so resource existence is not leaked. Workflow and workflow-run
    detail endpoints also return **403** for unknown IDs or inaccessible IDs to
    avoid IDOR leaks. List responses use `private, no-store`.


    **AI survey creation**

    `POST /api/v3/surveys/generate` returns a draft `POST /api/v3/surveys`
    payload plus validation metadata. It does not create a survey. It generates
    survey content for both `link` and `app` surveys; for `app` it seeds a
    default `distribution` (display once, no triggers, no targeting) that you
    finish configuring before publishing.

    Prompt text is sent to the configured AI provider for generation, but it is
    not stored by this endpoint, not logged by default, and not included in
    audit data.


    **Feedback Records** `/api/v3/feedbackRecords` is served by the **API
    gateway** (Envoy on Formbricks Cloud, Traefik in the one-click Docker
    install), which authorizes each call against the dataset and forwards it to
    the feedback store — not by the application routes that serve the rest of
    v3. Three consequences: the payload is the store's own, so members and
    filters are **`snake_case`** (`tenant_id`, `value_text`) rather than v3's
    camelCase; the dataset is named **`tenant_id`** on the wire and is required
    on every collection operation (it is the feedback dataset id, which the MCP
    `list_feedback_datasets` tool reports as `dataset_id`); and errors come in
    two shapes — gateway refusals (**400**, **401**, **403**, **413**, **503**)
    are `text/plain`, while the store's own problems are
    `application/problem+json` with a stable `code`. These eight operations are
    the whole of the gateway-served surface; any other path it receives under
    the prefix answers **400**. Authenticate with **`x-api-key`**: the key needs
    a permission on a workspace the dataset is assigned to — `read` to list, get
    and search, `write` to create and update, `manage` to delete — and may
    update or delete only in a dataset assigned to exactly one workspace.

    **OpenAPI**

    This spec is **not** produced by `pnpm generate-api-specs` (that script only
    builds v2 → `docs/api-v2-reference/openapi.yml`). When the route contract
    changes, edit the files under `docs/api-v3-reference/src/`, then run `pnpm
    api:v3:bundle` to regenerate the committed single-file bundle. `pnpm
    api:v3:lint` lints the source tree; `pnpm api:v3:check` (CI) fails when the
    bundle is stale.


    **Workflows Scope 1**

    Workflows are a follow-up extension of the existing v3 Survey API work. The
    Survey API establishes API-first survey authoring; Workflows build on that
    same v3 style to automate actions around survey responses through
    workspace-scoped JSON definitions. Scope 1 models the existing Follow-ups
    capability as workflows: one `response.completed` trigger with an optional
    `endingCardIds` filter, and `send_email` actions with Follow-up email field
    parity. The definition and run shapes mirror the shared Zod schemas in
    `packages/workflows` (ENG-1100), which are the implementation source of
    truth for this contract.


    Workflows use `draft`, `enabled`, and `disabled` lifecycle states plus an
    `archived` soft-delete state. Status only changes through the lifecycle
    endpoints (`enable`, `disable`, `archive`, `unarchive`); it is not writable
    via `POST` or `PATCH`. Enabling validates that the definition is executable
    and snapshots an immutable workflow version; runs reference that snapshot
    via `workflowVersionId`. `POST .../test` is a dry run: it validates the
    definition and resolves the trigger's references synchronously, creating no
    run and sending no email, and returns `{ workflowId, ok, problems }`
    directly. Real runs execute asynchronously and are polled via `GET
    /api/v3/workflows/runs/{runId}`.


    **Overview migration note**

    The v3-backed survey overview page intentionally removes actions that are
    not yet exposed by this contract: `Created by` filtering, `Duplicate`,
    `Copy...`, `Preview`, and `Copy link`.


    **Next steps (out of scope for this spec)**

    Additional v3 survey update endpoints, optional ETag/304, field selection,
    translation-specific writes, and survey version history.
  version: 0.1.0
  x-implementation-notes:
    route: apps/web/app/api/v3/surveys/route.ts
    generate-route: apps/web/app/api/v3/surveys/generate/route.ts
    query-parser: apps/web/app/api/v3/surveys/parse-v3-surveys-list-query.ts
    auth: apps/web/app/api/v3/lib/auth.ts
    workspace-resolution: apps/web/app/api/v3/lib/workspace-context.ts
    workflow-contract-ticket: ENG-1101
    workflow-shared-schemas: packages/workflows/src/types (feat/workflows-types)
    workflow-prisma-schema: packages/database/schema/workflows.prisma (feat/workflows-prisma-schema)
    openapi-generated: false
    spec-source: docs/api-v3-reference/src (regenerate bundle with `pnpm api:v3:bundle`)
    pagination-model: cursor
    cursor-pagination: supported
servers:
  - url: https://app.formbricks.com
    description: >-
      Formbricks Cloud. Paths in this spec already include the `/api/v3` prefix;
      self-hosted instances substitute their own origin.
security: []
paths:
  /api/v3/workflows/{workflowId}/archive:
    post:
      tags:
        - V3 Workflows
      summary: Archive a workflow
      description: >
        Soft-deletes a workflow by moving it from any non-archived state to
        `archived`. Archived

        workflows stop responding to trigger events immediately, become
        read-only, and are excluded

        from list responses unless requested with `filter[status][in]=archived`.
        Their runs, version

        history, and definition are preserved, and the workflow can be restored
        with

        `POST /api/v3/workflows/{workflowId}/unarchive`. Archiving an already
        archived workflow

        returns **422** with code `invalid_workflow_state`.
      operationId: archiveWorkflowV3
      parameters:
        - $ref: '#/components/parameters/WorkflowIdPath'
      responses:
        '200':
          $ref: '#/components/responses/WorkflowResourceResponse'
        '400':
          $ref: '#/components/responses/V3BadRequest'
        '401':
          $ref: '#/components/responses/V3Unauthorized'
        '403':
          $ref: '#/components/responses/V3Forbidden'
        '422':
          $ref: '#/components/responses/V3UnprocessableContent'
        '429':
          $ref: '#/components/responses/V3TooManyRequests'
        '500':
          $ref: '#/components/responses/V3InternalServerError'
      security:
        - sessionAuth: []
        - apiKeyAuth: []
components:
  parameters:
    WorkflowIdPath:
      in: path
      name: workflowId
      required: true
      schema:
        type: string
        format: cuid2
      description: Workflow identifier.
  responses:
    WorkflowResourceResponse:
      description: Workflow returned successfully
      headers:
        X-Request-Id:
          schema:
            type: string
          description: Request correlation ID
        Cache-Control:
          schema:
            type: string
          example: private, no-store
      content:
        application/json:
          schema:
            type: object
            required:
              - data
            properties:
              data:
                $ref: '#/components/schemas/WorkflowResource'
    V3BadRequest:
      description: >-
        Bad Request — malformed JSON, invalid query/body/params, duplicate name,
        or unsupported field.
      headers:
        X-Request-Id:
          schema:
            type: string
          description: Request correlation ID
        Cache-Control:
          schema:
            type: string
          example: private, no-store
      content:
        application/problem+json:
          schema:
            $ref: '#/components/schemas/Problem'
    V3Unauthorized:
      description: Not authenticated (no valid session or API key).
      headers:
        X-Request-Id:
          schema:
            type: string
          description: Request correlation ID
        Cache-Control:
          schema:
            type: string
          example: private, no-store
      content:
        application/problem+json:
          schema:
            $ref: '#/components/schemas/Problem'
    V3Forbidden:
      description: >-
        Forbidden — no workspace access, or resource does not exist (404 not
        used; avoids existence leak).
      headers:
        X-Request-Id:
          schema:
            type: string
          description: Request correlation ID
        Cache-Control:
          schema:
            type: string
          example: private, no-store
      content:
        application/problem+json:
          schema:
            $ref: '#/components/schemas/Problem'
    V3UnprocessableContent:
      description: >-
        Unprocessable Content — semantic validation failed or the requested
        lifecycle transition is invalid.
      headers:
        X-Request-Id:
          schema:
            type: string
          description: Request correlation ID
        Cache-Control:
          schema:
            type: string
          example: private, no-store
      content:
        application/problem+json:
          schema:
            $ref: '#/components/schemas/Problem'
    V3TooManyRequests:
      description: Rate limit exceeded.
      headers:
        X-Request-Id:
          schema:
            type: string
          description: Request correlation ID
        Cache-Control:
          schema:
            type: string
          example: private, no-store
        Retry-After:
          schema:
            type: integer
          description: Seconds until the current rate-limit window resets.
      content:
        application/problem+json:
          schema:
            $ref: '#/components/schemas/Problem'
    V3InternalServerError:
      description: Internal Server Error.
      headers:
        X-Request-Id:
          schema:
            type: string
          description: Request correlation ID
        Cache-Control:
          schema:
            type: string
          example: private, no-store
      content:
        application/problem+json:
          schema:
            $ref: '#/components/schemas/Problem'
  schemas:
    WorkflowResource:
      description: >
        Full workflow shape returned by detail, create, update, duplicate, and
        lifecycle endpoints:

        the list-item fields plus the complete `definition` document.
      allOf:
        - $ref: '#/components/schemas/WorkflowListItem'
        - type: object
          required:
            - definition
          properties:
            definition:
              $ref: '#/components/schemas/WorkflowDefinition'
    Problem:
      type: object
      description: >-
        RFC 9457 Problem Details for HTTP APIs (`application/problem+json`).
        Responses typically include a machine-readable `code` field alongside
        `title`, `status`, `detail`, and `requestId`.
      required:
        - title
        - status
        - detail
        - requestId
      properties:
        type:
          type: string
          format: uri
        title:
          type: string
        status:
          type: integer
        detail:
          type: string
        instance:
          type: string
        code:
          type: string
          enum:
            - ai_features_not_enabled
            - ai_generated_payload_invalid
            - ai_instance_not_configured
            - ai_output_too_long
            - ai_smart_tools_disabled
            - bad_gateway
            - bad_request
            - conflict
            - forbidden
            - internal_server_error
            - invalid_workflow_state
            - not_authenticated
            - not_found
            - payload_too_large
            - service_unavailable
            - too_many_requests
            - unprocessable_content
            - workflow_not_executable
        requestId:
          type: string
        details:
          type: object
        invalid_params:
          type: array
          items:
            $ref: '#/components/schemas/InvalidParam'
    WorkflowListItem:
      type: object
      description: >
        Slim workflow shape returned by `GET /api/v3/workflows`. `triggerType`
        and `surveyId` are

        derived read-only projections of `definition.trigger`, so list consumers
        can render and

        filter without loading full definitions. Serialized dates are ISO 8601
        strings.
      required:
        - id
        - workspaceId
        - name
        - description
        - status
        - triggerType
        - surveyId
        - createdBy
        - creator
        - createdAt
        - updatedAt
        - lastRun
        - runCount
      properties:
        id:
          type: string
          format: cuid2
        workspaceId:
          type: string
          format: cuid2
        name:
          type: string
        description:
          type:
            - string
            - 'null'
        status:
          $ref: '#/components/schemas/WorkflowStatus'
        triggerType:
          type: string
          enum:
            - response.completed
          description: Derived from `definition.trigger.triggerType`.
        surveyId:
          type: string
          format: cuid2
          description: Derived from `definition.trigger.config.surveyId`.
        createdBy:
          type:
            - string
            - 'null'
          format: cuid2
          description: >-
            User id that created the workflow. `null` when the creating user was
            deleted.
        creator:
          type:
            - object
            - 'null'
          required:
            - name
          properties:
            name:
              type: string
          description: Creating user's name. `null` when the creating user was deleted.
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time
        lastRun:
          anyOf:
            - $ref: '#/components/schemas/WorkflowRunSummary'
            - type: 'null'
          description: >-
            Most recent run summary (dry runs included). `null` when the
            workflow has never run.
        runCount:
          type: integer
          minimum: 0
          description: Total number of runs (dry runs included) for this workflow.
    WorkflowDefinition:
      type: object
      description: >
        Scope 1 workflow JSON document, mirroring `ZWorkflowDefinition` in
        `packages/workflows`. The

        definition is the public source of truth for the builder and API
        clients. Scope 1

        intentionally excludes user-authored if/else branches, webhook actions,
        schedules, delays,

        loops, and AI/MCP-assisted creation; future node types extend `nodes`
        additively.


        Graph rules enforced on every write (**400** with `invalid_params` on
        violation):

        node ids are unique across `trigger` and `nodes`; every edge references
        existing node ids;

        the trigger has at most one outgoing edge; `entryNodeId` equals
        `trigger.id`. Trigger-only

        drafts (no `nodes`, no `edges`) are valid persisted documents.
        Executability rules (exactly

        one outgoing trigger edge, acyclic graph, every node reachable from the
        trigger) are

        additionally enforced by `enable` and `test` with **422**
        `workflow_not_executable`.
      required:
        - trigger
        - entryNodeId
      properties:
        schemaVersion:
          type: integer
          enum:
            - 1
          default: 1
          description: >-
            Definition schema version. Optional on writes (defaults to the
            current version); always present in responses.
        trigger:
          $ref: '#/components/schemas/WorkflowResponseCompletedTriggerNode'
        nodes:
          type: array
          default: []
          items:
            $ref: '#/components/schemas/WorkflowSendEmailActionNode'
          description: >-
            Child nodes of the graph. The trigger lives in `trigger`, not in
            this array. Scope 1 accepts `send_email` action nodes only. Optional
            on writes (defaults to empty for trigger-only drafts); always
            present in responses.
        edges:
          type: array
          default: []
          items:
            $ref: '#/components/schemas/WorkflowEdge'
          description: >-
            Optional on writes (defaults to empty for trigger-only drafts);
            always present in responses.
        entryNodeId:
          type: string
          minLength: 1
          description: Workflow entry point. Must equal `trigger.id`.
      additionalProperties: false
    InvalidParam:
      type: object
      description: >
        Field-level validation error. `name` and `reason` are always intended
        for humans.

        Optional machine-readable fields are included when the API can identify
        the semantic error,

        especially for survey locale, identifier, and reference validation used
        by MCP and agent clients.
      required:
        - name
        - reason
      properties:
        name:
          type: string
          description: Dot-path to the invalid request field.
        reason:
          type: string
          description: Human-readable repair guidance.
        code:
          type: string
          enum:
            - dangling_reference
            - duplicate_identifier
            - duplicate_locale
            - forbidden_identifier
            - immutable_identifier
            - invalid_graph
            - invalid_locale
            - invalid_reference
            - missing_required_field
            - missing_translation
            - unsupported_field
            - unsupported_locale
          description: >
            Machine-readable validation category, when available.
            `invalid_graph` covers workflow

            graph-structure violations such as a duplicated trigger edge, an
            unreachable node, or a

            cycle.
        identifier:
          type: string
          description: >-
            Identifier involved in the validation failure, when available. For
            language errors, this can be the normalized language code.
        referenceType:
          type: string
          enum:
            - block
            - element
            - ending
            - hiddenField
            - language
            - variable
            - variableName
            - recall
            - node
            - edge
            - survey
            - endingCard
          description: >-
            Identifier namespace involved in the validation failure. `node`,
            `edge`, `survey`, and `endingCard` are used by workflow validation.
        missingId:
          type: string
          description: Referenced id that does not exist in the submitted survey document.
        firstUsedAt:
          type: string
          description: Dot-path where a duplicate identifier was first used.
        conflictsWith:
          type: string
          description: Dot-path to the existing identifier that conflicts with this field.
      additionalProperties: false
    WorkflowStatus:
      type: string
      enum:
        - draft
        - enabled
        - disabled
        - archived
      description: >
        Workflow lifecycle state. `draft` workflows are editable and inert.
        `enabled` workflows

        respond to trigger events. `disabled` workflows keep their configuration
        but are inert.

        `archived` workflows are soft-deleted: read-only, excluded from default
        list reads, and

        restorable via unarchive.


        Status only changes through lifecycle endpoints. Valid transitions:
        `draft` → `enabled`,

        `enabled` ↔ `disabled`, `enabled`/`disabled` → `draft`, any non-archived
        state → `archived`

        (archive), and `archived` → `draft` (unarchive).
    WorkflowRunSummary:
      type: object
      description: >
        Slim run shape embedded as `lastRun` in workflow resources. The runs
        list (`GET /api/v3/workflows/runs`)

        returns `WorkflowRunListItem`, which extends this with `workflowName`.
        Excludes `triggerPayload`, `data`, and `logs`.
      required:
        - id
        - workflowId
        - workspaceId
        - workflowVersionId
        - status
        - isDryRun
        - triggerType
        - surveyId
        - responseId
        - error
        - attempt
        - createdAt
        - updatedAt
        - startedAt
        - finishedAt
      properties:
        id:
          type: string
          format: cuid2
        workflowId:
          type: string
          format: cuid2
        workspaceId:
          type: string
          format: cuid2
        workflowVersionId:
          type:
            - string
            - 'null'
          format: cuid2
          description: >
            Immutable workflow version snapshot the run executes against. `null`
            for dry runs of

            workflows that have never been enabled. Version contents are not
            readable through the

            v3 API in Scope 1.
        status:
          $ref: '#/components/schemas/WorkflowRunStatus'
        isDryRun:
          type: boolean
          description: >-
            `true` for runs created by `POST
            /api/v3/workflows/{workflowId}/test`.
        triggerType:
          type: string
          enum:
            - response.completed
        surveyId:
          type:
            - string
            - 'null'
          format: cuid2
        responseId:
          type:
            - string
            - 'null'
          format: cuid2
          description: >-
            Survey response that triggered the run. `null` for synthesized
            dry-run data or when the response was deleted.
        error:
          type:
            - string
            - 'null'
          description: >-
            Terminal or most recent failure reason. `null` while the run is
            healthy.
        attempt:
          type: integer
          minimum: 0
          description: >-
            Retry attempt counter. `0` on the first execution; retries increment
            it without changing `status`.
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time
        startedAt:
          type:
            - string
            - 'null'
          format: date-time
        finishedAt:
          type:
            - string
            - 'null'
          format: date-time
    WorkflowResponseCompletedTriggerNode:
      type: object
      required:
        - id
        - type
        - triggerType
        - config
      properties:
        id:
          type: string
          minLength: 1
        type:
          type: string
          enum:
            - trigger
        triggerType:
          type: string
          enum:
            - response.completed
        label:
          type: string
          minLength: 1
          maxLength: 120
          description: Optional display label shown in the builder.
        config:
          type: object
          required:
            - surveyId
          properties:
            surveyId:
              type: string
              format: cuid2
              description: >-
                Survey whose completed responses trigger the workflow. Must
                exist in the workspace.
            endingCardIds:
              type: array
              items:
                type: string
                format: cuid2
              default: []
              description: >-
                Ending card ids that should trigger the workflow. Empty or
                omitted means every ending matches.
          additionalProperties: false
        ui:
          $ref: '#/components/schemas/WorkflowNodeUi'
      additionalProperties: false
    WorkflowSendEmailActionNode:
      type: object
      description: >-
        Send-email action node. Renders 1:1 with survey Follow-ups: recall-token
        body, question/hidden-field `to` resolution, and the branded Follow-ups
        email template.
      required:
        - id
        - type
        - actionType
        - config
      properties:
        id:
          type: string
          minLength: 1
        type:
          type: string
          enum:
            - action
        actionType:
          type: string
          enum:
            - send_email
        label:
          type: string
          minLength: 1
          maxLength: 120
          description: Optional display label shown in the builder.
        config:
          $ref: '#/components/schemas/WorkflowSendEmailActionConfig'
        ui:
          $ref: '#/components/schemas/WorkflowNodeUi'
      additionalProperties: false
    WorkflowEdge:
      type: object
      required:
        - id
        - source
        - target
      properties:
        id:
          type: string
          minLength: 1
        source:
          type: string
          minLength: 1
          description: Node id the edge starts from.
        target:
          type: string
          minLength: 1
          description: Node id the edge points to.
        sourceHandle:
          type: string
          minLength: 1
          description: >-
            Builder handle identifier on the source node. Reserved for branch
            nodes (`then`/`else`) in future scopes.
        targetHandle:
          type: string
          minLength: 1
          description: Builder handle identifier on the target node.
      additionalProperties: false
    WorkflowRunStatus:
      type: string
      enum:
        - queued
        - running
        - completed
        - failed
        - canceled
      description: >
        Workflow run lifecycle state. `completed`, `failed`, and `canceled` are
        terminal. Valid

        transitions: `queued` → `running`, `running` → `queued` (retry/backoff),
        `queued` →

        `canceled`, and `running` → `completed`/`failed`/`canceled`. Retries do
        not introduce a

        separate status; retry state is exposed via `attempt` and
        `nextAttemptAt`.
    WorkflowNodeUi:
      type: object
      description: >-
        Builder-only UI metadata that does not affect workflow execution.
        Additional keys are preserved.
      properties:
        position:
          type: object
          required:
            - x
            - 'y'
          properties:
            x:
              type: number
            'y':
              type: number
          additionalProperties: false
        collapsed:
          type: boolean
      additionalProperties: true
    WorkflowSendEmailActionConfig:
      type: object
      description: >-
        Send-email action with 1:1 field parity to survey Follow-ups
        (`ZSurveyFollowUpAction`).


        `to`, `subject` and `body` accept an empty string on create and update,
        so an author can save an unfinished draft. Completeness is enforced when
        the workflow has to run: enabling it returns **422**
        `workflow_not_executable` naming each blank field, and the dry run
        reports them as problems.
      required:
        - to
        - from
        - replyTo
        - subject
        - body
        - attachResponseData
      properties:
        to:
          type: string
          description: >
            Recipient: either a literal email address (e.g. a teammate) or the
            element id of a

            survey question / hidden field whose answer contains the
            respondent's email address

            (contact-info elements resolve index [2]) — the same semantics as
            the Follow-ups `to` field.


            A **literal** address must belong to someone who can access this
            workspace — an organization owner or manager, or a member of a team
            the

            workspace is shared with. Enabling a workflow whose literal
            recipient cannot access the workspace returns **422**
            `workflow_not_executable`, the

            dry run reports a `recipient_not_allowed` problem, and the runner
            refuses to send to it — so a

            workflow cannot forward response data to an arbitrary external
            inbox. An element-id `to`

            resolves to the respondent's own address and is never
            allowlist-checked.
        from:
          type: string
          format: email
        replyTo:
          type: array
          items:
            type: string
            format: email
        subject:
          type: string
          maxLength: 998
          description: >
            Email subject. Used verbatim (recall tokens are not expanded in the
            subject), the same as Follow-ups. Capped at RFC 5322's maximum line
            length.
        body:
          type: string
          maxLength: 100000
          description: >
            Email body as HTML with recall tokens
            (`#recall:[elementId]/fallback:x#`). Recall tokens

            are expanded against the response, the result is sanitized to a
            narrow HTML allowlist,

            and it is wrapped in the branded Follow-ups email template —
            identical rendering to survey Follow-ups.
        attachResponseData:
          type: boolean
        includeVariables:
          type: boolean
          default: false
        includeHiddenFields:
          type: boolean
          default: false
      additionalProperties: false
  securitySchemes:
    sessionAuth:
      type: apiKey
      in: cookie
      name: next-auth.session-token
      description: >
        NextAuth session JWT cookie. **Development:** often
        `next-auth.session-token`.

        **Production (HTTPS):** often `__Secure-next-auth.session-token`. Send
        the cookie your browser receives after sign-in.
    apiKeyAuth:
      type: apiKey
      in: header
      name: x-api-key
      description: >
        Management API key; must include **workspaceId** as an allowed workspace
        with read, write, or manage permission.

````