# AUTO-GENERATED tier-A mirror spec — do not edit.
# Source: openapi/runtime/workflows.yaml (runtime 0.71.2)
#  + policy: openapi/mirror-policies/workflows.json
# Regenerate: node scripts/generate-mirror-specs.mjs
openapi: 3.0.3
info:
  title: naturali.ai — Workflows API
  version: 1.0.0
  description: >-
    Workflows: the state machines tasks move through — states, the transitions between them, their
    guards and the automation each one fires — versioned so a change never rewrites the history of
    work already done. A fully runtime-backed module — this spec is generated verbatim from the
    runtime's own, re-rooted under /v1/projects/{project_id}. The project in the path is authorized
    by naturali and enforced upstream by the project's scoped credential.


    This module mirrors the upstream runtime verbatim (tier A, #304): paths are the runtime's own
    re-rooted under /v1/projects/{project_id}, and every field, method, status code and error shape
    passes through unchanged. Errors raised by the runtime arrive in its envelope; errors raised by
    naturali itself (authentication, project resolution, an unreachable runtime) use naturali's.
  contact:
    name: naturali.ai
    url: https://naturali.ai
servers:
  - url: "{baseUrl}"
    description: Host of your naturali.ai deployment; every path carries the /v1 prefix.
    variables:
      baseUrl:
        description: Base host URL.
        default: https://api.naturali.ai
tags:
  - name: Workflows
    description: Manage workflow definitions
security:
  - bearerAuth: []
  - oauth2:
      - mcp:access
paths:
  /v1/projects/{project_id}/workflows:
    get:
      description: Lists workflow definitions in a project.
      tags:
        - Workflows
      summary: List workflows
      operationId: listWorkflows
      parameters:
        - name: limit
          in: query
          required: false
          description: Maximum number of results to return
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 50
        - name: offset
          in: query
          required: false
          description: Number of results to skip
          schema:
            type: integer
            default: 0
      responses:
        "200":
          description: A list of workflows
          content:
            application/json:
              schema:
                type: object
                required:
                  - data
                  - total
                  - limit
                  - offset
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/Workflow"
                  total:
                    type: integer
                  limit:
                    type: integer
                  offset:
                    type: integer
        "401":
          description: Unauthorized
        "403":
          description: Forbidden
    post:
      description: Creates a new workflow definition. The definition is statically validated.
      tags:
        - Workflows
      summary: Create a workflow
      operationId: createWorkflow
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateWorkflowRequest"
      responses:
        "201":
          description: Workflow created
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Workflow"
        "400":
          description: Bad request (invalid definition)
        "401":
          description: Unauthorized
        "403":
          description: Forbidden
        "409":
          description: A workflow with this name already exists
    parameters:
      - $ref: "#/components/parameters/ProjectId"
  /v1/projects/{project_id}/workflows/{workflow_id}:
    get:
      description: Retrieves a workflow definition.
      tags:
        - Workflows
      summary: Get a workflow
      operationId: getWorkflow
      parameters:
        - name: workflow_id
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Workflow details
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Workflow"
        "401":
          description: Unauthorized
        "403":
          description: Forbidden
        "404":
          description: Workflow not found
    patch:
      description: Updates a workflow definition. Structural changes (states/transitions) are
        re-validated. Existing tasks in a removed state stay put but can only leave via transitions
        valid in the new definition.
      tags:
        - Workflows
      summary: Update a workflow
      operationId: updateWorkflow
      parameters:
        - name: workflow_id
          in: path
          required: true
          schema:
            type: string
        - $ref: "#/components/parameters/IfMatchVersion"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UpdateWorkflowRequest"
      responses:
        "200":
          description: Workflow updated
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Workflow"
        "400":
          description: Bad request (invalid definition)
        "401":
          description: Unauthorized
        "403":
          description: Forbidden
        "404":
          description: Workflow not found
        "409":
          $ref: "#/components/responses/VersionConflict"
    delete:
      description: Deletes a workflow. Rejected while open tasks exist.
      tags:
        - Workflows
      summary: Delete a workflow
      operationId: deleteWorkflow
      parameters:
        - name: workflow_id
          in: path
          required: true
          schema:
            type: string
      responses:
        "204":
          description: Workflow deleted
        "401":
          description: Unauthorized
        "403":
          description: Forbidden
        "404":
          description: Workflow not found
        "409":
          description: The workflow has open tasks and cannot be deleted
    parameters:
      - $ref: "#/components/parameters/ProjectId"
  /v1/projects/{project_id}/workflows/{workflow_id}/versions:
    get:
      description: >
        Returns the workflow's archived state machines, newest first. A version is written on create
        and on every subsequent write that changes the definition (`states`, `transitions`,
        `payload_schema`) — through the REST API or a formation apply alike. Metadata-only edits
        (name, description) do not archive a version. See
        [Versioning](/docs/modules/workflows#versioning).
      tags:
        - Workflows
      summary: List a workflow's versions
      operationId: listWorkflowVersions
      parameters:
        - name: workflow_id
          in: path
          required: true
          schema:
            type: string
        - name: limit
          in: query
          required: false
          description: Maximum number of results to return
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 50
        - name: offset
          in: query
          required: false
          description: Number of results to skip
          schema:
            type: integer
            default: 0
      responses:
        "200":
          description: List of workflow versions, newest first
          content:
            application/json:
              schema:
                type: object
                required:
                  - data
                  - total
                  - limit
                  - offset
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/WorkflowVersion"
                  total:
                    type: integer
                  limit:
                    type: integer
                  offset:
                    type: integer
        "401":
          description: Unauthorized
        "403":
          description: Forbidden
        "404":
          description: Workflow not found
    parameters:
      - $ref: "#/components/parameters/ProjectId"
  /v1/projects/{project_id}/workflows/{workflow_id}/versions/{version}:
    get:
      description: >
        Returns the exact state machine a given version describes. Every task records the version it
        entered on in `workflow_version` and runs on that machine for its whole life, so this is how
        you read the definition a task is actually being validated against — including a task whose
        workflow has been rewired since.
      tags:
        - Workflows
      summary: Fetch an archived workflow version
      operationId: getWorkflowVersion
      parameters:
        - name: workflow_id
          in: path
          required: true
          schema:
            type: string
        - name: version
          in: path
          required: true
          description: The archived version number
          schema:
            type: integer
            minimum: 1
      responses:
        "200":
          description: Archived workflow version
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/WorkflowVersion"
        "400":
          description: Bad request — version is not a positive integer
        "401":
          description: Unauthorized
        "403":
          description: Forbidden
        "404":
          description: Not found
    parameters:
      - $ref: "#/components/parameters/ProjectId"
  /v1/projects/{project_id}/workflows/{workflow_id}/versions/{version}/restore:
    post:
      description: >
        Writes an archived version's state machine back as the workflow's live definition, which
        archives it again as a **new** version rather than rewinding the counter — so a task pinned
        to any version in between still runs on the machine it entered on.


        The restore runs through the ordinary update path, so the archived definition goes through
        the same validation as an authored one. That includes resolving every `on_enter` dispatch
        target, so restoring a version whose agent or orchestration has since been deleted fails
        with `400` rather than writing a definition that would strand a task on entry. Restoring the
        definition the workflow already holds is a no-op and archives nothing. Tasks already in
        flight are unaffected either way — a restore is an ordinary edit, and pinning is what keeps
        it from reaching them.
      tags:
        - Workflows
      summary: Restore an archived workflow state machine
      operationId: restoreWorkflowVersion
      parameters:
        - name: workflow_id
          in: path
          required: true
          schema:
            type: string
        - name: version
          in: path
          required: true
          description: The archived version number
          schema:
            type: integer
            minimum: 1
      requestBody:
        required: false
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/RestoreWorkflowVersionRequest"
      responses:
        "200":
          description: The workflow, at its new version
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Workflow"
        "400":
          description: Bad request — version is not a positive integer, or the restored definition is invalid
        "401":
          description: Unauthorized
        "403":
          description: Forbidden
        "404":
          description: Not found
        "409":
          $ref: "#/components/responses/VersionConflict"
    parameters:
      - $ref: "#/components/parameters/ProjectId"
components:
  schemas:
    WorkflowState:
      type: object
      description: "A named state. Exactly one state must be `initial: true`; any number may be `terminal:
        true`. A `kind: human` state never dispatches — the task parks until a transition fires.
        `on_enter` (§5) dispatches exactly one of an agent generation (`kind: agent`, `agent_id`),
        an orchestration run (`kind: orchestration`, `orchestration_id`) or a tool call (`kind:
        tool`, `tool_id`, optional `operation_id`) on entry, optionally under a `retry` policy
        (`max_attempts` 1-10, `backoff_seconds`, `backoff_multiplier`) that re-runs execution
        failures before `on_failure` applies. A `tool` dispatch settles within the dispatch and is
        adjudicated by the same guardrails as an orchestration `tool` node; for anything that must
        wait (a delay, a poll, a multi-step pipeline, or an approval-gated tool), dispatch an
        orchestration instead."
      required:
        - name
      properties:
        name:
          type: string
        initial:
          type: boolean
        terminal:
          type: boolean
        kind:
          type: string
          description: "`human` marks a human-in-the-loop parking state: the state never dispatches (declaring
            `on_enter` on it is rejected at validation) and the task parks until a principal fires a
            transition."
        stalled_after:
          type: integer
          nullable: true
          description: Seconds a task may sit in this state before the stall sweeper emits a `tasks.stalled`
            event (once per stall episode, re-armed on the next transition). Must be a positive
            integer when set. Omit or null to never stall. The event does not move the task — route
            on it with a webhook/trigger.
        on_enter:
          type: object
          nullable: true
          description: What entering the state dispatches, and how the result routes. Omitted or null on a
            state that only parks.
          properties:
            dispatch:
              type: object
              description: "The one thing entering the state runs. `kind` picks it and names the id it needs:
                `agent` → `agent_id`, `orchestration` → `orchestration_id`, `tool` → `tool_id` (with
                an optional `operation_id`)."
              properties:
                kind:
                  type: string
                  enum:
                    - agent
                    - orchestration
                    - tool
                  description: Which target is dispatched.
                agent_id:
                  x-naturali-ref: agents
                  type: string
                  nullable: true
                  description: "The agent a `kind: agent` dispatch generates with."
                orchestration_id:
                  x-naturali-ref: orchestrations
                  type: string
                  nullable: true
                  description: "The orchestration a `kind: orchestration` dispatch starts a run of."
                tool_id:
                  x-naturali-ref: tools
                  type: string
                  nullable: true
                  description: "The tool a `kind: tool` dispatch calls."
                operation_id:
                  type: string
                  nullable: true
                  description: Which operation of a multi-operation tool to invoke — the same selector an
                    orchestration `tool` node takes.
                input_mapping:
                  type: object
                  nullable: true
                  additionalProperties: true
                  description: JSON Logic building the dispatch input from the task. Carried verbatim.
                payload_writes:
                  type: object
                  nullable: true
                  additionalProperties: true
                  description: JSON Logic expressions over `{task, result}` whose values are written into
                    `task.payload` when the dispatch completes, one named channel per key. Carried
                    verbatim.
            retry:
              type: object
              nullable: true
              description: Re-runs the dispatch's execution failures before `on_failure` applies. Omitted means a
                single attempt.
              properties:
                max_attempts:
                  type: integer
                  description: Attempts to make, 1–10.
                backoff_seconds:
                  type: number
                  nullable: true
                  description: Delay before the second attempt.
                backoff_multiplier:
                  type: number
                  nullable: true
                  description: "Factor the delay grows by per further attempt: `backoff_seconds *
                    backoff_multiplier^(n - 2)`."
            on_complete:
              type: array
              nullable: true
              description: Rules evaluated in order when the dispatch completes; the first whose `when` is truthy
                fires its transition.
              items:
                type: object
                properties:
                  when:
                    description: JSON Logic over `{task, result}`. Carried verbatim.
                  transition:
                    type: string
                    description: The transition to fire, by name.
            on_failure:
              type: string
              nullable: true
              description: "Transition fired when the dispatch fails after its last attempt. Omitted parks the
                task with `automation_status: failed`."
    WorkflowTransition:
      type: object
      description: A named, directional move. `from` is a list of source states; `to` is one target state.
        `guard` is a JSON Logic expression over `{task, transition, principal}` that must be truthy
        for the move to apply.
      required:
        - name
        - from
        - to
      properties:
        name:
          type: string
        from:
          type: array
          items:
            type: string
        to:
          type: string
        guard:
          type: object
          nullable: true
        requires_approval:
          type: boolean
          description: Gate the transition behind a human approval. When `true`, firing the transition (by
            anyone other than the approval resolution itself) parks a pending `ApprovalItem` instead
            of moving the task; the task exposes `pending_transition` until the item resolves.
            Approval fires the transition as the `approval` principal (its guard re-evaluated at
            resolution time); rejection or expiry clears the gate and appends a history note.
    Workflow:
      type: object
      properties:
        id:
          type: string
        project_id:
          type: string
        name:
          type: string
        description:
          type: string
          nullable: true
        version:
          type: integer
          description: >
            Incremented on every write that changes the state machine; prior versions are archived.
            A task pins the version it entered on, so these fields are a draft for tasks created
            from now on rather than a live rewrite of the ones already in flight.
          example: 1
        states:
          type: array
          items:
            $ref: "#/components/schemas/WorkflowState"
        transitions:
          type: array
          items:
            $ref: "#/components/schemas/WorkflowTransition"
        payload_schema:
          type: object
          nullable: true
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
    CreateWorkflowRequest:
      type: object
      required:
        - name
        - states
        - transitions
      additionalProperties: false
      properties:
        name:
          type: string
        description:
          type: string
          nullable: true
        states:
          type: array
          items:
            $ref: "#/components/schemas/WorkflowState"
        transitions:
          type: array
          items:
            $ref: "#/components/schemas/WorkflowTransition"
        payload_schema:
          type: object
          nullable: true
        version_label:
          type: string
          description: Optional tag for the version this create archives, e.g. `initial`.
          example: initial
    UpdateWorkflowRequest:
      type: object
      additionalProperties: false
      properties:
        name:
          type: string
        description:
          type: string
          nullable: true
        states:
          type: array
          items:
            $ref: "#/components/schemas/WorkflowState"
        transitions:
          type: array
          items:
            $ref: "#/components/schemas/WorkflowTransition"
        payload_schema:
          type: object
          nullable: true
        version_label:
          type: string
          description: Optional tag for the version this write archives, e.g. `pre-rewire`. Ignored when the
            write changes no definition field, since no version is archived.
          example: pre-rewire
        expected_version:
          description: Refuses the write unless the resource is at this version.
          allOf:
            - $ref: "#/components/schemas/ExpectedVersion"
    WorkflowVersion:
      type: object
      description: An immutable archive of a workflow's state machine at one version.
      properties:
        id:
          type: string
          description: Public ID of the archived version
          example: wfl_ver_V1StGXR8Z5jdHi6B
        workflow_id:
          x-naturali-ref: workflows
          type: string
          description: Public ID of the workflow this version belongs to
          example: wfl_V1StGXR8Z5jdHi6B
        version:
          type: integer
          description: The archived version number
          example: 1
        config:
          type: object
          additionalProperties: true
          description: >-
            The workflow's versioned surface as it stood at this version: `states`, `transitions`
            and `payload_schema`. Name and description are metadata — bumping the version when one
            of them changes would make two version numbers denote the same state machine, which is
            exactly what a task cites.


            Deliberately open rather than a fixed schema: an archive written by an earlier release
            of the runtime reflects the workflow surface **of its own time**, so it may carry fields
            the current API no longer documents.
          properties:
            states:
              type: array
              items:
                $ref: "#/components/schemas/WorkflowState"
            transitions:
              type: array
              items:
                $ref: "#/components/schemas/WorkflowTransition"
            payload_schema:
              type: object
              nullable: true
        label:
          type: string
          nullable: true
          description: Optional human tag for this version, e.g. `pre-rewire`. Set from the `version_label`
            field of a write, the `label` field of a restore, or generated for one.
          example: restored from v2
        created_by:
          x-naturali-ref: users
          type: string
          nullable: true
          description: Public ID of the user whose action produced this version. Null for writes with no
            request user behind them.
        created_at:
          type: string
          format: date-time
    RestoreWorkflowVersionRequest:
      type: object
      additionalProperties: false
      properties:
        label:
          type: string
          description: Optional tag for the new version the restore archives. Defaults to `restored from vN`.
          example: rollback to pre-rewire
    ErrorResponse:
      type: object
      required:
        - error
      properties:
        error:
          type: object
          description: Structured error. Every error response uses this shape, so `code` can be read without
            first checking the type of `error`.
          required:
            - code
            - message
          properties:
            code:
              type: string
              description: "Machine-readable error code: lower_snake for an error naturali raises
                (`access_denied`), UPPER_SNAKE for one the runtime reports (`RESOURCE_NOT_FOUND`)."
              example: access_denied
            message:
              type: string
              description: Human-readable explanation.
              example: Your role in this project does not carry this action.
            details:
              type: object
              additionalProperties: true
              description: Structured context for an error naturali raises, such as the `resource` and `limit` of
                a `plan_limit_reached`.
            meta:
              type: object
              description: Structured context for an error the runtime reports.
    ExpectedVersion:
      type: integer
      minimum: 1
      nullable: true
      description: >-
        The version the caller believes the resource holds. When the resource is at any other
        version the write is refused with `409 VERSION_CONFLICT` and nothing is written;
        `meta.current_version` on that response names the version in force.


        Omit it to write unconditionally. Omitting it does not make the write unordered: two writes
        that reach the server together are still serialized, and the one whose version was taken
        first is refused the same way. What the field adds is refusing a write whose author read the
        resource some time ago and has not seen what happened since.


        The `If-Match` header carries the same precondition for a client that prefers the HTTP
        spelling. Sending both with different versions is `400 VALIDATION_FAILED`.
      example: 3
  parameters:
    ProjectId:
      name: project_id
      in: path
      required: true
      description: Project public ID (proj_ prefix).
      schema:
        type: string
        example: proj_V1StGXR8Z5jdHi6B
    IfMatchVersion:
      name: If-Match
      in: header
      required: false
      description: The version the caller believes the resource holds, as an entity tag (`3` or `"3"`).
        Equivalent to `expected_version` in the request body; `*` states no precondition beyond the
        resource existing. A mismatch is `409 VERSION_CONFLICT`.
      schema:
        type: string
      example: "3"
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      description: A naturali API key (nat_sk_…) or a session JWT.
    oauth2:
      type: oauth2
      description: "A connected app's OAuth access token, issued by this API's authorization server
        (discovery: /.well-known/oauth-authorization-server). Its one scope carries every operation,
        confined to the projects the user chose when approving the app."
      flows:
        authorizationCode:
          authorizationUrl: https://api.naturali.ai/authorize
          tokenUrl: https://api.naturali.ai/token
          refreshUrl: https://api.naturali.ai/token
          scopes:
            mcp:access: Every operation this API serves, on the projects the grant covers.
  responses:
    VersionConflict:
      description: "The resource has moved past the version this write read (`VERSION_CONFLICT`): a stated
        `expected_version` or `If-Match` no longer matches, or a concurrent write took the version
        first. `meta.current_version` names the version in force. Nothing is written."
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
