---
title: "Validate an orchestration graph"
method: POST
path: "/v1/projects/{project_id}/orchestrations/validate"
tags: ["Orchestrations"]
---

# Validate an orchestration graph

`POST /v1/projects/{project_id}/orchestrations/validate`

## Request

- Base URL: `https://api.naturali.ai`
- URL: `https://api.naturali.ai/v1/projects/{project_id}/orchestrations/validate`
- Auth: one of:
  - HTTP bearer
  - OAuth 2 (authorization code, authorization URL `https://api.naturali.ai/authorize`, token URL `https://api.naturali.ai/token`, scopes: `mcp:access`)

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 `problems` and non-blocking `warnings` (e.g. a state key only written on a conditional branch). The same checks are enforced on create and update, which fail with `400 ORCHESTRATION_VALIDATION_FAILED` listing the same items under `meta.problems` and `meta.warnings`.

## Request body

- ValidateOrchestrationRequest
  - `nodes` OrchestrationNode[]: 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.
    - `id` string, required: Unique node identifier within this orchestration.
    - `type` string, required: 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` string: For agent nodes — public ID of the agent to invoke.
    - `tool_id` string: For tool and poll nodes — public ID of the tool to call.
    - `operation_id` string: For tool and poll nodes — specific operation/action on MCP tools.
    - `expression` unknown
    - `exit_condition` unknown
    - `prompt` string: For human nodes — prompt shown to the human reviewer.
    - `options` string[]: For human nodes — constrained choices.
    - `arguments` object: 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` integer: 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` string: For approval nodes — optional guidance shown to the approver.
    - `reasoning` unknown
    - `evidence` unknown
    - `predicted_impact` unknown
    - `input_mapping` object: 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` object: 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` object: 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` string: For loop nodes — state path to the collection to iterate over.
    - `item_variable` string: For loop nodes — variable name injected into state for each item.
    - `parallelism` integer: For loop nodes — number of items to process in parallel.
    - `on_item_error` 'fail' | 'collect': 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` string[], nullable: 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` string: 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` boolean: 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` string: For delay nodes — how long to wait. Accepts a friendly suffix form (`5s`, `30s`, `5m`, `2h`, `500ms`) or ISO 8601 (e.g. PT5S).
    - `mode` 'receive': 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` string: 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` string: 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` integer: Maximum iterations before the node is aborted. For poll nodes this is the maximum number of attempts (default 10, ceiling 1000).
    - `retry` object: 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.
      - `max_attempts` integer: Total attempts including the first (default 1, ceiling 20).
      - `backoff` object
        - `strategy` 'fixed' | 'exponential': `fixed` waits `delay_ms` between every attempt; `exponential` doubles per prior attempt. Default `fixed`.
        - `delay_ms` integer: Base delay between attempts in ms (default 1000).
        - `max_delay_ms` integer: Cap on the computed backoff delay in ms (default 300000).
  - `edges` OrchestrationEdge[]: 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.
    - `from` string, required: Source node ID.
    - `to` string, required: Target node ID.
    - `condition` string: For condition node routing — label to match against condition output.
    - `activation_group` string: Groups edges for join semantics.
    - `activation_condition` 'all' | 'any': Whether all or any edges in the activation group must come from a completed node before the target runs. Omitted, `all`.
  - `input_schema` object, nullable: Optional JSON Schema for run inputs; its top-level properties seed state.

## Response `200`

Validation result

- OrchestrationValidationResult
  - `valid` boolean, required: True when there are no blocking problems.
  - `problems` ValidationProblem[], required: Every blocking problem — the items a refused write lists under `meta.problems`.
    - `code` string, required: What kind of problem it is, stable across releases.
    - `path` string, required: Where in the request body the problem is.
    - `message` string, required: The problem in words, naming the values involved.
  - `warnings` ValidationProblem[], required: Problems that do not block a write, such as a state key written only on a conditional branch (`CONDITIONAL_STATE_KEY`). A refused write carries them under `meta.warnings`.
    - `code` string, required: What kind of problem it is, stable across releases.
    - `path` string, required: Where in the request body the problem is.
    - `message` string, required: The problem in words, naming the values involved.

## Other responses

- `401`: Unauthorized

---

[API](https://skmtc.dev/naturali/apis/naturali-ai-api.md) · [All operations](https://skmtc.dev/naturali/apis/naturali-ai-api/llms.txt) · [OpenAPI document](https://skmtc.dev/naturali/apis/naturali-ai-api/revisions/e4905b78f422?raw)
