openapi: 3.0.3
info:
  title: naturali.ai — Orchestrations API
  version: 1.0.0
  description: >
    Orchestrations — declarative, DAG-based agent pipelines (API.md §2). Define
    a graph of nodes (agent, tool, transform, human, approval, loop, poll,
    delay, webhook, emit_event, sub_orchestration, …) and edges between them,
    then start runs of that graph.

    This module fronts the runtime's own Orchestrations resource directly: an
    orchestration's id and a run's id are the runtime's own ids, and the graph
    itself (`nodes`/`edges`) is passed through unreshaped — its schema depends
    on each node's `type`, and the runtime is the sole authority on which
    combination is valid (`POST …/orchestrations/validate` runs the same checks
    create/update enforce, without persisting anything).

    A run executes durably in the background by default: the response returns
    immediately with `status: "queued"`, and progress is observed by polling
    `GET …/orchestration-runs/{orchestration_run_id}`. Pass `wait: true` on
    start to block until the run reaches a terminal or `awaiting_input` state
    instead.
  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: Define and run DAG-based agent pipelines.
security:
  - bearerAuth: []
paths:
  /v1/projects/{project_id}/orchestrations:
    parameters:
      - $ref: '#/components/parameters/ProjectId'
    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':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '502':
          $ref: '#/components/responses/UpstreamUnavailable'
    get:
      tags: [Orchestrations]
      summary: List orchestrations
      description: Lists the project's orchestration definitions.
      operationId: listOrchestrations
      parameters:
        - $ref: '#/components/parameters/Limit'
        - $ref: '#/components/parameters/Offset'
      responses:
        '200':
          description: A page of orchestrations.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OrchestrationList'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '502':
          $ref: '#/components/responses/UpstreamUnavailable'
  /v1/projects/{project_id}/orchestrations/validate:
    parameters:
      - $ref: '#/components/parameters/ProjectId'
    post:
      tags: [Orchestrations]
      summary: Validate an orchestration graph
      description: >
        Statically validates a graph without persisting anything — the same
        checks `create`/`update` enforce (unique node ids, edges reference
        existing nodes, the graph is acyclic unless it contains a loop node,
        every `input_mapping` reference resolves). Returns blocking `errors`
        and non-blocking `warnings`.
      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/ValidationResult'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '502':
          $ref: '#/components/responses/UpstreamUnavailable'
  /v1/projects/{project_id}/orchestrations/{orchestration_id}:
    parameters:
      - $ref: '#/components/parameters/ProjectId'
      - $ref: '#/components/parameters/OrchestrationId'
    get:
      tags: [Orchestrations]
      summary: Get an orchestration
      description: >
        Returns one orchestration with its nodes and edges. Belonging to
        another project responds `404`, not `403` — existence is not leaked.
      operationId: getOrchestration
      responses:
        '200':
          description: Orchestration details.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Orchestration'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '502':
          $ref: '#/components/responses/UpstreamUnavailable'
    patch:
      tags: [Orchestrations]
      summary: Update an orchestration
      description: Partially updates an orchestration's definition.
      operationId: updateOrchestration
      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':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '502':
          $ref: '#/components/responses/UpstreamUnavailable'
    delete:
      tags: [Orchestrations]
      summary: Delete an orchestration
      description: Deletes the orchestration definition and all of its runs.
      operationId: deleteOrchestration
      responses:
        '204':
          description: Deleted.
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '502':
          $ref: '#/components/responses/UpstreamUnavailable'
  /v1/projects/{project_id}/orchestration-runs:
    parameters:
      - $ref: '#/components/parameters/ProjectId'
    post:
      tags: [Orchestrations]
      summary: Start an orchestration run
      description: >-
        Starts a new run of the orchestration named by `orchestration_id`,
        which must belong to this project. By default the run executes
        durably in the background and this returns immediately with
        `status: "queued"`; pass `wait: true` to block until the run reaches a
        terminal or `awaiting_input` state instead.
      operationId: startOrchestrationRun
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/StartRunRequest'
      responses:
        '201':
          description: 'Run created (and, with wait: true, executed).'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OrchestrationRun'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          description: The project, or the referenced orchestration, was not found.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '502':
          $ref: '#/components/responses/UpstreamUnavailable'
    get:
      tags: [Orchestrations]
      summary: List orchestration runs
      description: >
        Lists runs of one orchestration. `orchestration_id` is required — it
        is what scopes the list to this project, since a run carries no
        cheaper project-level filter of its own.
      operationId: listOrchestrationRuns
      parameters:
        - name: orchestration_id
          in: query
          required: true
          description: Orchestration public ID to list runs of.
          schema:
            type: string
          example: orch_V1StGXR8Z5jdHi6B
        - $ref: '#/components/parameters/Limit'
        - $ref: '#/components/parameters/Offset'
      responses:
        '200':
          description: A page of runs.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OrchestrationRunList'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '502':
          $ref: '#/components/responses/UpstreamUnavailable'
  /v1/projects/{project_id}/orchestration-runs/{orchestration_run_id}:
    parameters:
      - $ref: '#/components/parameters/ProjectId'
      - $ref: '#/components/parameters/RunId'
    get:
      tags: [Orchestrations]
      summary: Get an orchestration run
      description: Returns the status, state, and artifacts of one run.
      operationId: getOrchestrationRun
      responses:
        '200':
          description: Run details.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OrchestrationRun'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '502':
          $ref: '#/components/responses/UpstreamUnavailable'
  /v1/projects/{project_id}/orchestration-runs/{orchestration_run_id}/cancel:
    parameters:
      - $ref: '#/components/parameters/ProjectId'
      - $ref: '#/components/parameters/RunId'
    post:
      tags: [Orchestrations]
      summary: Cancel an orchestration run
      description: Cancels a run that has not yet reached a terminal state.
      operationId: cancelOrchestrationRun
      responses:
        '200':
          description: Cancelled run.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OrchestrationRun'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          description: The run has already reached a terminal state.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '502':
          $ref: '#/components/responses/UpstreamUnavailable'
  /v1/projects/{project_id}/orchestration-runs/{orchestration_run_id}/resume:
    parameters:
      - $ref: '#/components/parameters/ProjectId'
      - $ref: '#/components/parameters/RunId'
    post:
      tags: [Orchestrations]
      summary: Resume an orchestration run
      description: >-
        Re-drives an `awaiting_input` run from its last checkpoint. This does
        not satisfy the pause itself — a run parked on a human or webhook node
        re-parks on the same node. Use `human-input` to supply the awaited
        payload and advance the run.
      operationId: resumeOrchestrationRun
      responses:
        '200':
          description: Resumed run.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OrchestrationRun'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          description: The run is not awaiting input.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '502':
          $ref: '#/components/responses/UpstreamUnavailable'
  /v1/projects/{project_id}/orchestration-runs/{orchestration_run_id}/human-input:
    parameters:
      - $ref: '#/components/parameters/ProjectId'
      - $ref: '#/components/parameters/RunId'
    post:
      tags: [Orchestrations]
      summary: Submit human input
      description: >-
        Provides human input to a run that is `awaiting_input` at a human
        node, and advances it.
      operationId: submitHumanInput
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/HumanInputRequest'
      responses:
        '200':
          description: Run after processing the human input.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OrchestrationRun'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          description: The run is not awaiting input.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '502':
          $ref: '#/components/responses/UpstreamUnavailable'
