openapi: 3.0.3
info:
  title: SOAT Playbooks API
  version: 1.0.0
  description: >-
    API for playbooks — versioned templates of nodes whose inputs a person
    fills or an earlier node produces — and the cases that fill one version
    for one subject.
  contact:
    name: SOAT Team
    url: https://github.com/ttoss/soat
servers:
  - url: '{baseUrl}'
    description: Base URL of your SOAT deployment (e.g. https://your-soat.com or http://localhost:5047)
    variables:
      baseUrl:
        description: The base URL of your SOAT deployment
        default: http://localhost:5047
tags:
  - name: Playbooks
    description: Manage playbooks and the cases filled against them
security:
  - bearerAuth: []
paths:
  /api/v1/playbooks:
    get:
      tags:
        - Playbooks
      summary: List playbooks
      description: Returns the playbooks defined in a project, newest first.
      operationId: listPlaybooks
      parameters:
        - name: project_id
          in: query
          description: Project ID (required if not using project key auth)
          schema:
            type: string
            example: proj_V1StGXR8Z5jdHi6B
        - $ref: './pagination.yaml#/components/parameters/Limit'
        - $ref: './pagination.yaml#/components/parameters/Offset'
      responses:
        '200':
          description: List of playbooks
          content:
            application/json:
              schema:
                type: object
                required:
                  - data
                  - total
                  - limit
                  - offset
                properties:
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/Playbook'
                  total:
                    type: integer
                  limit:
                    type: integer
                  offset:
                    type: integer
        '401':
          description: Unauthorized
        '403':
          description: Forbidden
    post:
      tags:
        - Playbooks
      summary: Create a playbook
      description: >-
        Creates a playbook at version 1. The definition is refused with
        `400 PLAYBOOK_VALIDATION_FAILED` when it holds a duplicate node or
        input, an input reading a missing node or field, an `execute` node
        without an executor or another kind with one, transformations on a
        non-final node, or a cycle; `meta.problems` lists every one. A duplicate
        `name` in the project is `409 NAME_CONFLICT`.


        The caller becomes the version's author, whom its executors run as, so
        it must be allowed to start every executor the definition names
        (`403 PLAYBOOK_EXECUTOR_FORBIDDEN`). A definition with executors
        written with a trigger or OAuth token is `409
        PLAYBOOK_AUTHOR_UNAVAILABLE`: those carry their boundary in the token,
        so no run can act as them.
      operationId: createPlaybook
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreatePlaybookRequest'
      responses:
        '201':
          description: Playbook created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Playbook'
        '400':
          description: Bad request — a malformed or invalid definition
        '401':
          description: Unauthorized
        '403':
          description: Forbidden
        '409':
          description: A playbook with this name already exists in the project

  /api/v1/playbooks/validate:
    post:
      tags:
        - Playbooks
      summary: Validate a playbook definition
      description: >-
        Answers what a create or update would be told about a definition,
        without writing: `valid`, and every graph problem it holds. A definition
        whose shape cannot be read is `400 VALIDATION_FAILED`, as on a write.
      operationId: validatePlaybook
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ValidatePlaybookRequest'
      responses:
        '200':
          description: Validation result
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PlaybookValidationResult'
        '400':
          description: Bad request — the definition's shape cannot be read
        '401':
          description: Unauthorized

  /api/v1/playbooks/{playbook_id}:
    get:
      tags:
        - Playbooks
      summary: Get a playbook
      description: Returns a playbook with its current definition and version.
      operationId: getPlaybook
      x-soat-resource:
        kind: playbook
        from: playbook_id
      parameters:
        - $ref: '#/components/parameters/PlaybookId'
      responses:
        '200':
          description: The playbook
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Playbook'
        '401':
          description: Unauthorized
        '404':
          description: Playbook not found
    patch:
      tags:
        - Playbooks
      summary: Update a playbook
      description: >-
        Updates any of `name`, `description` and `definition`. Only a change to
        `definition` archives a new version; a rename or a rewrite of the
        definition the playbook already holds leaves `version` where it is. A
        case keeps the version it was created on.


        A new version is authored by the caller, checked as on create: `403
        PLAYBOOK_EXECUTOR_FORBIDDEN` for an executor it may not start, `409
        PLAYBOOK_AUTHOR_UNAVAILABLE` for a trigger or OAuth token.
      operationId: updatePlaybook
      x-soat-resource:
        kind: playbook
        from: playbook_id
      parameters:
        - $ref: '#/components/parameters/PlaybookId'
        - $ref: './concurrency.yaml#/components/parameters/IfMatchVersion'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UpdatePlaybookRequest'
      responses:
        '200':
          description: Playbook updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Playbook'
        '400':
          description: Bad request — a malformed or invalid definition
        '401':
          description: Unauthorized
        '403':
          description: Forbidden
        '404':
          description: Playbook not found
        '409':
          $ref: './concurrency.yaml#/components/responses/VersionConflict'
    delete:
      tags:
        - Playbooks
      summary: Delete a playbook
      description: >-
        Deletes a playbook and its version archive. A playbook with cases is
        refused with `409 PLAYBOOK_HAS_CASES`.
      operationId: deletePlaybook
      x-soat-resource:
        kind: playbook
        from: playbook_id
      parameters:
        - $ref: '#/components/parameters/PlaybookId'
      responses:
        '204':
          description: Playbook deleted
        '401':
          description: Unauthorized
        '403':
          description: Forbidden
        '404':
          description: Playbook not found
        '409':
          description: The playbook has cases

  /api/v1/playbooks/{playbook_id}/versions:
    get:
      tags:
        - Playbooks
      summary: List a playbook's versions
      description: >-
        Returns the playbook's archived definitions, newest first. A version is
        written on create and on every write that changes `definition`.
      operationId: listPlaybookVersions
      x-soat-resource:
        kind: playbook
        from: playbook_id
      parameters:
        - $ref: '#/components/parameters/PlaybookId'
        - $ref: './pagination.yaml#/components/parameters/Limit'
        - $ref: './pagination.yaml#/components/parameters/Offset'
      responses:
        '200':
          description: List of playbook versions, newest first
          content:
            application/json:
              schema:
                type: object
                required:
                  - data
                  - total
                  - limit
                  - offset
                properties:
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/PlaybookVersion'
                  total:
                    type: integer
                  limit:
                    type: integer
                  offset:
                    type: integer
        '401':
          description: Unauthorized
        '404':
          description: Playbook not found

  /api/v1/playbooks/{playbook_id}/versions/{version}:
    get:
      tags:
        - Playbooks
      summary: Fetch an archived playbook version
      description: >-
        Returns the definition a version held: what a case pinned to it is
        evaluated against.
      operationId: getPlaybookVersion
      x-soat-resource:
        kind: playbook
        from: playbook_id
      parameters:
        - $ref: '#/components/parameters/PlaybookId'
        - $ref: '#/components/parameters/Version'
      responses:
        '200':
          description: Archived playbook version
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PlaybookVersion'
        '400':
          description: Bad request — version is not a positive integer
        '401':
          description: Unauthorized
        '404':
          description: Not found

  /api/v1/playbooks/{playbook_id}/versions/{version}/restore:
    post:
      tags:
        - Playbooks
      summary: Restore an archived playbook version
      description: >-
        Writes an archived definition back as the playbook's live one, which
        archives it again as a new version rather than rewinding the counter.
        Restoring the definition the playbook already holds is a no-op. The
        caller authors the new version, checked as on create.
      operationId: restorePlaybookVersion
      x-soat-resource:
        kind: playbook
        from: playbook_id
      parameters:
        - $ref: '#/components/parameters/PlaybookId'
        - $ref: '#/components/parameters/Version'
      requestBody:
        required: false
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/RestorePlaybookVersionRequest'
      responses:
        '200':
          description: The playbook, at its new version
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Playbook'
        '400':
          description: Bad request — version is not a positive integer
        '401':
          description: Unauthorized
        '403':
          description: Forbidden
        '404':
          description: Not found
        '409':
          $ref: './concurrency.yaml#/components/responses/VersionConflict'

  /api/v1/playbooks/{playbook_id}/cases:
    post:
      tags:
        - Playbooks
      summary: Create a case
      description: >-
        Creates a case for one subject on the playbook's current version. The
        case stays on that version however the playbook changes later.
      operationId: createPlaybookCase
      x-soat-resource:
        kind: playbook
        from: playbook_id
      parameters:
        - $ref: '#/components/parameters/PlaybookId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreatePlaybookCaseRequest'
      responses:
        '201':
          description: Case created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PlaybookCase'
        '400':
          description: Bad request — subject is missing
        '401':
          description: Unauthorized
        '403':
          description: Forbidden
        '404':
          description: Playbook not found

  /api/v1/playbook-cases:
    get:
      tags:
        - Playbooks
      summary: List playbook cases
      description: >-
        Returns a project's cases, newest first, without node states; read one
        case for those.
      operationId: listPlaybookCases
      parameters:
        - name: project_id
          in: query
          description: Project ID (required if not using project key auth)
          schema:
            type: string
            example: proj_V1StGXR8Z5jdHi6B
        - name: playbook_id
          in: query
          description: Only the cases of this playbook
          schema:
            type: string
            example: pbk_V1StGXR8Z5jdHi6B
        - name: automation_status
          in: query
          required: false
          description: >-
            Only the cases in these automation statuses. Repeat the parameter to
            OR values. A value outside the enum, empty string included, is a
            `400`.
          schema:
            type: array
            items:
              $ref: '#/components/schemas/PlaybookAutomationStatus'
          style: form
          explode: true
        - $ref: './pagination.yaml#/components/parameters/Limit'
        - $ref: './pagination.yaml#/components/parameters/Offset'
      responses:
        '200':
          description: List of cases
          content:
            application/json:
              schema:
                type: object
                required:
                  - data
                  - total
                  - limit
                  - offset
                properties:
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/PlaybookCaseSummary'
                  total:
                    type: integer
                  limit:
                    type: integer
                  offset:
                    type: integer
        '401':
          description: Unauthorized
        '403':
          description: Forbidden

  /api/v1/playbook-cases/{case_id}:
    get:
      tags:
        - Playbooks
      summary: Get a case
      description: >-
        Returns a case with every node's state, computed from its entries on
        each read and never stored.
      operationId: getPlaybookCase
      x-soat-resource:
        kind: playbook_case
        from: case_id
      parameters:
        - $ref: '#/components/parameters/CaseId'
      responses:
        '200':
          description: The case
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PlaybookCase'
        '401':
          description: Unauthorized
        '404':
          description: Case not found
    delete:
      tags:
        - Playbooks
      summary: Delete a case
      description: Deletes a case and every entry written to it.
      operationId: deletePlaybookCase
      x-soat-resource:
        kind: playbook_case
        from: case_id
      parameters:
        - $ref: '#/components/parameters/CaseId'
      responses:
        '204':
          description: Case deleted
        '401':
          description: Unauthorized
        '403':
          description: Forbidden
        '404':
          description: Case not found

  /api/v1/playbook-cases/{case_id}/inputs:
    post:
      tags:
        - Playbooks
      summary: Write a case's inputs
      description: >-
        Writes a batch of manual inputs, all or none. Each write names the
        revision it read in `expected_revision`, `null` for a field still unset;
        when any field has moved on the whole batch is refused with
        `409 PLAYBOOK_INPUT_CONFLICT` and `meta.conflicts` names each field. An
        input the case's version does not declare, one that reads another node,
        or a value its schema refuses is `400 PLAYBOOK_INPUT_INVALID`. A value
        equal to the field's current one writes no revision. Once written, every
        node the write makes runnable is queued to run, unless the case is
        paused. With `wait: true` it answers once
        every execution queued, and every one those queue, has settled or
        parked. A write made with a credential the case's own work runs as — the
        case, one of its executions in flight, or the task or run one started —
        is `403 PLAYBOOK_SELF_WRITE`.
      operationId: writePlaybookCaseInputs
      x-soat-resource:
        kind: playbook_case
        from: case_id
      parameters:
        - $ref: '#/components/parameters/CaseId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/WritePlaybookCaseInputsRequest'
      responses:
        '200':
          description: The case after the write
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PlaybookCase'
        '400':
          description: Bad request — an unknown input, a non-manual input or a value its schema refuses
        '401':
          description: Unauthorized
        '403':
          description: Forbidden
        '404':
          description: Case not found
        '409':
          description: A field changed since it was read; nothing was written

  /api/v1/playbook-cases/{case_id}/missing:
    get:
      tags:
        - Playbooks
      summary: List what a node still needs
      description: >-
        Returns the manual inputs still unset, or refused by their schema,
        upstream of `node_id`, grouped in waves: nothing in a later wave can run
        before the earlier ones' required inputs are filled. Optional inputs are
        listed with `required: false`; they never block a node.
      operationId: getPlaybookCaseMissing
      x-soat-resource:
        kind: playbook_case
        from: case_id
      parameters:
        - $ref: '#/components/parameters/CaseId'
        - name: node_id
          in: query
          required: true
          description: The ID of the node to reach, usually a final node
          schema:
            type: string
            example: pricing
      responses:
        '200':
          description: The missing inputs, by wave
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PlaybookCaseMissing'
        '400':
          description: Bad request — node_id names no node of the case's version
        '401':
          description: Unauthorized
        '404':
          description: Case not found

  /api/v1/playbook-cases/{case_id}/entries:
    get:
      tags:
        - Playbooks
      summary: List a case's entries
      description: >-
        Returns every value written to the case, newest first. Entries are never
        rewritten, so this is the case's full history.
      operationId: listPlaybookCaseEntries
      x-soat-resource:
        kind: playbook_case
        from: case_id
      parameters:
        - $ref: '#/components/parameters/CaseId'
        - name: node_id
          in: query
          description: Only the entries of this node
          schema:
            type: string
            example: revenue
        - $ref: './pagination.yaml#/components/parameters/Limit'
        - $ref: './pagination.yaml#/components/parameters/Offset'
      responses:
        '200':
          description: List of entries, newest first
          content:
            application/json:
              schema:
                type: object
                required:
                  - data
                  - total
                  - limit
                  - offset
                properties:
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/PlaybookEntry'
                  total:
                    type: integer
                  limit:
                    type: integer
                  offset:
                    type: integer
        '401':
          description: Unauthorized
        '404':
          description: Case not found

  /api/v1/playbook-cases/{case_id}/nodes/{node}/run:
    post:
      tags:
        - Playbooks
      summary: Run a node of a case
      description: >-
        Runs one node now. The cascade already runs every node whose inputs
        are set once a write, a settle or a resume makes it runnable, and never
        reruns a `failed` or `done` one: this is how a failed node is retried,
        and how a `done` node is recomputed after an upgrade changed its
        executor. A `collect`
        node takes its own input values as its output, any other node starts
        its executor with the node's input values mapped through the
        executor's `input`. The executor runs as the
        author of the case's playbook version, whose permission to start it is
        checked again here (`403 PLAYBOOK_EXECUTOR_FORBIDDEN`); an author that
        no longer exists is `409 PLAYBOOK_AUTHOR_UNAVAILABLE`. Only a `ready`,
        `stale`, `failed` or `done` node with every required input set runs;
        anything else is `409 PLAYBOOK_NODE_NOT_RUNNABLE`. A paused case is `409
        PLAYBOOK_CASE_PAUSED`, and a case in a paused project `409
        PROJECT_PAUSED`.


        The output is written as an entry when the execution settles. A value
        equal to the node's current output writes no revision. If the node's
        inputs change while it runs, the execution settles as `superseded` and
        writes nothing.


        Without `wait` the execution answers `running` and settles in the
        background. With `wait: true` it answers once the executor settles, or
        once it parks: a workflow task still open, or an orchestration run
        waiting on a person or a timer, settles the node when it closes or
        ends.
      operationId: runPlaybookCaseNode
      x-soat-resource:
        kind: playbook_case
        from: case_id
      parameters:
        - $ref: '#/components/parameters/CaseId'
        - $ref: '#/components/parameters/NodeKey'
      requestBody:
        required: false
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/RunPlaybookCaseNodeRequest'
      responses:
        '201':
          description: The execution, `running` or settled
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PlaybookExecution'
        '400':
          description: Bad request — a node the case's version does not declare
        '401':
          description: Unauthorized
        '403':
          description: Forbidden, or the author may not start the executor
        '404':
          description: Case not found
        '409':
          description: The node cannot run now, the case is paused, or the version has no author to run as

  /api/v1/playbook-cases/{case_id}/executions:
    get:
      tags:
        - Playbooks
      summary: List a case's executions
      description: Returns every run of the case's nodes, newest first.
      operationId: listPlaybookCaseExecutions
      x-soat-resource:
        kind: playbook_case
        from: case_id
      parameters:
        - $ref: '#/components/parameters/CaseId'
        - name: node_id
          in: query
          description: Only the executions of this node
          schema:
            type: string
            example: revenue
        - $ref: './pagination.yaml#/components/parameters/Limit'
        - $ref: './pagination.yaml#/components/parameters/Offset'
      responses:
        '200':
          description: List of executions, newest first
          content:
            application/json:
              schema:
                type: object
                required:
                  - data
                  - total
                  - limit
                  - offset
                properties:
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/PlaybookExecution'
                  total:
                    type: integer
                  limit:
                    type: integer
                  offset:
                    type: integer
        '401':
          description: Unauthorized
        '404':
          description: Case not found

  /api/v1/playbook-cases/{case_id}/executions/{execution_id}:
    get:
      tags:
        - Playbooks
      summary: Get an execution
      description: Returns one run of a case's node.
      operationId: getPlaybookCaseExecution
      x-soat-resource:
        kind: playbook_case
        from: case_id
      parameters:
        - $ref: '#/components/parameters/CaseId'
        - $ref: '#/components/parameters/ExecutionId'
      responses:
        '200':
          description: The execution
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PlaybookExecution'
        '401':
          description: Unauthorized
        '404':
          description: Case or execution not found

  /api/v1/playbook-cases/{case_id}/nodes/{node}/exports:
    post:
      tags:
        - Playbooks
      summary: Export a final node
      description: >-
        Renders one revision of a final node as files, one export per
        transformation: each transformation's tool is called with the case's
        `case_id` and `subject`, the `node_id`, the `revision` and that revision's
        `output`, and must answer a file (`execute.response_mode: base64`) of
        its `format`'s content type. The file is kept in the project's files
        under `/.system/playbooks/`. An export never feeds a node.


        Without `revision` the node's current output is exported, which needs
        the node `done` (`409 PLAYBOOK_NODE_NOT_EXPORTABLE`); a `revision`
        names an earlier output or confirmation of the node. Each tool runs as
        the author of the case's playbook version, whose permission to call it
        is checked here (`403 PLAYBOOK_EXECUTOR_FORBIDDEN`). A paused case is
        `409 PLAYBOOK_CASE_PAUSED`, and a case in a paused project `409
        PROJECT_PAUSED`.


        Without `wait` every export answers `running` and settles in the
        background; with `wait: true` it answers once each has its file or its
        error.
      operationId: exportPlaybookCaseNode
      x-soat-resource:
        kind: playbook_case
        from: case_id
      parameters:
        - $ref: '#/components/parameters/CaseId'
        - $ref: '#/components/parameters/NodeKey'
      requestBody:
        required: false
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ExportPlaybookCaseNodeRequest'
      responses:
        '201':
          description: One export per transformation, `running` or settled
          content:
            application/json:
              schema:
                type: object
                required:
                  - data
                properties:
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/PlaybookExport'
        '400':
          description: >-
            Bad request — a node the version does not declare or that has no
            transformations, a transformation it does not declare, or a
            revision that is no output of the node
        '401':
          description: Unauthorized
        '403':
          description: Forbidden, or the author may not call a transformation's tool
        '404':
          description: Case not found
        '409':
          description: >-
            The node has no current output, the case is paused, or the version
            has no author to run as

  /api/v1/playbook-cases/{case_id}/exports:
    get:
      tags:
        - Playbooks
      summary: List a case's exports
      description: Returns every file rendered from the case's final nodes, newest first.
      operationId: listPlaybookCaseExports
      x-soat-resource:
        kind: playbook_case
        from: case_id
      parameters:
        - $ref: '#/components/parameters/CaseId'
        - name: node_id
          in: query
          description: Only the exports of this node
          schema:
            type: string
            example: pricing
        - name: transformation
          in: query
          description: Only the exports of this transformation
          schema:
            type: string
            example: proposal
        - $ref: './pagination.yaml#/components/parameters/Limit'
        - $ref: './pagination.yaml#/components/parameters/Offset'
      responses:
        '200':
          description: List of exports, newest first
          content:
            application/json:
              schema:
                type: object
                required:
                  - data
                  - total
                  - limit
                  - offset
                properties:
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/PlaybookExport'
                  total:
                    type: integer
                  limit:
                    type: integer
                  offset:
                    type: integer
        '401':
          description: Unauthorized
        '404':
          description: Case not found

  /api/v1/playbook-cases/{case_id}/exports/{export_id}:
    get:
      tags:
        - Playbooks
      summary: Get an export
      description: Returns one file rendered from a case's final node.
      operationId: getPlaybookCaseExport
      x-soat-resource:
        kind: playbook_case
        from: case_id
      parameters:
        - $ref: '#/components/parameters/CaseId'
        - $ref: '#/components/parameters/ExportId'
      responses:
        '200':
          description: The export
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PlaybookExport'
        '401':
          description: Unauthorized
        '404':
          description: Case or export not found

  /api/v1/playbook-cases/{case_id}/upgrade:
    post:
      tags:
        - Playbooks
      summary: Upgrade a case
      description: >-
        Moves a case to another version of its playbook: the playbook's
        current version, or the `version` named, an earlier one included.
        Values carry over by node ID, and every node's state is read against
        the new version: a node added is open, a node removed leaves the
        structure while its entries stay, a node whose input reads another
        field is `stale`, and a value its input's new schema refuses is
        missing again. The cascade then runs what became runnable. With `wait: true` it answers once
        every execution queued, and every one those queue, has settled or
        parked. Each
        upgrade is recorded with what moved; the version the case is already
        on records nothing.


        An execution running is `409 PLAYBOOK_CASE_BUSY`; one queued but not
        started settles `superseded`. An upgrade made with a credential the
        case's own work runs as is `403 PLAYBOOK_SELF_WRITE`.
      operationId: upgradePlaybookCase
      x-soat-resource:
        kind: playbook_case
        from: case_id
      parameters:
        - $ref: '#/components/parameters/CaseId'
      requestBody:
        required: false
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UpgradePlaybookCaseRequest'
      responses:
        '200':
          description: The case on its new version
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PlaybookCase'
        '400':
          description: Bad request — a version the playbook does not have
        '401':
          description: Unauthorized
        '403':
          description: Forbidden, or an upgrade from the case's own work
        '404':
          description: Case not found
        '409':
          description: An execution of the case is running

  /api/v1/playbook-cases/{case_id}/upgrades:
    get:
      tags:
        - Playbooks
      summary: List a case's upgrades
      description: Returns every version change of the case, newest first.
      operationId: listPlaybookCaseUpgrades
      x-soat-resource:
        kind: playbook_case
        from: case_id
      parameters:
        - $ref: '#/components/parameters/CaseId'
        - $ref: './pagination.yaml#/components/parameters/Limit'
        - $ref: './pagination.yaml#/components/parameters/Offset'
      responses:
        '200':
          description: List of upgrades, newest first
          content:
            application/json:
              schema:
                type: object
                required:
                  - data
                  - total
                  - limit
                  - offset
                properties:
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/PlaybookUpgrade'
                  total:
                    type: integer
                  limit:
                    type: integer
                  offset:
                    type: integer
        '401':
          description: Unauthorized
        '404':
          description: Case not found

  /api/v1/playbook-cases/{case_id}/pause:
    post:
      tags:
        - Playbooks
      summary: Pause a case
      description: >-
        Stops the case starting anything: no node is queued and no queued
        execution starts until it is resumed. Executions already running
        settle. Input writes are still accepted. Pausing a paused case keeps the
        first pause.
      operationId: pausePlaybookCase
      x-soat-resource:
        kind: playbook_case
        from: case_id
      parameters:
        - $ref: '#/components/parameters/CaseId'
      responses:
        '200':
          description: The paused case
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PlaybookCase'
        '401':
          description: Unauthorized
        '403':
          description: Forbidden
        '404':
          description: Case not found

  /api/v1/playbook-cases/{case_id}/resume:
    post:
      tags:
        - Playbooks
      summary: Resume a case
      description: >-
        Lifts the pause and starts what became runnable while the case was
        paused. A case that is not paused is `409 PLAYBOOK_CASE_NOT_PAUSED`;
        one in a paused project is `409 PROJECT_PAUSED` and stays paused until
        the project resumes.
      operationId: resumePlaybookCase
      x-soat-resource:
        kind: playbook_case
        from: case_id
      parameters:
        - $ref: '#/components/parameters/CaseId'
      responses:
        '200':
          description: The resumed case
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PlaybookCase'
        '401':
          description: Unauthorized
        '403':
          description: Forbidden
        '404':
          description: Case not found
        '409':
          description: The case is not paused

  /api/v1/playbook-cases/{case_id}/nodes/{node}/confirmation:
    post:
      tags:
        - Playbooks
      summary: Confirm a node of a case
      description: >-
        Approves or rejects a `confirm` node on the inputs the confirmer read.
        `expected_revisions` names the revision of each of the node's inputs as
        read (`nodes[].inputs[].revision` on the case); an input that moved
        since is `409 PLAYBOOK_INPUT_CONFLICT`, `meta.conflicts` naming each,
        and nothing is written.


        An approval writes a `confirmation` entry whose value is the node's
        input values: it is the node's output, and the nodes reading it run
        next. With `wait: true` it answers once
        every execution queued, and every one those queue, has settled or
        parked. A rejection writes a `rejection` entry with the `reason`: the node
        is `rejected`, and every node reading it is blocked, until its inputs
        change or it is approved. Another node, or a `confirm` node with a
        required input unset or reading a node that is not `done`, is `409
        PLAYBOOK_NODE_NOT_CONFIRMABLE`. A confirmation made with a credential the
        case's own work runs as is `403 PLAYBOOK_SELF_WRITE`.
      operationId: confirmPlaybookCaseNode
      x-soat-resource:
        kind: playbook_case
        from: case_id
      parameters:
        - $ref: '#/components/parameters/CaseId'
        - $ref: '#/components/parameters/NodeKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ConfirmPlaybookCaseNodeRequest'
      responses:
        '200':
          description: The case after the confirmation
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PlaybookCase'
        '400':
          description: Bad request — an unknown decision, a rejection without a reason, or a node the version does not declare
        '401':
          description: Unauthorized
        '403':
          description: Forbidden, or a write from the case's own work
        '404':
          description: Case not found
        '409':
          description: The node cannot be confirmed now, or an input changed since it was read

components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT
  parameters:
    PlaybookId:
      name: playbook_id
      in: path
      required: true
      description: The playbook ID
      schema:
        type: string
        example: pbk_V1StGXR8Z5jdHi6B
    CaseId:
      name: case_id
      in: path
      required: true
      description: The case ID
      schema:
        type: string
        example: pbk_case_V1StGXR8Z5jdHi6B
    NodeKey:
      name: node
      in: path
      required: true
      description: The ID of a node of the case's playbook version
      schema:
        type: string
        example: revenue
    ExecutionId:
      name: execution_id
      in: path
      required: true
      description: The execution ID
      schema:
        type: string
        example: pbk_exe_V1StGXR8Z5jdHi6B
    ExportId:
      name: export_id
      in: path
      required: true
      description: The export ID
      schema:
        type: string
        example: pbk_exp_V1StGXR8Z5jdHi6B
    Version:
      name: version
      in: path
      required: true
      description: The archived version number
      schema:
        type: integer
        minimum: 1
  schemas:
    PlaybookInputSource:
      description: >-
        `manual` for an input a person or an integration writes, or the field of
        an earlier node it reads: `output[.path]`, or `input.<name>[.path]`.
        Sources are the playbook's edges.
      oneOf:
        - type: string
          enum:
            - manual
        - type: object
          additionalProperties: false
          required:
            - node_id
            - field
          properties:
            node_id:
              type: string
              description: The ID of the node it reads
              example: revenue
            field:
              type: string
              example: output.annual_revenue
    PlaybookInput:
      type: object
      additionalProperties: false
      required:
        - name
        - schema
        - required
        - source
      properties:
        name:
          type: string
          description: Letters, digits and underscores, starting with a letter or underscore
          example: revenue
        schema:
          type: object
          description: The JSON Schema a value must satisfy
          example:
            type: number
        required:
          type: boolean
          description: The node cannot run until a required input is set
        description:
          type: string
          description: What to ask for; shown with the input when it is missing
        source:
          $ref: '#/components/schemas/PlaybookInputSource'
    PlaybookExecutor:
      type: object
      additionalProperties: false
      required:
        - type
        - id
      properties:
        type:
          type: string
          enum:
            - agent
            - tool
            - decider
            - workflow
            - orchestration
        id:
          type: string
          description: The ID of the agent, tool, decider, workflow or orchestration
          example: dcd_V1StGXR8Z5jdHi6B
        input:
          type: object
          description: >-
            JSON Logic over the node's inputs, as an orchestration's
            `input_mapping`: each key is a field of the executor's input. Absent,
            the executor receives the node's input values as they are.
    PlaybookTransformation:
      type: object
      additionalProperties: false
      required:
        - name
        - format
        - tool
      properties:
        name:
          type: string
          example: proposal
        format:
          type: string
          description: >-
            The file's format; the tool must answer its content type:
            `application/pdf` for `pdf`, the Office Open XML types for `docx`,
            `xlsx` and `pptx`, `text/csv`, `text/html`, `text/markdown`,
            `text/plain`, `application/json`, `image/png` and `image/jpeg` for
            `jpg`
          enum:
            - csv
            - docx
            - html
            - jpg
            - json
            - md
            - pdf
            - png
            - pptx
            - txt
            - xlsx
          example: pdf
        tool:
          type: string
          description: >-
            The tool that renders the final node's output as a file, answering
            with `execute.response_mode: base64`
          example: tool_V1StGXR8Z5jdHi6B
    PlaybookNode:
      type: object
      additionalProperties: false
      required:
        - id
        - title
        - kind
        - inputs
      properties:
        id:
          type: string
          description: >-
            Stable across versions: a case's values carry over by ID. Letters,
            digits and underscores, starting with a letter or underscore.
          example: regime
        title:
          type: string
          example: Tax regime
        description:
          type: string
        kind:
          type: string
          enum:
            - collect
            - execute
            - confirm
          description: >-
            `collect` outputs its inputs, `execute` runs its executor, `confirm`
            waits for a person to confirm its inputs.
        final:
          type: boolean
          description: A goal of the playbook; only a final node carries transformations
          default: false
        inputs:
          type: array
          maxItems: 50
          items:
            $ref: '#/components/schemas/PlaybookInput'
        executor:
          $ref: '#/components/schemas/PlaybookExecutor'
        transformations:
          type: array
          items:
            $ref: '#/components/schemas/PlaybookTransformation'
    PlaybookDefinition:
      type: object
      additionalProperties: false
      required:
        - nodes
      properties:
        nodes:
          type: array
          minItems: 1
          maxItems: 200
          items:
            $ref: '#/components/schemas/PlaybookNode'
    Playbook:
      type: object
      properties:
        id:
          type: string
          example: pbk_V1StGXR8Z5jdHi6B
        project_id:
          x-soat-ref: projects
          type: string
          example: proj_V1StGXR8Z5jdHi6B
        name:
          type: string
          example: client-pricing
        description:
          type: string
          nullable: true
        version:
          type: integer
          description: >-
            The definition's version. Bumped only by a write that changes
            `definition`.
          example: 1
        definition:
          $ref: '#/components/schemas/PlaybookDefinition'
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
    CreatePlaybookRequest:
      type: object
      additionalProperties: false
      required:
        - name
        - definition
      properties:
        project_id:
          x-soat-ref: projects
          type: string
          description: Project ID (required if not using project key auth)
          example: proj_V1StGXR8Z5jdHi6B
        name:
          type: string
          description: Human-readable name, unique per project
          example: client-pricing
        description:
          description: What the playbook is for
          type: string
          nullable: true
        definition:
          description: The nodes and how their inputs are sourced
          allOf:
            - $ref: '#/components/schemas/PlaybookDefinition'
        version_label:
          type: string
          description: Optional tag for version 1, e.g. `initial`
          example: initial
    UpdatePlaybookRequest:
      type: object
      additionalProperties: false
      properties:
        name:
          description: Human-readable name, unique per project
          type: string
        description:
          description: What the playbook is for
          type: string
          nullable: true
        definition:
          description: Replaces the whole definition; a change archives a new version
          allOf:
            - $ref: '#/components/schemas/PlaybookDefinition'
        version_label:
          type: string
          description: >-
            Optional tag for the version this write archives. Ignored when the
            write changes no definition, since no version is archived.
        expected_version:
          description: Refuses the write unless the playbook is at this version.
          allOf:
            - $ref: './concurrency.yaml#/components/schemas/ExpectedVersion'
    ValidatePlaybookRequest:
      type: object
      additionalProperties: false
      required:
        - definition
      properties:
        definition:
          description: The definition to check
          allOf:
            - $ref: '#/components/schemas/PlaybookDefinition'
    PlaybookValidationResult:
      type: object
      required:
        - valid
        - problems
      properties:
        valid:
          type: boolean
        problems:
          type: array
          description: >-
            Every reason the definition cannot be stored: `DUPLICATE_NODE`,
            `DUPLICATE_INPUT`, `MISSING_NODE`, `UNKNOWN_FIELD`,
            `EXECUTOR_REQUIRED`, `EXECUTOR_FORBIDDEN`,
            `TRANSFORMATIONS_NOT_FINAL`, `DUPLICATE_TRANSFORMATION` or `CYCLE`.
            The same items a refused write lists under `meta.problems`.
          items:
            $ref: './errors.yaml#/components/schemas/ValidationProblem'
    PlaybookVersion:
      type: object
      description: An immutable archive of a playbook's definition at one version.
      properties:
        id:
          type: string
          example: pbk_ver_V1StGXR8Z5jdHi6B
        playbook_id:
          x-soat-ref: playbooks
          type: string
          example: pbk_V1StGXR8Z5jdHi6B
        version:
          type: integer
          example: 1
        config:
          type: object
          additionalProperties: true
          description: The versioned surface as it stood at this version.
          properties:
            definition:
              $ref: '#/components/schemas/PlaybookDefinition'
        label:
          type: string
          nullable: true
          example: restored from v2
        created_by_principal_type:
          nullable: true
          allOf:
            - $ref: './principals.yaml#/components/schemas/PrincipalType'
          description: >-
            Who produced this version. Null for a write with no principal behind
            it.
        created_by_principal_id:
          type: string
          nullable: true
          description: >-
            Public id of that principal: the user (`user_…`), or for API key
            auth the key itself (`key_…`), so the record names which key acted.
          example: user_V1StGXR8Z5jdHi6B
        created_at:
          type: string
          format: date-time
    RestorePlaybookVersionRequest:
      type: object
      additionalProperties: false
      properties:
        label:
          type: string
          description: Optional tag for the new version. Defaults to `restored from vN`.
          example: rollback
    CreatePlaybookCaseRequest:
      type: object
      additionalProperties: false
      required:
        - subject
      properties:
        subject:
          type: string
          description: Who or what the case is about, as you name it
          example: ACME Ltda (CNPJ 12.345.678/0001-90)
    PlaybookNodeState:
      type: string
      enum:
        - blocked
        - ready
        - running
        - waiting
        - done
        - stale
        - failed
        - rejected
      description: >-
        `blocked` while a required input is unset or reads a node that is not
        `done`; `ready` when it can run; `running` while its execution is
        queued or runs its executor; `waiting` while it waits on the workflow
        task or orchestration run it started, named by `run`; `done` when its
        output is current; `stale` when an input changed after its output was
        produced; `failed` when its execution failed on its current inputs;
        `rejected` when a person rejected its current inputs.
    PlaybookNodeRun:
      type: object
      required:
        - type
        - id
      properties:
        type:
          type: string
          enum:
            - task
            - orchestration_run
        id:
          type: string
          description: The task's or orchestration run's ID
          example: task_V1StGXR8Z5jdHi6B
        state:
          type: string
          nullable: true
          description: >-
            A task's workflow state; absent for a run, null once the task no
            longer exists
        automation_status:
          type: string
          nullable: true
          description: >-
            A task's automation status: `failed` when its state's dispatch
            failed and no `on_failure` routes it, so the task waits on a person
            to move it. Absent for a run.
        status:
          type: string
          nullable: true
          description: >-
            A run's status, such as `awaiting_input`; absent for a task, null
            once the run no longer exists
    PlaybookCaseNode:
      type: object
      properties:
        id:
          type: string
          example: regime
        state:
          $ref: '#/components/schemas/PlaybookNodeState'
        missing:
          type: array
          description: Required inputs that are unset or refused by their schema
          items:
            type: string
        blocked_by:
          type: array
          description: Nodes it reads that must be `done` first
          items:
            type: string
        run:
          nullable: true
          allOf:
            - $ref: '#/components/schemas/PlaybookNodeRun'
          description: >-
            The task or run a `waiting` node waits on, read live on every case
            read; null in every other state.
        inputs:
          type: array
          items:
            type: object
            properties:
              name:
                type: string
              value:
                description: The value it reads now; null when unset
              revision:
                type: integer
                nullable: true
                description: The revision of the entry the value comes from
        output:
          type: object
          nullable: true
          properties:
            value:
              description: The node's latest output
            revision:
              type: integer
    PlaybookCaseSummary:
      type: object
      properties:
        id:
          type: string
          example: pbk_case_V1StGXR8Z5jdHi6B
        project_id:
          x-soat-ref: projects
          type: string
          example: proj_V1StGXR8Z5jdHi6B
        playbook_id:
          x-soat-ref: playbooks
          type: string
          example: pbk_V1StGXR8Z5jdHi6B
        playbook_version:
          type: integer
          description: The version the case was created on and is evaluated against
          example: 1
        subject:
          type: string
        revision:
          type: integer
          description: The case's revision counter; every entry takes the next value
          example: 0
        automation_status:
          $ref: '#/components/schemas/PlaybookAutomationStatus'
        pause_requested_at:
          type: string
          format: date-time
          nullable: true
          description: When the case was paused; null while it runs
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
    PlaybookAutomationStatus:
      type: string
      description: >-
        `paused` while a pause of the case or of its project stands, else
        `running` while an execution is
        queued or runs its executor, else `waiting` while every execution in
        flight waits on a workflow task or orchestration run it started, else
        `idle`
      enum:
        - idle
        - running
        - waiting
        - paused
    PlaybookCase:
      allOf:
        - $ref: '#/components/schemas/PlaybookCaseSummary'
        - type: object
          properties:
            nodes:
              type: array
              description: Every node of the case's version, in definition order
              items:
                $ref: '#/components/schemas/PlaybookCaseNode'
    PlaybookInputWrite:
      type: object
      additionalProperties: false
      required:
        - node_id
        - name
        - value
        - expected_revision
      properties:
        node_id:
          type: string
          example: revenue
        name:
          type: string
          example: revenue
        value:
          description: The value, which must satisfy the input's schema
          example: 4000000
        expected_revision:
          type: integer
          nullable: true
          description: >-
            The revision of the field the writer read, or `null` for a field it
            read as unset. Any other current revision refuses the batch.
          example: null
    WritePlaybookCaseInputsRequest:
      type: object
      additionalProperties: false
      required:
        - inputs
      properties:
        inputs:
          description: The writes, applied all or none
          type: array
          minItems: 1
          maxItems: 100
          items:
            $ref: '#/components/schemas/PlaybookInputWrite'
        wait:
          type: boolean
          default: false
          description: >-
            Answer once every execution the write queued, and every one those
            queue in turn, has settled or parked, instead of right away
    PlaybookMissingInput:
      type: object
      properties:
        node_id:
          type: string
        input:
          type: string
        schema:
          type: object
          description: The JSON Schema a value must satisfy
        description:
          type: string
        required:
          type: boolean
          description: >-
            Whether the node waits for this input; an optional one never blocks
            it
    PlaybookCaseMissing:
      type: object
      properties:
        case_id:
          type: string
          example: pbk_case_V1StGXR8Z5jdHi6B
        node_id:
          type: string
          example: pricing
        waves:
          type: array
          description: Missing inputs grouped by the wave of the node that holds them
          items:
            type: array
            items:
              $ref: '#/components/schemas/PlaybookMissingInput'
    PlaybookEntry:
      type: object
      properties:
        id:
          type: string
          example: pbk_ent_V1StGXR8Z5jdHi6B
        revision:
          type: integer
        node_id:
          type: string
        kind:
          type: string
          enum:
            - input
            - output
            - confirmation
            - rejection
        input:
          type: string
          nullable: true
          description: The input written, on an `input` entry
        value:
          description: The value written
        basis:
          type: object
          nullable: true
          description: What an output, confirmation or rejection was decided on
        playbook_version:
          type: integer
        created_by_principal_type:
          nullable: true
          allOf:
            - $ref: './principals.yaml#/components/schemas/PrincipalType'
          description: >-
            Who recorded this entry; null for one the case's own cascade
            recorded.
        created_by_principal_id:
          type: string
          nullable: true
          description: >-
            Public id of that principal: the user (`user_…`), or for API key
            auth the key itself (`key_…`), so the record names which key acted.
          example: user_V1StGXR8Z5jdHi6B
        on_behalf_of:
          $ref: './on-behalf-of.yaml#/components/schemas/OnBehalfOf'
        created_at:
          type: string
          format: date-time
    RunPlaybookCaseNodeRequest:
      type: object
      additionalProperties: false
      properties:
        wait:
          type: boolean
          default: false
          description: >-
            Answer once the executor settles or parks instead of right away
    PlaybookExecution:
      type: object
      properties:
        id:
          type: string
          example: pbk_exe_V1StGXR8Z5jdHi6B
        case_id:
          type: string
          example: pbk_case_V1StGXR8Z5jdHi6B
        node_id:
          type: string
          description: The ID of the node that ran
          example: revenue
        playbook_version:
          type: integer
          description: The playbook version the case runs on
        executor:
          type: object
          nullable: true
          description: What ran; null for a `collect` node
          properties:
            type:
              type: string
              enum:
                - agent
                - tool
                - decider
                - workflow
                - orchestration
            id:
              type: string
              description: The ID of the agent, tool, decider, workflow or orchestration
              example: agent_V1StGXR8Z5jdHi6B
        status:
          type: string
          description: >-
            `queued` until a worker claims it, `running` while its executor
            runs or a task or run it started is open, then `completed` or
            `failed`
          enum:
            - queued
            - running
            - completed
            - failed
        result:
          type: string
          nullable: true
          enum:
            - current
            - superseded
            - null
          description: >-
            Once settled: `superseded` when the node's inputs changed while it
            ran, so its output was not written
        run_id:
          type: string
          nullable: true
          description: The generation, decision, task or orchestration run it started
        input:
          description: What the executor was given
        output:
          description: What the executor answered; null until it succeeds
        output_revision:
          type: integer
          nullable: true
          description: The revision of the output entry it wrote; null when it wrote none
        error:
          allOf:
            - $ref: './errors.yaml#/components/schemas/StoredError'
          nullable: true
          description: 'Why it failed: `{ code, message, meta? }`'
        created_by_principal_type:
          nullable: true
          allOf:
            - $ref: './principals.yaml#/components/schemas/PrincipalType'
          description: >-
            Who asked for the run; null when the case started it.
        created_by_principal_id:
          type: string
          nullable: true
          description: >-
            Public id of that principal: the user (`user_…`), or for API key
            auth the key itself (`key_…`), so the record names which key acted.
          example: user_V1StGXR8Z5jdHi6B
        on_behalf_of:
          $ref: './on-behalf-of.yaml#/components/schemas/OnBehalfOf'
        created_at:
          type: string
          format: date-time
        finished_at:
          type: string
          format: date-time
          nullable: true
    ExportPlaybookCaseNodeRequest:
      type: object
      additionalProperties: false
      properties:
        transformations:
          type: array
          minItems: 1
          description: The names of the transformations to render; every one the node declares when omitted
          items:
            type: string
            example: proposal
        revision:
          type: integer
          description: >-
            The output or confirmation of the node to render; its current
            output when omitted
        wait:
          type: boolean
          default: false
          # Pinned to `true` for tool calls: a tool caller wants the file, and
          # a render is one bounded tool call per transformation.
          x-soat-tool-forced: true
          description: >-
            Answer once every export settles instead of right away. A tool
            call, `builtin` or MCP, always waits.
    PlaybookExport:
      type: object
      properties:
        id:
          type: string
          example: pbk_exp_V1StGXR8Z5jdHi6B
        case_id:
          type: string
          example: pbk_case_V1StGXR8Z5jdHi6B
        node_id:
          type: string
          description: The ID of the final node rendered
          example: pricing
        revision:
          type: integer
          description: The output or confirmation entry rendered
        playbook_version:
          type: integer
          description: The playbook version whose transformation rendered it
        transformation:
          type: string
          example: proposal
        format:
          type: string
          example: pdf
        tool_id:
          type: string
          example: tool_V1StGXR8Z5jdHi6B
        status:
          type: string
          description: '`running` while its tool runs, then `completed` or `failed`'
          enum:
            - running
            - completed
            - failed
        stale:
          type: boolean
          description: >-
            Whether `revision` is no longer the node's current output, or the
            node is no longer `done`
        file_id:
          type: string
          nullable: true
          x-soat-ref: files
          description: The file rendered; null until it succeeds, and once the file is deleted
          example: file_V1StGXR8Z5jdHi6B
        error:
          allOf:
            - $ref: './errors.yaml#/components/schemas/StoredError'
          nullable: true
          description: 'Why it failed: `{ code, message, meta? }`'
        created_by_principal_type:
          nullable: true
          allOf:
            - $ref: './principals.yaml#/components/schemas/PrincipalType'
          description: >-
            Who asked for the export.
        created_by_principal_id:
          type: string
          nullable: true
          description: >-
            Public id of that principal: the user (`user_…`), or for API key
            auth the key itself (`key_…`), so the record names which key acted.
          example: user_V1StGXR8Z5jdHi6B
        on_behalf_of:
          $ref: './on-behalf-of.yaml#/components/schemas/OnBehalfOf'
        created_at:
          type: string
          format: date-time
        finished_at:
          type: string
          format: date-time
          nullable: true
    UpgradePlaybookCaseRequest:
      type: object
      additionalProperties: false
      properties:
        version:
          type: integer
          description: The version to move the case to; the playbook's current one when omitted
          example: 2
        wait:
          type: boolean
          default: false
          description: >-
            Answer once every execution the write queued, and every one those
            queue in turn, has settled or parked, instead of right away
    PlaybookUpgrade:
      type: object
      properties:
        id:
          type: string
          example: pbk_upg_V1StGXR8Z5jdHi6B
        case_id:
          type: string
          example: pbk_case_V1StGXR8Z5jdHi6B
        from_version:
          type: integer
          description: The version the case was on
        to_version:
          type: integer
          description: The version the case moved to
        diff:
          type: object
          description: What moved between the two versions, by node ID
          properties:
            added:
              type: array
              description: Nodes the new version adds
              items:
                type: string
            removed:
              type: array
              description: Nodes the new version drops
              items:
                type: string
            changed:
              type: array
              description: >-
                Nodes in both whose executor or inputs changed; a title,
                description or transformation change is not listed
              items:
                type: object
                properties:
                  node_id:
                    type: string
                  executor:
                    type: boolean
                    description: Whether the node's executor changed
                  inputs:
                    type: array
                    items:
                      type: object
                      properties:
                        name:
                          type: string
                        change:
                          type: string
                          enum:
                            - added
                            - removed
                            - source
                            - schema
                            - required
        created_by_principal_type:
          nullable: true
          allOf:
            - $ref: './principals.yaml#/components/schemas/PrincipalType'
          description: >-
            Who upgraded the case.
        created_by_principal_id:
          type: string
          nullable: true
          description: >-
            Public id of that principal: the user (`user_…`), or for API key
            auth the key itself (`key_…`), so the record names which key acted.
          example: user_V1StGXR8Z5jdHi6B
        on_behalf_of:
          $ref: './on-behalf-of.yaml#/components/schemas/OnBehalfOf'
        created_at:
          type: string
          format: date-time
    ConfirmPlaybookCaseNodeRequest:
      type: object
      additionalProperties: false
      required:
        - decision
        - expected_revisions
      properties:
        decision:
          type: string
          description: Whether the node's inputs, as read, are approved
          enum:
            - approve
            - reject
        reason:
          type: string
          description: Why the inputs were rejected; required on a rejection
          example: The revenue does not match the filed statement
        expected_revisions:
          type: object
          description: >-
            Each of the node's inputs, by name, mapped to the revision read, or
            null for one read as unset
          additionalProperties:
            type: integer
            nullable: true
          example:
            revenue: 3
        wait:
          type: boolean
          default: false
          description: >-
            Answer once every execution the write queued, and every one those
            queue in turn, has settled or parked, instead of right away
