# AUTO-GENERATED tier-A mirror spec — do not edit.
# Source: openapi/runtime/orchestrations.yaml (runtime 0.71.2)
#  + policy: openapi/mirror-policies/orchestrations.json
# Regenerate: node scripts/generate-mirror-specs.mjs
openapi: 3.0.3
info:
  title: naturali.ai — Orchestrations API
  version: 1.0.0
  description: >-
    Orchestrations: multi-step pipelines declared as a graph of nodes — agents, tools, conditions,
    waits — versioned, and executed as runs that can be polled, resumed and cancelled. 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: Orchestrations
    description: Manage orchestrations and their runs
security:
  - bearerAuth: []
  - oauth2:
      - mcp:access
paths:
  /v1/projects/{project_id}/orchestrations:
    post:
      tags:
        - Orchestrations
      summary: Create an orchestration
      description: Creates a new orchestration (pipeline) definition in the project.
      operationId: createOrchestration
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateOrchestrationRequest"
      responses:
        "201":
          description: Orchestration created
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Orchestration"
        "400":
          description: Validation error
        "401":
          description: Unauthorized
        "403":
          description: Forbidden
    get:
      tags:
        - Orchestrations
      summary: List orchestrations
      description: Returns orchestrations accessible to the caller.
      operationId: listOrchestrations
      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: List of orchestrations
          content:
            application/json:
              schema:
                type: object
                required:
                  - data
                  - total
                  - limit
                  - offset
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/Orchestration"
                  total:
                    type: integer
                  limit:
                    type: integer
                  offset:
                    type: integer
        "401":
          description: Unauthorized
        "403":
          description: Forbidden
    parameters:
      - $ref: "#/components/parameters/ProjectId"
  /v1/projects/{project_id}/orchestrations/validate:
    post:
      tags:
        - Orchestrations
      summary: Validate an orchestration graph
      description: >
        Statically validates an orchestration graph without persisting anything. Checks that every
        node has its required field, node ids are unique, edges reference existing nodes, the graph
        is acyclic (unless it contains a loop node), and every `input_mapping` `{"var": "..."}`
        reference resolves to a state key written by an upstream node or seeded by `input_schema`.
        Returns blocking `errors` and non-blocking `warnings` (e.g. a state key only written on a
        conditional branch). The same `errors` checks are enforced on create and update, which fail
        with `400` when any error is present.
      operationId: validateOrchestration
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ValidateOrchestrationRequest"
      responses:
        "200":
          description: Validation result
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/OrchestrationValidationResult"
        "401":
          description: Unauthorized
    parameters:
      - $ref: "#/components/parameters/ProjectId"
  /v1/projects/{project_id}/orchestrations/queue/stats:
    get:
      tags:
        - Orchestrations
      summary: Get orchestration queue stats
      description: >
        Returns a point-in-time snapshot of the orchestration run queue: how many tasks are waiting
        to be claimed (`queue_depth`), how many are currently claimed with a valid lease
        (`claimed_tasks`), the age of the oldest waiting task, recent claim-latency percentiles over
        a rolling in-process window, and a per-project breakdown. Intended for admin/operator
        policies; guarded by `orchestrations:GetQueueStats`.

        Every figure is scoped to what the caller may see. A project-scoped caller gets
        `per_project` for their own projects only, `queue_depth` and `claimed_tasks` summed over
        those same projects, and `null` for `oldest_queued_age_seconds` and the `claim_latency_ms`
        percentiles — both describe the whole deployment and cannot be narrowed, so they are
        withheld rather than approximated. An unrestricted caller (the action granted on every
        project) gets the deployment-wide figures.
      operationId: getQueueStats
      security:
        - bearerAuth: []
        - oauth2:
            - mcp:access
      responses:
        "200":
          description: Queue stats snapshot
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/QueueStats"
        "401":
          description: Unauthorized
        "403":
          description: Forbidden
    parameters:
      - $ref: "#/components/parameters/ProjectId"
  /v1/projects/{project_id}/orchestrations/{orchestration_id}:
    get:
      tags:
        - Orchestrations
      summary: Get an orchestration
      description: Returns the orchestration with nodes and edges.
      operationId: getOrchestration
      x-naturali-resource:
        kind: orchestration
        from: orchestration_id
      parameters:
        - $ref: "#/components/parameters/orchestration_id"
      responses:
        "200":
          description: Orchestration details
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Orchestration"
        "401":
          description: Unauthorized
        "403":
          description: Forbidden
        "404":
          description: Not found
    patch:
      tags:
        - Orchestrations
      summary: Update an orchestration
      description: Partially updates an orchestration definition.
      operationId: updateOrchestration
      x-naturali-resource:
        kind: orchestration
        from: orchestration_id
      parameters:
        - $ref: "#/components/parameters/orchestration_id"
        - $ref: "#/components/parameters/IfMatchVersion"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UpdateOrchestrationRequest"
      responses:
        "200":
          description: Updated orchestration
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Orchestration"
        "400":
          description: Validation error
        "401":
          description: Unauthorized
        "403":
          description: Forbidden
        "404":
          description: Not found
        "409":
          $ref: "#/components/responses/VersionConflict"
    delete:
      tags:
        - Orchestrations
      summary: Delete an orchestration
      description: Deletes an orchestration definition and all its runs.
      operationId: deleteOrchestration
      x-naturali-resource:
        kind: orchestration
        from: orchestration_id
      parameters:
        - $ref: "#/components/parameters/orchestration_id"
      responses:
        "204":
          description: Deleted
        "401":
          description: Unauthorized
        "403":
          description: Forbidden
        "404":
          description: Not found
    parameters:
      - $ref: "#/components/parameters/ProjectId"
  /v1/projects/{project_id}/orchestrations/{orchestration_id}/versions:
    get:
      tags:
        - Orchestrations
      summary: List an orchestration's graph versions
      description: >
        Returns the orchestration's archived graphs, newest first. A version is written on create
        and on every subsequent write that changes the graph (`nodes`, `edges`, `state_schema`,
        `input_schema`) — through the REST API or a formation apply alike. Metadata-only edits
        (name, description) do not archive a version. See
        [Versioning](/docs/modules/orchestrations#versioning).
      operationId: listOrchestrationVersions
      x-naturali-resource:
        kind: orchestration
        from: orchestration_id
      parameters:
        - $ref: "#/components/parameters/orchestration_id"
        - 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 orchestration versions, newest first
          content:
            application/json:
              schema:
                type: object
                required:
                  - data
                  - total
                  - limit
                  - offset
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/OrchestrationVersion"
                  total:
                    type: integer
                  limit:
                    type: integer
                  offset:
                    type: integer
        "401":
          description: Unauthorized
        "403":
          description: Forbidden
        "404":
          description: Orchestration not found
    parameters:
      - $ref: "#/components/parameters/ProjectId"
  /v1/projects/{project_id}/orchestrations/{orchestration_id}/versions/{version}:
    get:
      tags:
        - Orchestrations
      summary: Fetch an archived orchestration version
      description: >
        Returns the exact graph a given version describes. Every run records the version it started
        on in `orchestration_version` and executes that graph for its whole life, so this is how you
        read the topology a run actually took — including a run whose orchestration has been rewired
        since.
      operationId: getOrchestrationVersion
      x-naturali-resource:
        kind: orchestration
        from: orchestration_id
      parameters:
        - $ref: "#/components/parameters/orchestration_id"
        - name: version
          in: path
          required: true
          description: The archived version number
          schema:
            type: integer
            minimum: 1
      responses:
        "200":
          description: Archived orchestration version
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/OrchestrationVersion"
        "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}/orchestrations/{orchestration_id}/versions/{version}/restore:
    post:
      tags:
        - Orchestrations
      summary: Restore an archived orchestration graph
      description: >
        Writes an archived version's graph back as the orchestration's live definition, which
        archives it again as a **new** version rather than rewinding the counter — so a run pinned
        to any version in between still resolves the graph it started on.


        The restore runs through the ordinary update path, so the archived graph goes through the
        same static validation as an authored one. Node resource references (`agent_id`, `tool_id`,
        `orchestration_id`) resolve when a run reaches the node, so a target deleted since the
        snapshot was taken restores cleanly and surfaces as a failed run rather than a `400`.
        Restoring the graph the orchestration already holds is a no-op and archives nothing. Runs
        already in flight are unaffected either way — a restore is an ordinary edit, and pinning is
        what keeps it from reaching them.
      operationId: restoreOrchestrationVersion
      x-naturali-resource:
        kind: orchestration
        from: orchestration_id
      parameters:
        - $ref: "#/components/parameters/orchestration_id"
        - 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/RestoreOrchestrationVersionRequest"
      responses:
        "200":
          description: The orchestration, at its new version
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Orchestration"
        "400":
          description: Bad Request — version is not a positive integer
        "401":
          description: Unauthorized
        "403":
          description: Forbidden
        "404":
          description: Not found
        "409":
          $ref: "#/components/responses/VersionConflict"
    parameters:
      - $ref: "#/components/parameters/ProjectId"
  /v1/projects/{project_id}/orchestration-runs:
    post:
      tags:
        - Orchestrations
      summary: Start an orchestration run
      description: 'Creates a new run for the orchestration named by orchestration_id. By default the run
        executes durably in the background: the response returns immediately with status "queued" (a
        worker then claims it and moves it to "running") and progress is observed via
        get-orchestration-run or run lifecycle webhook events
        (orchestration_runs.started/awaiting_input/succeeded/failed). Delay and poll waits park the
        run as "sleeping" and are woken by a background scheduler, surviving restarts. Pass
        wait=true to block until the run reaches a terminal or awaiting_input state.'
      operationId: startOrchestrationRun
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/StartOrchestrationRunRequest"
      responses:
        "200":
          description: Duplicate request — the run the `idempotency_key` already names is returned and no
            second run is started.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/OrchestrationRun"
        "201":
          description: Run created and executed
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/OrchestrationRun"
        "400":
          description: Validation error (e.g. a `tool_context` key that cannot become a header, `metadata`
            that is not a JSON object, or an `idempotency_key` that is not a non-empty string of at
            most 255 characters). No run is created.
        "401":
          description: Unauthorized
        "403":
          description: Forbidden
        "404":
          description: Orchestration not found
        "409":
          description: "`IDEMPOTENCY_KEY_REUSED` — the key is already claimed by a run started from a
            different request. No run is created."
    get:
      tags:
        - Orchestrations
      summary: List orchestration runs
      description: >-
        Returns orchestration runs the caller can access, optionally filtered by orchestration, by
        parent run, by status, or by whether the run has a parent at all.


        Note when aggregating: a run's `usage` covers its whole subtree, so summing it over a list
        that contains both a parent and its children counts the children more than once. Pass
        `nested=false` to sum over runs a caller started.
      operationId: listOrchestrationRuns
      parameters:
        - name: orchestration_id
          in: query
          required: false
          description: Filter by orchestration public ID (orch_...)
          schema:
            type: string
        - name: parent_orchestration_run_id
          in: query
          required: false
          description: Filter to the runs one specific parent run's `loop` / `sub_orchestration` nodes started
            (run_...). This is how a caller holding a parent names the individual children behind
            its `usage`.
          schema:
            type: string
        - name: nested
          in: query
          required: false
          description: >-
            Filter by whether the run was started by another run. `false` returns only the runs a
            caller started (no parent), which is the set to sum `usage` over; `true` returns only
            the runs a `loop` / `sub_orchestration` node started, across every parent. Omit to
            return both.


            Contradicting `parent_orchestration_run_id` with `nested=false` is a `400`; any value
            other than `true` or `false` is a `400`.
          schema:
            type: boolean
        - name: status
          in: query
          required: false
          description: >-
            Filter by run status. Repeat the parameter to OR values —
            `status=queued&status=running&status=sleeping&status=awaiting_input` is the set still
            driving, which is how a caller finds live work without paging every run the project ever
            started.


            There is no `non_terminal` shorthand on purpose: which statuses count as live is the
            caller's policy. A value outside the enum, empty string included, is a `400`.
          schema:
            type: array
            items:
              type: string
              enum:
                - queued
                - running
                - sleeping
                - awaiting_input
                - succeeded
                - failed
                - cancelled
                - expired
          style: form
          explode: true
          example:
            - queued
            - running
        - 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 runs
          content:
            application/json:
              schema:
                type: object
                required:
                  - data
                  - total
                  - limit
                  - offset
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/OrchestrationRun"
                  total:
                    type: integer
                  limit:
                    type: integer
                  offset:
                    type: integer
        "401":
          description: Unauthorized
        "403":
          description: Forbidden
    parameters:
      - $ref: "#/components/parameters/ProjectId"
  /v1/projects/{project_id}/orchestration-runs/{orchestration_run_id}/cancel:
    post:
      tags:
        - Orchestrations
      summary: Cancel an orchestration run
      description: Cancels a run that has not yet reached a terminal state.
      operationId: cancelOrchestrationRun
      x-naturali-resource:
        kind: orchestration_run
        from: orchestration_run_id
      parameters:
        - $ref: "#/components/parameters/orchestration_run_id"
      responses:
        "200":
          description: Cancelled run
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/OrchestrationRun"
        "401":
          description: Unauthorized
        "403":
          description: Forbidden
        "404":
          description: Not found
        "409":
          description: Run is already in a terminal state
    parameters:
      - $ref: "#/components/parameters/ProjectId"
  /v1/projects/{project_id}/orchestration-runs/{orchestration_run_id}/pause:
    post:
      tags:
        - Orchestrations
      summary: Pause an orchestration run
      description: >-
        Parks a run in flight as `awaiting_input` at its next checkpoint, with a `required_action`
        of type `paused` naming the pause as operator-initiated rather than a node's. Unlike cancel,
        the run keeps its last checkpoint and resume-orchestration-run re-drives it from there — so
        work already done is deferred rather than discarded.

        A `queued` or `sleeping` run is parked immediately (a sleeping run keeps the wake it was
        due, and resuming hands it back to the scheduler at that instant). A `running` run keeps
        running until the round in flight reaches its checkpoint, so the response may still read
        `running` while `pause_requested_at` is set. A run already parked on a human, webhook or
        approval node keeps that node's `required_action`; the pause is still recorded, which is
        what makes submit-human-input refuse until the run is resumed.

        The pause fans out to the run's `loop` / `sub_orchestration` descendants — each parks at its
        own next checkpoint — because otherwise a parent's pause would bound nothing. Resuming does
        not fan out: each parked descendant is resumed by its own id.

        Idempotent: pausing an already-paused run answers with it unchanged.
      operationId: pauseOrchestrationRun
      x-naturali-resource:
        kind: orchestration_run
        from: orchestration_run_id
      parameters:
        - $ref: "#/components/parameters/orchestration_run_id"
      requestBody:
        required: false
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/PauseOrchestrationRunRequest"
      responses:
        "200":
          description: Paused run
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/OrchestrationRun"
        "401":
          description: Unauthorized
        "403":
          description: Forbidden
        "404":
          description: Not found
        "409":
          description: Run has already settled
    parameters:
      - $ref: "#/components/parameters/ProjectId"
  /v1/projects/{project_id}/orchestration-runs/{orchestration_run_id}/human-input:
    post:
      tags:
        - Orchestrations
      summary: Submit human input
      description: Provides human input to a run that is awaiting_input at a human node.
      operationId: submitHumanInput
      x-naturali-resource:
        kind: orchestration_run
        from: orchestration_run_id
      parameters:
        - $ref: "#/components/parameters/orchestration_run_id"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/HumanInputRequest"
      responses:
        "200":
          description: Run after processing human input
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/OrchestrationRun"
        "400":
          description: Invalid input
        "401":
          description: Unauthorized
        "403":
          description: Forbidden
        "404":
          description: Not found
        "409":
          description: Run is not awaiting input, or an operator pause is in force
    parameters:
      - $ref: "#/components/parameters/ProjectId"
  /v1/projects/{project_id}/orchestration-runs/{orchestration_run_id}/resume:
    post:
      tags:
        - Orchestrations
      summary: Resume an orchestration run
      description: >-
        Re-drives an awaiting_input orchestration run from its last checkpoint. This does not
        satisfy the pause itself — it carries no node_id or payload, so a run parked on a human or
        webhook-receive node re-parks on the same node. Use submit-human-input to supply the awaited
        payload and advance the run.

        It is also the only thing that lifts an operator pause (pause-orchestration-run): a run
        parked with `required_action.type` of `paused` re-drives the frontier that had not run yet,
        and one paused mid-timer goes back to `sleeping` for the wake it was already due.
      operationId: resumeOrchestrationRun
      x-naturali-resource:
        kind: orchestration_run
        from: orchestration_run_id
      parameters:
        - $ref: "#/components/parameters/orchestration_run_id"
      responses:
        "200":
          description: Resumed run
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/OrchestrationRun"
        "401":
          description: Unauthorized
        "403":
          description: Forbidden
        "404":
          description: Not found
        "409":
          description: Run is not awaiting input
    parameters:
      - $ref: "#/components/parameters/ProjectId"
  /v1/projects/{project_id}/orchestration-runs/{orchestration_run_id}:
    get:
      tags:
        - Orchestrations
      summary: Get an orchestration run
      description: Returns the status, state, and artifacts of a specific run.
      operationId: getOrchestrationRun
      x-naturali-resource:
        kind: orchestration_run
        from: orchestration_run_id
      parameters:
        - $ref: "#/components/parameters/orchestration_run_id"
      responses:
        "200":
          description: Run details
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/OrchestrationRun"
        "401":
          description: Unauthorized
        "403":
          description: Forbidden
        "404":
          description: Not found
    parameters:
      - $ref: "#/components/parameters/ProjectId"
components:
  schemas:
    QueueStats:
      type: object
      description: A point-in-time snapshot of the orchestration run queue.
      properties:
        driver:
          type: string
          enum:
            - postgres
            - sqs
          description: The active queue driver (`ORCHESTRATION_QUEUE_DRIVER`). Under `sqs`,
            `oldest_queued_age_seconds` is always `null` and `per_project` is always empty — SQS
            exposes neither.
          example: postgres
        queue_depth:
          type: integer
          description: Tasks waiting to be claimed now (unclaimed and past their `available_at`).
            Backoff-delayed tasks are excluded. Summed over the caller's own projects when the
            caller is project-scoped.
          example: 12
        claimed_tasks:
          type: integer
          description: Tasks currently claimed with a valid (unexpired) lease. Summed over the caller's own
            projects when the caller is project-scoped.
          example: 3
        oldest_queued_age_seconds:
          type: number
          nullable: true
          description: "Age in seconds of the oldest claimable-now task, or `null` when none are waiting. Also
            `null` for a project-scoped caller: the figure is deployment-wide."
          example: 4.2
        claim_latency_ms:
          type: object
          description: "Claim-latency percentiles (time from a task becoming available to being claimed) over
            a rolling in-process window. `p50`/`p95` are `null` when no claim happened in the
            window, and for a project-scoped caller: the window is deployment-wide."
          properties:
            p50:
              type: number
              nullable: true
              example: 18
            p95:
              type: number
              nullable: true
              example: 240
            window_seconds:
              type: integer
              example: 300
        per_project:
          type: array
          description: One row per project with any queued or claimed task.
          items:
            type: object
            properties:
              project_id:
                type: string
                description: Public project ID (proj_ prefix).
                example: proj_V1StGXR8Z5jdHi6B
              queued:
                type: integer
                example: 5
              claimed:
                type: integer
                example: 1
    OrchestrationNode:
      type: object
      description: A single execution unit in the orchestration graph.
      required:
        - id
        - type
      properties:
        id:
          type: string
          description: Unique node identifier within this orchestration.
        type:
          type: string
          description: "Node execution type. Known types: agent, tool, transform, knowledge, condition, human,
            approval, loop, poll, delay, webhook, emit_event, sub_orchestration. Open set — new
            types may be added in minor releases, and an unrecognized type is accepted at create
            time (the run fails when the node dispatches), so clients must tolerate unknown values."
        agent_id:
          x-naturali-ref: agents
          type: string
          description: For agent nodes — public ID of the agent to invoke.
        tool_id:
          x-naturali-ref: tools
          type: string
          description: For tool and poll nodes — public ID of the tool to call.
        operation_id:
          type: string
          description: For tool and poll nodes — specific operation/action on MCP tools.
        expression:
          description: For transform/condition nodes — JSON Logic rule (https://jsonlogic.com) evaluated
            against the run state. A rule may be any JSON value (object, string, number, boolean,
            array), so no type is constrained.
        exit_condition:
          description: >
            For poll nodes — JSON Logic stop condition, evaluated each attempt against the run state
            augmented with `response` (the latest tool result) and `attempt` (1-based count); a
            truthy result stops polling.
        prompt:
          type: string
          description: For human nodes — prompt shown to the human reviewer.
        options:
          type: array
          items:
            type: string
          description: For human nodes — constrained choices.
        arguments:
          type: object
          additionalProperties: true
          description: >
            For approval nodes — input-mapping-style object (JSON Logic values) resolved against run
            state into the proposed tool call's arguments, frozen onto the created approval item.
        expires_in:
          type: integer
          description: >
            For approval nodes — seconds until the created approval item expires. Defaults to 86400
            (24h) when omitted. An expired item can never execute; the run routes down its
            `on_expired` edge.
        instructions:
          type: string
          description: For approval nodes — optional guidance shown to the approver.
        reasoning:
          description: For approval nodes — JSON Logic (any JSON value) resolved into the item's reasoning.
        evidence:
          description: For approval nodes — JSON Logic (any JSON value) resolved into the item's evidence.
        predicted_impact:
          description: For approval nodes — JSON Logic (any JSON value) resolved into the item's predicted
            impact.
        input_mapping:
          type: object
          additionalProperties: true
          description: >
            Maps node input keys to values. Each value is JSON Logic (https://jsonlogic.com), the
            same evaluator used by transform and condition nodes. A single-key object is evaluated
            against the run state — `{"var": "key"}` reads `state.key`, `{"cat": [...]}` and `{">":
            [...]}` compute derived values. Any other value (string, number, boolean, array,
            multi-key object) is passed through as a literal.
        state_mapping:
          type: object
          additionalProperties: true
          description: >
            Maps state write paths to values. Each key is a `state.<path>` destination (the `state.`
            prefix is optional); each value is JSON Logic (https://jsonlogic.com) evaluated against
            `{ "output": <node artifact>, "state": <run state> }` — e.g. `{ "summary": {"var":
            "output.content"} }` writes the artifact's `content` field to `state.summary`. The same
            evaluator as input_mapping/transform/condition; only the context differs. An `agent`
            node's artifact always carries both `content` (the text response) and `object` (the
            parsed value when a schema applied, `null` otherwise), so `output.content` reads the
            same whether or not the node declares an `output_schema`.
        output_schema:
          type: object
          description: >
            For agent nodes — JSON Schema the model's answer is parsed into, reaching the artifact
            as `object`. Declaring it here is only needed for an agent that carries no
            `output_schema` of its own: the agent's schema already produces the artifact's `object`
            wherever that agent generates.
        collection:
          type: string
          description: For loop nodes — state path to the collection to iterate over.
        item_variable:
          type: string
          description: For loop nodes — variable name injected into state for each item.
        parallelism:
          type: integer
          description: For loop nodes — number of items to process in parallel.
        on_item_error:
          type: string
          enum:
            - fail
            - collect
          description: "For loop nodes — what a failed item does. `fail` (the default) fails the node on the
            first item whose child run settles `failed`, `cancelled` or `expired`. `collect` keeps
            going: that item's entry in `results` becomes `{ \"error\": { \"code\", \"message\" },
            \"orchestration_run_id\" }`, in its original position. Either way the artifact carries
            `failed_count`. A child run that could not be started (for example past the nesting
            depth bound) fails the node in both modes."
        context_keys:
          type: array
          nullable: true
          items:
            type: string
          description: For loop and sub_orchestration nodes — allowlist of the run's `tool_context` keys the
            child run inherits. When `null` (the default), the child inherits the parent's whole
            bag. When set, only the listed keys are handed down, so a run holding a broad credential
            can delegate one step to a shared sub-graph without passing on what that sub-graph does
            not need; `[]` hands down nothing. Matching is case-insensitive, since an entry names a
            key that becomes an HTTP header name; an entry outside that grammar is rejected at write
            time with `INVALID_TOOL_CONTEXT_KEY`. The server-derived identity keys (`session_id`,
            `actor_id`, `actor_external_id`) are unaffected — they are re-derived per generation in
            the child regardless of this list. Ignored for other node types.
        interval:
          type: string
          description: >
            For poll nodes — wait between attempts. Accepts a friendly suffix form (`5s`, `30s`,
            `5m`, `2h`, `500ms`) or ISO 8601 (e.g. PT5S).
        fail_on_timeout:
          type: boolean
          description: >
            For poll nodes — when max_iterations is reached without the exit condition becoming
            true, fail the run (true) instead of completing with condition_met=false (default
            false).
        duration:
          type: string
          description: >
            For delay nodes — how long to wait. Accepts a friendly suffix form (`5s`, `30s`, `5m`,
            `2h`, `500ms`) or ISO 8601 (e.g. PT5S).
        mode:
          type: string
          enum:
            - receive
          description: >
            For webhook nodes — parks the run awaiting an inbound callback. `receive` is the only
            mode; to send a notification out of a graph, use an `emit_event` node instead.
        event_type:
          type: string
          description: >
            For emit_event nodes — the internal event type to emit (e.g. `guardrail.exception`). The
            node's input_mapping becomes the event `data`. Any Webhook subscribed to this event type
            in the run's project then delivers it — signed, retried, and tracked by the Webhooks
            module — so the graph holds no URL or secret of its own.
        orchestration_id:
          x-naturali-ref: orchestrations
          type: string
          description: >
            Public ID of the orchestration this node runs — the child orchestration for
            sub_orchestration nodes, and the orchestration run once per item for loop nodes.
        max_iterations:
          type: integer
          description: >
            Maximum iterations before the node is aborted. For poll nodes this is the maximum number
            of attempts (default 10, ceiling 1000).
        retry:
          type: object
          description: >
            Retry-on-failure policy. When the node throws a transient error
            (unexpected/infrastructure errors and upstream 5xx) and attempts remain, the run parks
            as `sleeping` and re-executes the node after the backoff delay. Terminal errors (4xx
            business errors) fail immediately. Absent or `max_attempts <= 1` means fail-fast.
          additionalProperties: false
          properties:
            max_attempts:
              type: integer
              description: |
                Total attempts including the first (default 1, ceiling 20).
            backoff:
              type: object
              additionalProperties: false
              properties:
                strategy:
                  type: string
                  enum:
                    - fixed
                    - exponential
                  description: >
                    `fixed` waits `delay_ms` between every attempt; `exponential` doubles per prior
                    attempt. Default `fixed`.
                delay_ms:
                  type: integer
                  description: Base delay between attempts in ms (default 1000).
                max_delay_ms:
                  type: integer
                  description: |
                    Cap on the computed backoff delay in ms (default 300000).
    OrchestrationEdge:
      type: object
      description: A directed connection between two nodes.
      required:
        - from
        - to
      properties:
        from:
          type: string
          description: Source node ID.
        to:
          type: string
          description: Target node ID.
        condition:
          type: string
          description: For condition node routing — label to match against condition output.
        activation_group:
          type: string
          description: Groups edges for join semantics.
        activation_condition:
          type: string
          enum:
            - all
            - any
          description: Whether all or any edges in the activation group must come from a completed node before
            the target runs. Omitted, `all`.
    Orchestration:
      type: object
      required:
        - id
        - project_id
        - name
        - version
        - nodes
        - edges
        - created_at
        - updated_at
      properties:
        id:
          type: string
          description: Public ID (orch_...).
        project_id:
          x-naturali-ref: projects
          type: string
          description: Public ID of the owning project.
        name:
          type: string
          description: Human-readable name.
        description:
          type: string
          nullable: true
          description: Optional description.
        version:
          type: integer
          description: >
            Incremented on every write that changes the graph; prior versions are archived. A run
            pins the version it started on, so these fields are a draft for runs started from now on
            rather than a live rewrite of the ones already executing.
          example: 1
        nodes:
          type: array
          items:
            $ref: "#/components/schemas/OrchestrationNode"
        edges:
          type: array
          items:
            $ref: "#/components/schemas/OrchestrationEdge"
        state_schema:
          type: object
          nullable: true
          description: Optional JSON Schema for state validation.
        input_schema:
          type: object
          nullable: true
          description: Schema for run inputs (initial state).
        output_mapping:
          $ref: "#/components/schemas/OrchestrationOutputMapping"
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
    OrchestrationOutputMapping:
      type: object
      nullable: true
      additionalProperties: true
      description: "The shape of a succeeded run's `output`, owned by the orchestration rather than by its
        terminal node ids. Each key is an output field (a dotted key such as `summary.title` builds
        a nested object); each value is JSON Logic (https://jsonlogic.com) evaluated against `{
        \"state\": <final run state> }`, which includes `state.input` and `state.nodes.<id>`. A
        missing path maps to `null`; a mapping that throws fails the run. Null (or omitted) keys
        `output` by terminal node id. Versioned with the graph; `null` on update clears it."
      example:
        summary:
          var: state.summary
        answer:
          var: state.nodes.answer.content
    CreateOrchestrationRequest:
      type: object
      required:
        - name
        - nodes
        - edges
      additionalProperties: false
      properties:
        name:
          type: string
          description: Human-readable name.
        description:
          type: string
          nullable: true
        nodes:
          description: The graph's nodes. Each `id` is unique and is what edges and `nodes.<id>` state
            references name; a node with no incoming edge starts the run.
          type: array
          items:
            $ref: "#/components/schemas/OrchestrationNode"
        edges:
          description: Directed edges between node `id`s. Every `from`/`to` must name a node in `nodes`, and
            the graph must be acyclic unless it contains a `loop` node. A node with several outgoing
            edges activates every target in parallel. `[]` for a graph of independent nodes.
          type: array
          items:
            $ref: "#/components/schemas/OrchestrationEdge"
        state_schema:
          type: object
          nullable: true
        input_schema:
          type: object
          nullable: true
        output_mapping:
          $ref: "#/components/schemas/OrchestrationOutputMapping"
        version_label:
          type: string
          description: Optional tag for the version this create archives, e.g. `initial`.
          example: initial
    UpdateOrchestrationRequest:
      type: object
      additionalProperties: false
      properties:
        name:
          type: string
        description:
          type: string
          nullable: true
        nodes:
          description: The graph's nodes. Each `id` is unique and is what edges and `nodes.<id>` state
            references name; a node with no incoming edge starts the run. Replaces the stored list;
            omitted, the stored nodes are kept and validated against the new `edges`.
          type: array
          items:
            $ref: "#/components/schemas/OrchestrationNode"
        edges:
          description: Directed edges between node `id`s. Every `from`/`to` must name a node in `nodes`, and
            the graph must be acyclic unless it contains a `loop` node. A node with several outgoing
            edges activates every target in parallel. Replaces the stored list; omitted, the stored
            edges are kept and validated against the new `nodes`.
          type: array
          items:
            $ref: "#/components/schemas/OrchestrationEdge"
        state_schema:
          type: object
          nullable: true
        input_schema:
          type: object
          nullable: true
        output_mapping:
          $ref: "#/components/schemas/OrchestrationOutputMapping"
        version_label:
          type: string
          description: Optional tag for the version this write archives, e.g. `pre-rewire`. Ignored when the
            write changes no graph 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"
    OrchestrationVersion:
      type: object
      description: An immutable archive of an orchestration's graph at one version.
      properties:
        id:
          type: string
          description: Public ID of the archived version
          example: orch_ver_V1StGXR8Z5jdHi6B
        orchestration_id:
          x-naturali-ref: orchestrations
          type: string
          description: Public ID of the orchestration this version belongs to
          example: orch_V1StGXR8Z5jdHi6B
        version:
          type: integer
          description: The archived version number
          example: 1
        config:
          type: object
          additionalProperties: true
          description: >-
            The orchestration's versioned surface as it stood at this version: `nodes`, `edges`,
            `state_schema` and `input_schema`. Name and description are metadata — bumping the
            version when one of them changes would make two version numbers denote the same
            topology, which is exactly what a run cites.


            Deliberately open rather than a fixed schema: an archive written by an earlier release
            of the runtime reflects the orchestration surface **of its own time**, so it may carry
            fields the current API no longer documents.
          properties:
            nodes:
              type: array
              items:
                $ref: "#/components/schemas/OrchestrationNode"
            edges:
              type: array
              items:
                $ref: "#/components/schemas/OrchestrationEdge"
            state_schema:
              type: object
              nullable: true
            input_schema:
              type: object
              nullable: true
            output_mapping:
              $ref: "#/components/schemas/OrchestrationOutputMapping"
        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
    RestoreOrchestrationVersionRequest:
      type: object
      additionalProperties: false
      properties:
        label:
          type: string
          description: Optional tag for the version the restore creates. Defaults to `restored from v<version>`.
          example: rollback to pre-incident graph
    OrchestrationRun:
      type: object
      required:
        - id
        - orchestration_id
        - project_id
        - status
        - state
        - active_nodes
        - artifacts
        - created_at
        - updated_at
      properties:
        id:
          type: string
          description: Public ID (run_...).
        orchestration_id:
          x-naturali-ref: orchestrations
          type: string
          description: Public ID of the parent orchestration.
        orchestration_version:
          type: integer
          nullable: true
          description: >
            The orchestration version this run executes, fixed when the run started. Every later
            step of the run — the first drive, a wake from `sleeping`, a human or approval resume, a
            redrive after a crash — resolves the graph from this version, so editing the
            orchestration never re-shapes a run already in flight. Fetch the graph it names at `GET
            /v1/projects/{project_id}/orchestrations/{orchestration_id}/versions/{version}`.


            Null for runs created before pinning existed, which execute the live graph.
          example: 3
        project_id:
          x-naturali-ref: projects
          type: string
          description: Public ID of the owning project.
        status:
          type: string
          description: Run lifecycle state. `queued` awaits a worker; `running` is actively executing;
            `sleeping` is parked on a delay/poll wait (no worker); `awaiting_input` is parked on a
            human node; `succeeded`/`failed`/ `cancelled` are terminal; `expired` is a wait that
            passed its deadline.
          enum:
            - queued
            - running
            - sleeping
            - awaiting_input
            - succeeded
            - failed
            - cancelled
            - expired
        state:
          type: object
          description: Current accumulated state.
        active_nodes:
          type: array
          items:
            type: string
          description: Node IDs currently active.
        artifacts:
          type: object
          description: Map of node ID to output artifact.
        error:
          type: object
          nullable: true
          description: Error details when status is failed.
        trace_id:
          x-naturali-ref: traces
          type: string
          nullable: true
        input:
          type: object
          nullable: true
          description: Initial input provided at run creation.
        metadata:
          description: The caller-owned key/value metadata supplied at run creation, returned verbatim. Null
            when the run was started without any. The server writes nothing here and no key is
            reserved; the bag is never merged into `state`, so nothing in it reaches the graph.
          example:
            tenant_account_id: "42"
            dispatch_batch: nightly-2026-08-25
          allOf:
            - $ref: "#/components/schemas/NullableMetadataBag"
        idempotency_key:
          type: string
          nullable: true
          description: The deduplication key the run was started under, unique within the project and claimed
            for as long as the run record exists. Null for a run started without one, which every
            platform-started run is.
          example: dispatch-2026-09-18-activation-42
        parent_orchestration_run_id:
          x-naturali-ref: orchestration-runs
          type: string
          nullable: true
          description: The run whose node started this one — set only on a child a `loop` or
            `sub_orchestration` node spawned, null for a run a caller started. A child is its own
            run with its own usage events, so this is what makes a delegated run's spend
            attributable to the run that ordered it.
        parent_node_id:
          type: string
          nullable: true
          description: The node within `parent_orchestration_run_id` that started this run. Null when
            `parent_orchestration_run_id` is null.
        orchestration_run_depth:
          type: integer
          minimum: 0
          description: "`loop` / `sub_orchestration` edges between this run and the run a caller started: `0`
            for a caller-started run, one more than its parent's for a child. Starting a child past
            the effective bound — the smaller of the deployment's `MAX_ORCHESTRATION_RUN_DEPTH`
            (default 10) and the project's `max_orchestration_run_depth` — is refused with
            `ORCHESTRATION_RUN_DEPTH_LIMIT`, which fails the run that tried to descend. That bounds
            a graph whose `sub_orchestration` node names itself, directly or through a cycle of two
            graphs, which the intra-graph cycle check cannot see."
          example: 0
        output:
          type: object
          nullable: true
          description: "What a succeeded run returns: the orchestration's `output_mapping` evaluated over the
            final state, or, when it declares none, the terminal node artifacts keyed by node id.
            Null unless the run succeeded."
        node_executions:
          type: array
          description: Per-node execution records in chronological order. Each entry captures the resolved
            input, output, status, and error for a single node execution — the orchestration
            analogue of an LLM trace.
          items:
            $ref: "#/components/schemas/NodeExecution"
        usage:
          allOf:
            - $ref: "#/components/schemas/UsageTotals"
          description: >-
            What the run cost: token counts and `cost_usd` summed across every metered generation it
            produced **and every run it started** through `loop` / `sub_orchestration` nodes, at any
            depth. Present only on the single-run read (`GET
            /v1/projects/{project_id}/orchestration-runs/{orchestration_run_id}`); omitted from run
            lists and from every write's response, including a start with `wait: true`.


            A nested child is a run record of its own, so this figure spans several of them. Two
            consequences: summing `usage` across a list that mixes parents and children
            double-counts (filter with `nested=false`), and the per-event receipt at
            `/v1/projects/{project_id}/usage/receipt` stays scoped to one run — its line items carry
            a `node_id` from one graph only.
        usage_own:
          allOf:
            - $ref: "#/components/schemas/UsageTotals"
          description: >-
            The same roll-up restricted to **this run's own nodes**, excluding every nested run it
            started. Equal to `usage` for a run with no children; below it for a run that delegates.
            Present only on the single-run read, like `usage`.


            This is the field to read to see where cost sits in a run tree — own versus subtree —
            without walking the children.
        required_action:
          allOf:
            - $ref: "#/components/schemas/RequiredAction"
          nullable: true
        pause_requested_at:
          type: string
          format: date-time
          nullable: true
          description: "When an operator pause was requested, or null when none is in force. Independent of
            `status`: a `running` run keeps running until its next checkpoint, and a run parked on a
            node keeps that node's `required_action`."
        pause_reason:
          type: string
          nullable: true
          description: The reason supplied with the pause, when one was.
        started_at:
          type: string
          format: date-time
          nullable: true
        completed_at:
          type: string
          format: date-time
          nullable: true
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
    NodeExecution:
      type: object
      description: Record of a single node execution within a run, used to debug which node failed, what
        input it received, and what it produced.
      required:
        - node_id
        - attempt
        - dispatches
        - status
        - created_at
      properties:
        node_id:
          type: string
          description: ID of the executed node.
        node_type:
          type: string
          nullable: true
          description: Type of the executed node (e.g. agent, transform).
        attempt:
          type: integer
          description: >
            1-based attempt number. A node with a retry policy produces one record per attempt
            (failed attempts followed by a final record).
        dispatches:
          type: integer
          description: >
            How many times this attempt was dispatched, including the first. A worker that stops
            mid-node leaves its task to be redelivered, and the redelivery re-runs the node under
            this same record — so a count above 1 is work the run issued, and was billed for, more
            than once.
        status:
          type: string
          enum:
            - running
            - completed
            - failed
            - requires_action
            - skipped
          description: Node execution status. `running` marks an execution record whose node is still in
            flight. Open set — new statuses may be added in minor releases; clients must tolerate
            unknown values.
        input:
          type: object
          nullable: true
          description: Resolved input_mapping the node received.
        output:
          type: object
          nullable: true
          description: Output artifact the node produced (null when failed).
        error:
          type: object
          nullable: true
          description: Error details when status is failed.
        started_at:
          type: string
          format: date-time
          nullable: true
        completed_at:
          type: string
          format: date-time
          nullable: true
        created_at:
          type: string
          format: date-time
    RequiredAction:
      type: object
      description: Why an awaiting_input run is parked. `node_id`, `prompt` and `context` describe a
        node's own pause and are present for every kind but `paused`, which is an operator pause
        with no node of its own and carries `reason` instead.
      required:
        - type
      properties:
        type:
          type: string
          enum:
            - human_input
            - webhook_receive
            - approval
            - paused
          description: Discriminator identifying the kind of pause. Open enum — new pause kinds may be added
            in minor releases; clients must tolerate unknown values.
        node_id:
          type: string
          description: The node the run is parked at. Absent for a `paused` action, which no node produced.
        prompt:
          type: string
        context:
          type: object
        reason:
          type: string
          nullable: true
          description: Present for `paused` — the reason the operator gave, or null when they gave none.
        options:
          type: array
          items:
            type: string
          nullable: true
        approval_spec:
          type: object
          description: Present only for `approval` pauses — the frozen tool proposal the engine emits as an
            ApprovalItem when the run parks. Copied as a value; inner keys stay exactly as authored.
        approval_id:
          x-naturali-ref: approvals
          type: string
          description: Present once the approval item is emitted.
        expires_at:
          type: string
          format: date-time
          description: Present for `approval` pauses — when the item expires.
    PauseOrchestrationRunRequest:
      type: object
      properties:
        reason:
          type: string
          maxLength: 256
          description: Why the run is being paused, surfaced on the parked run's `required_action.reason` and
            on `pause_reason`.
      additionalProperties: false
    HumanInputRequest:
      type: object
      required:
        - node_id
      additionalProperties: false
      properties:
        node_id:
          type: string
          description: ID of the human node to satisfy.
        output:
          type: object
          description: Output/response provided by the human reviewer.
    StartOrchestrationRunRequest:
      type: object
      required:
        - orchestration_id
      additionalProperties: false
      properties:
        orchestration_id:
          x-naturali-ref: orchestrations
          type: string
          description: Orchestration to run (orch_...).
          example: orch_V1StGXR8Z5jdHi6B
        input:
          type: object
          description: Initial state for the run (merged with orchestration defaults).
        tool_context:
          type: object
          additionalProperties:
            type: string
          description: >-
            Key-value pairs forwarded as `X-Naturali-Context-<key>` headers on every `http` and
            `mcp` tool call made by an agent node of this run — including the agents of any child
            run a `loop` or `sub_orchestration` node starts. The header name is
            `X-Naturali-Context-` plus the key verbatim; no character is re-cased.


            The bag is stored on the run and re-read on every step, so it survives an
            `awaiting_input` pause, a `sleeping` wait, a background worker drive and a crash
            redrive. It is **write-only**: a run is a record every principal who may read runs can
            read, and a credential in it is not theirs to see, so it is never returned on one. It
            also does not outlive the run: reaching a terminal status (`succeeded`, `failed`,
            `cancelled` or `expired`) clears it. A key that is not a valid HTTP header name, or two
            keys that map to the same header, are rejected with `400 INVALID_TOOL_CONTEXT_KEY` and
            no run is created.


            The reserved identity keys (`session_id`, `actor_id`, `actor_external_id`) are stripped
            at generation time — a caller cannot address them from here.
          example:
            ocaToken: eyJhbGciOiJIUzI1NiJ9.abc
        metadata:
          description: >-
            Caller-supplied key/value metadata attached to the run record for per-run attribution
            (e.g. which of your own tenants this run belongs to, or the dispatch batch that started
            it). Round-trips verbatim on every read of the run, on the list as well as the single
            read.


            The bag is caller-owned and no key is reserved: server-owned state (status, the pinned
            orchestration version, the trace, usage, artifacts, the run's own `input` and
            accumulated `state`) lives in its own top-level field and cannot be written from here.


            It is **not** merged into run state: no graph node sees it, and an `input_schema` never
            has to tolerate it — which is what makes it the place for an infrastructural label,
            rather than `input`. Keys are never transformed. It is not inherited by the child runs a
            `loop` or `sub_orchestration` node starts; each child carries whatever the graph gives
            it, which today is nothing.
          example:
            tenant_account_id: "42"
            dispatch_batch: nightly-2026-08-25
          allOf:
            - $ref: "#/components/schemas/MetadataBag"
        idempotency_key:
          type: string
          maxLength: 255
          description: >-
            Deduplication key, unique within the project, that makes a retry of an ambiguous failure
            safe. The first request under a key starts the run and answers `201`; any later request
            carrying the same key answers `200` with that same run, whatever state it has reached —
            including a retry that arrives while the original is still running.


            The key is claimed by the run record and stays claimed for as long as the record exists,
            so it never silently expires and lets a second run through.


            `orchestration_id`, `input`, `tool_context` and `metadata` are the request the key
            names: reusing a key with any of them changed is `409 IDEMPOTENCY_KEY_REUSED` rather
            than a replay of a run that does something else. `wait` is not part of that comparison —
            it says how the caller waits, not what the run is — so a retry may flip it.
          example: dispatch-2026-09-18-activation-42
        wait:
          type: boolean
          default: false
          description: When true, block until the run reaches a terminal (succeeded/failed) or awaiting_input
            state and return the settled run. When false (default), return immediately with status
            "queued" and execute the run in the background.
    ValidateOrchestrationRequest:
      type: object
      additionalProperties: false
      properties:
        nodes:
          description: The graph's nodes. Each `id` is unique and is what edges and `nodes.<id>` state
            references name; a node with no incoming edge starts the run. Checked exactly as create
            checks them, without persisting.
          type: array
          items:
            $ref: "#/components/schemas/OrchestrationNode"
        edges:
          description: Directed edges between node `id`s. Every `from`/`to` must name a node in `nodes`, and
            the graph must be acyclic unless it contains a `loop` node. A node with several outgoing
            edges activates every target in parallel. Checked exactly as create checks them, without
            persisting.
          type: array
          items:
            $ref: "#/components/schemas/OrchestrationEdge"
        input_schema:
          type: object
          nullable: true
          description: Optional JSON Schema for run inputs; its top-level properties seed state.
    OrchestrationValidationError:
      type: object
      properties:
        path:
          type: string
          description: Location of the issue (e.g. nodes[1].input_mapping.val).
        message:
          type: string
          description: Human-readable description of the issue.
    OrchestrationValidationResult:
      type: object
      required:
        - valid
        - errors
        - warnings
      properties:
        valid:
          type: boolean
          description: True when there are no blocking errors.
        errors:
          type: array
          items:
            $ref: "#/components/schemas/OrchestrationValidationError"
        warnings:
          type: array
          items:
            $ref: "#/components/schemas/OrchestrationValidationError"
    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
    NullableMetadataBag:
      type: object
      nullable: true
      description: A `MetadataBag` on a field where `null` is meaningful — a full-replacement update that
        clears the bag, or a record whose bag was never set.
      example:
        author: John
        revision: 2
    UsageTotals:
      type: object
      description: >
        Token counts and cost for one slice of the meter. Tokens and cost only: a `compute_second`
        or `gb_day` meter is reported by the aggregate's `components` array.


        The three input dimensions partition the prompt — `input_tokens` = `uncached_input_tokens` +
        `cached_tokens` + `cache_write_tokens` — and are separate because they are separately
        priced. Note that the `input_tokens` **component** on a usage event is the uncached figure
        alone; here `input_tokens` is the whole prompt.
      properties:
        cost_usd:
          type: number
          nullable: true
          description: Sum of priced component costs; null when nothing in the slice was priced. Always null
            at step altitude — the ledger prices one event per generation at write time, and a price
            read back per step would disagree with it after any price change.
        input_tokens:
          type: integer
          description: Full prompt tokens, reconstructed from the components.
        uncached_input_tokens:
          type: integer
          description: Prompt tokens priced at the plain input rate — the prompt minus cache reads and cache
            writes.
        output_tokens:
          type: integer
        cached_tokens:
          type: integer
          description: Prompt tokens served from the provider's prompt cache.
        cache_write_tokens:
          type: integer
          description: Prompt tokens written into the provider's prompt cache.
        reasoning_tokens:
          type: integer
          description: A non-billable subset of `output_tokens`, reported where the provider breaks it out.
    MetadataBag:
      type: object
      description: >-
        Caller-owned annotations on a resource, stored as the object they were written as: the types
        a value was written with are the types a read returns, so a filter can ask an ordering
        question about a number. Unlike other body fields, keys are stored and returned verbatim in
        the casing supplied — they are not converted between snake_case and camelCase.


        No key is reserved, and that is the point: every piece of state the platform owns lives in
        its own typed column, so nothing written here reaches platform state. The platform never
        reads the bag — it is not an IAM context, not a policy input and not part of a prompt —
        which is what separates it from a tag bag.
      example:
        author: John
        revision: 2
  parameters:
    ProjectId:
      name: project_id
      in: path
      required: true
      description: Project public ID (proj_ prefix).
      schema:
        type: string
        example: proj_V1StGXR8Z5jdHi6B
    orchestration_id:
      in: path
      name: orchestration_id
      required: true
      schema:
        type: string
      description: Public ID of the orchestration (orch_...)
    orchestration_run_id:
      in: path
      name: orchestration_run_id
      required: true
      schema:
        type: string
      description: Public ID of the run (run_...)
    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"