components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      description: A naturali API key (nat_sk_…) or a session JWT.
  parameters:
    ProjectId:
      name: project_id
      in: path
      required: true
      description: Project public ID (proj_ prefix).
      schema:
        type: string
        example: proj_V1StGXR8Z5jdHi6B
    OrchestrationId:
      name: orchestration_id
      in: path
      required: true
      description: Orchestration public ID (orch_ prefix).
      schema:
        type: string
        example: orch_V1StGXR8Z5jdHi6B
    RunId:
      name: orchestration_run_id
      in: path
      required: true
      description: Orchestration run public ID (orch_run_ prefix).
      schema:
        type: string
        example: orch_run_V1StGXR8Z5jdHi6B
    Limit:
      name: limit
      in: query
      required: false
      description: Maximum items to return (1–100).
      schema:
        type: integer
        minimum: 1
        maximum: 100
        default: 20
    Offset:
      name: offset
      in: query
      required: false
      description: >
        Items to skip. These lists page by offset rather than by naturali's
        usual opaque cursor because the upstream ordering is offset-based; a
        cursor here would only imitate a keyset.
      schema:
        type: integer
        minimum: 0
        default: 0
  responses:
    BadRequest:
      description: The request was malformed or failed validation.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    Unauthorized:
      description: Missing or invalid credentials.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    NotFound:
      description: The resource does not exist (existence is not leaked).
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    UpstreamUnavailable:
      description: The upstream runtime could not complete the operation.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
  schemas:
    OrchestrationNode:
      type: object
      description: >
        A single execution unit in the graph. Required fields beyond `id` and
        `type` vary by node type (`agent`, `tool`, `transform`, `knowledge`,
        `memory_write`, `condition`, `human`, `approval`, `loop`, `poll`,
        `delay`, `webhook`, `emit_event`, `sub_orchestration`) — passed through
        exactly as sent, so it is not re-declared field by field here. Sent to
        (and validated by) `POST …/orchestrations/validate` before it is
        trusted.
      additionalProperties: true
      required: [id, type]
      properties:
        id:
          type: string
          description: Unique node identifier within this orchestration.
        type:
          type: string
          description: Node execution type.
    OrchestrationEdge:
      type: object
      description: A directed connection between two nodes.
      additionalProperties: true
      required: [from, to]
      properties:
        from:
          type: string
          description: Source node ID.
        to:
          type: string
          description: Target node ID.
    Orchestration:
      type: object
      description: An orchestration (pipeline) definition.
      properties:
        id:
          type: string
          nullable: true
          description: Public orchestration ID (orch_ prefix).
          example: orch_V1StGXR8Z5jdHi6B
        project_id:
          type: string
          nullable: true
          x-naturali-ref: project
          example: proj_V1StGXR8Z5jdHi6B
        name:
          type: string
          example: Refund review pipeline
        description:
          type: string
          nullable: true
        nodes:
          type: array
          items:
            $ref: '#/components/schemas/OrchestrationNode'
        edges:
          type: array
          items:
            $ref: '#/components/schemas/OrchestrationEdge'
        state_schema:
          type: object
          nullable: true
          additionalProperties: true
          description: Optional JSON Schema the run's accumulated state is validated against.
        input_schema:
          type: object
          nullable: true
          additionalProperties: true
          description: Schema for a run's initial input; its top-level properties seed state.
        created_at:
          type: string
          format: date-time
          nullable: true
        updated_at:
          type: string
          format: date-time
          nullable: true
      required:
        - id
        - project_id
        - name
        - description
        - nodes
        - edges
        - state_schema
        - input_schema
        - created_at
        - updated_at
    CreateOrchestrationRequest:
      type: object
      required: [name, nodes, edges]
      properties:
        name:
          type: string
          example: Refund review pipeline
        description:
          type: string
          nullable: true
        nodes:
          type: array
          items:
            $ref: '#/components/schemas/OrchestrationNode'
        edges:
          type: array
          items:
            $ref: '#/components/schemas/OrchestrationEdge'
        state_schema:
          type: object
          nullable: true
          additionalProperties: true
        input_schema:
          type: object
          nullable: true
          additionalProperties: true
    UpdateOrchestrationRequest:
      type: object
      description: Every field is independently optional; send only what changes.
      properties:
        name:
          type: string
        description:
          type: string
          nullable: true
        nodes:
          type: array
          items:
            $ref: '#/components/schemas/OrchestrationNode'
        edges:
          type: array
          items:
            $ref: '#/components/schemas/OrchestrationEdge'
        state_schema:
          type: object
          nullable: true
          additionalProperties: true
        input_schema:
          type: object
          nullable: true
          additionalProperties: true
    OrchestrationList:
      type: object
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/Orchestration'
        total:
          type: integer
          nullable: true
          example: 12
        limit:
          type: integer
          example: 20
        offset:
          type: integer
          example: 0
      required: [data, total, limit, offset]
    ValidateOrchestrationRequest:
      type: object
      properties:
        nodes:
          type: array
          items:
            $ref: '#/components/schemas/OrchestrationNode'
        edges:
          type: array
          items:
            $ref: '#/components/schemas/OrchestrationEdge'
        input_schema:
          type: object
          nullable: true
          additionalProperties: true
    ValidationIssue:
      type: object
      properties:
        path:
          type: string
          description: Location of the issue (e.g. `nodes[1].input_mapping.val`).
        message:
          type: string
      required: [path, message]
    ValidationResult:
      type: object
      properties:
        valid:
          type: boolean
          description: True when there are no blocking errors.
        errors:
          type: array
          items:
            $ref: '#/components/schemas/ValidationIssue'
        warnings:
          type: array
          items:
            $ref: '#/components/schemas/ValidationIssue'
      required: [valid, errors, warnings]
    RequiredAction:
      type: object
      description: Details for an `awaiting_input` run waiting on human input.
      properties:
        type:
          type: string
          enum: [human_input, webhook_receive]
        node_id:
          type: string
        prompt:
          type: string
        context:
          type: object
          additionalProperties: true
        options:
          type: array
          items:
            type: string
          nullable: true
      required: [type, node_id, prompt, context]
    NodeExecution:
      type: object
      description: >
        Record of a single node execution within a run — resolved input,
        output, status and error — the orchestration analogue of a trace step.
      properties:
        node_id:
          type: string
        node_type:
          type: string
          nullable: true
        attempt:
          type: integer
          description: 1-based attempt number; a retried node produces one record per attempt.
        status:
          type: string
          enum: [completed, failed, requires_action, skipped]
        input:
          type: object
          nullable: true
          additionalProperties: true
        output:
          type: object
          nullable: true
          additionalProperties: true
        error:
          type: object
          nullable: true
          additionalProperties: true
        started_at:
          type: string
          format: date-time
          nullable: true
        completed_at:
          type: string
          format: date-time
          nullable: true
      required: [node_id, attempt, status]
    OrchestrationRun:
      type: object
      properties:
        id:
          type: string
          nullable: true
          description: Public run ID (orch_run_ prefix).
          example: orch_run_V1StGXR8Z5jdHi6B
        orchestration_id:
          type: string
          nullable: true
          x-naturali-ref: orchestration
          example: orch_V1StGXR8Z5jdHi6B
        project_id:
          type: string
          nullable: true
          x-naturali-ref: project
          example: proj_V1StGXR8Z5jdHi6B
        status:
          type: string
          description: >-
            `queued` awaits a worker; `running` is executing; `sleeping` is
            parked on a delay/poll wait; `awaiting_input` is parked on a human
            or webhook 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
          additionalProperties: true
          description: Current accumulated state.
        active_nodes:
          type: array
          items:
            type: string
        artifacts:
          type: object
          additionalProperties: true
          description: Map of node ID to output artifact.
        error:
          type: object
          nullable: true
          additionalProperties: true
        trace_id:
          type: string
          nullable: true
          x-naturali-ref: trace
        input:
          type: object
          nullable: true
          additionalProperties: true
        output:
          type: object
          nullable: true
          additionalProperties: true
          description: Terminal node artifact(s), once the run has succeeded.
        node_executions:
          type: array
          items:
            $ref: '#/components/schemas/NodeExecution'
        usage:
          type: object
          additionalProperties: true
          description: >-
            Token/cost roll-up across every metered generation the run
            produced. Present on the single-run read; omitted from run lists.
        required_action:
          type: object
          nullable: true
          allOf:
            - $ref: '#/components/schemas/RequiredAction'
        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
          nullable: true
        updated_at:
          type: string
          format: date-time
          nullable: true
      required:
        - id
        - orchestration_id
        - project_id
        - status
        - state
        - active_nodes
        - artifacts
        - error
        - trace_id
        - input
        - output
        - node_executions
        - required_action
        - started_at
        - completed_at
        - created_at
        - updated_at
    OrchestrationRunList:
      type: object
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/OrchestrationRun'
        total:
          type: integer
          nullable: true
        limit:
          type: integer
        offset:
          type: integer
      required: [data, total, limit, offset]
    StartRunRequest:
      type: object
      required: [orchestration_id]
      properties:
        orchestration_id:
          type: string
          x-naturali-ref: orchestration
          description: Orchestration to run.
          example: orch_V1StGXR8Z5jdHi6B
        input:
          type: object
          additionalProperties: true
          description: Initial state for the run, merged with orchestration defaults.
        tool_context:
          type: object
          additionalProperties:
            type: string
          description: >-
            Write-only. Per-run context forwarded to the run's tool calls: every
            entry is sent as an `X-Naturali-Context-<key>` request header on each
            `http`/`mcp` tool call the run's agent nodes make, including those of
            a `loop` or `sub_orchestration` child run. This is how a per-user
            credential reaches a tool endpoint without being written into the
            prompt — the endpoint reads it from a header it can trust rather than
            from model output.


            The bag is stored on the run, so it survives an `awaiting_input`
            pause, a durable wait and a restart. It is never returned on a read:
            any principal on the project can read the project's runs.


            A key becomes an HTTP header name verbatim — no character is
            re-cased, so `ocaToken` and `oca_token` are two different keys — and
            must contain only letters, digits and `!#$%&'*+-.^_`|~`; two keys
            that differ only in case are rejected, since HTTP folds them into
            one. To land a value in a header the target already expects (usually
            `Authorization`), have the tool declare it with a
            `{{context:<key>}}` reference in its own `headers`, and set the
            tool's `context_keys` so the value reaches that tool alone.
          example:
            ocaToken: usr_token_V1StGXR8Z5jdHi6B
        wait:
          type: boolean
          default: false
          description: >-
            When true, block until the run reaches a terminal or
            `awaiting_input` state and return the settled run. When false
            (default), return immediately with `status: "queued"`.
    HumanInputRequest:
      type: object
      required: [node_id]
      properties:
        node_id:
          type: string
          description: ID of the human node to satisfy.
        output:
          type: object
          additionalProperties: true
          description: Output/response provided by the human reviewer.
    ErrorResponse:
      type: object
      required: [error]
      properties:
        error:
          type: object
          required: [code, message]
          properties:
            code:
              type: string
              example: upstream_unavailable
            message:
              type: string
              example: Could not create the orchestration on the upstream runtime.
            details:
              type: object
              additionalProperties: true
              description: Optional structured context for the error.
