---
title: "Execute a stored plan"
method: POST
path: "/api/v1/plan/execute"
tags: ["Multi-Agent Planning"]
---

# Execute a stored plan

`POST /api/v1/plan/execute`

## Request

- Base URL: `https://orchestrator.getaxonflow.com`
- URL: `https://orchestrator.getaxonflow.com/api/v1/plan/execute`
- Auth: none declared
- TypeScript: <https://skmtc.dev/getaxonflow/apis/axonflow-orchestrator-api/docs/api/v1/plan/execute/post.ts> (a function with typed inputs; its response is checked against the schema)

Executes a plan previously generated and stored via `POST /api/v1/plan`.
The plan to execute is identified by `context.plan_id` in the request body.
The execution mode is taken from the **stored plan** (not this request):
`auto`, `parallel`, `sequential`, `balanced`, or the Enterprise-only
HITL modes `confirm` / `step`.

Where a multi-agent step is decided - in `confirm` and `step` mode, and in
the other modes only when the orchestrator runs with
`AXONFLOW_HITL_ENABLED=true` outside Community (otherwise no step is
decided per step) - it is decided over its content as text: its
`prompt`, `statement` and `parameters` as written, then what its
processor sends (the rendered prompt of an `llm-call`; the statement and
built parameters of a `connector-call`) or, for any other step type, the
input it is passed. In `confirm` and `step` mode a step's hold asks no
policy; each step is decided when it runs (`POST
/api/v1/plan/{id}/resume`), for the credential that resumed the plan, in
the plan's organization, and anything but an allow fails the step.

**Requires authentication**: requests must be routed through the AxonFlow
Agent (`user.id` must be set). The `X-Tenant-ID` / `X-Org-ID` headers set
by the Agent auth chain override any identity fields in the body.

A plan can be executed once: re-executing returns 409, a cancelled plan
returns 409, and an expired plan returns 410.

## Request body

- PlanRequest
  - `query` string, required: Natural language task description
  - `domain` 'travel' | 'healthcare' | 'finance' | 'generic': Task domain for specialized handling
  - `execution_mode` 'auto' | 'parallel' | 'sequential' | 'balanced' | 'confirm' | 'step': How to execute sub-tasks. - `auto`: Automatically determines parallel/sequential (recommended) - `parallel`: Force parallel execution of all independent steps - `sequential`: Force sequential step-by-step execution - `balanced`: I/O-bound connector steps parallel, LLM steps sequential - `confirm`: Every step requires explicit approval before execution (Enterprise only) - `step`: First step auto-executes, subsequent steps require approval (Enterprise only)
  - `user` UserContext, required
    - `id` integer
    - `email` string
    - `role` string
    - `region` string: User's region, read by geo-based routing policies.
    - `permissions` string[]
    - `tenant_id` string
    - `org_id` string: Organisation for multi-tenant isolation, populated from the X-Org-ID header the agent stamps on the trusted hop.
  - `client` object
  - `context` object

## Response `200`

Plan executed successfully. Body is a PlanResponse for normal
execution, in which every step was decided by the organization's
policy before it ran, on every deployment (#4382; since v11.1.0
`AXONFLOW_HITL_ENABLED` is ignored). HITL `confirm`/`step`
dispatch returns an inline object (`plan_id`, `workflow_id`,
`status`, `current_step`, `total_steps`, `step_name`,
`approval_info`) instead.

- PlanResponse
  - `success` boolean
  - `plan_id` string
  - `version` integer: Plan version number (for optimistic locking)
  - `steps` object[]: Generated plan steps
    - `name` string
    - `type` string
    - `description` string
  - `workflow_execution_id` string
  - `result` unknown
  - `metadata` object
    - `tasks_executed` integer
    - `execution_mode` string
    - `execution_time_ms` integer
    - `tasks` object[]
      - `name` string
      - `status` string
      - `time_ms` integer
  - `error` string
  - `policy_info` PolicyEvaluationResult: The policy verdict carried on every orchestrator response. Its property SET is held equal to the Go type's JSON members by `TestThePublishedSchemasMatchTheTypesThePlatformMarshals`, which compares the two by reflection, so a field added to one and not the other fails CI rather than reaching a spec-generated client (#3724). The properties are also written in the Go type's declaration order as a courtesy to a reader diffing the two; nothing enforces that, and order is not part of the contract.
    - `allowed` boolean
    - `applied_policies` string[]
    - `risk_score` number
    - `severity` 'critical' | 'high' | 'medium' | 'low': Highest severity among the matched policies.
    - `severity_policy_id` string: The policy that contributed `severity`.
    - `required_actions` string[]
    - `processing_time_ms` integer
    - `database_accessed` boolean
    - `evaluation_error` boolean: Distinguishes **could not govern** from **a policy said block**, and is the only signal that does. True when the engine could NOT complete evaluation because governance-segment resolution failed (a resolver or storage error -- never "the caller belongs to zero segments"). `allowed` is always false when this is set, because the engine fails CLOSED on that error, so a consumer reading only `allowed` still behaves safely; a consumer that audits or alerts MUST read this field to tell an availability failure apart from a genuine policy match. Before it existed the only signal was the magic string `applied_policies: ["segment_resolution_failed"]`.
    - `segments_resolved` boolean: True only when a resolved, non-empty governance-segment set was actually factored into this verdict. False covers every legitimate organisation-only case -- no identity supplied, no resolver wired (community, or no SCIM), or the caller belongs to zero segments -- as well as the `evaluation_error` case. None of those are failures: the flag exists so a reader of a policy-simulation preview does not mistake a legitimate org-only allow for a segment-aware one.
    - `applied_policies_detail` AppliedPolicyDetail[]: Structured mirror of `applied_policies` carrying each matched policy's risk level and allow_override metadata, without a second query. No session override is applied to a result in v11 (#4252).
      - `policy_id` string
      - `policy_name` string
      - `description` string
      - `action` string
      - `risk_level` 'low' | 'medium' | 'high' | 'critical'
      - `allow_override` boolean: False if and only if the policy forbids a session override.
      - `matched_rule` string
      - `segment_id` string: The governance segment this policy is scoped to, or absent when it is not segment-scoped. ATTRIBUTION AND AUDIT ONLY -- it is not an override-eligibility signal anywhere: a segment-scoped policy uses the same `allow_override` contract as a tenant policy.
    - `preferred_provider` string: LLM provider a matched routing policy prefers. When more than one applying route row names one, the LAST applying row in evaluation order wins it and the routing reason: rows are walked by priority, highest first, then newest first, so the winner is the lowest-priority applying row (#4249).
    - `allowed_providers` string[]: Strict provider allow-list for compliance routing. Failover stays within this list. It is the intersection of every applying route row's list, whatever their order; an empty intersection refuses the request (`no_compliant_provider`).
    - `routing_reason` string: Why routing was changed.
  - `engine` 'anchored': The engine that decided a refused plan: `anchored`, the ADR-065 decision plane (PRD v11 §1.1). Present on the policy refusal (HTTP 403) only, with the same meaning as on `/api/v1/process`.
  - `subject_type` string: The type of principal the plan was decided for. The orchestrator admits the client credential the agent's proxy authentication forwards, so it is `Client` on every edition. Omitted on a refusal made before a subject was admitted, and wherever `engine` is.
  - `policy_bundle` string: The digest of the policy set that decided the plan. Omitted wherever `subject_type` is.
  - `verdict` 'blocked': `blocked` on the policy refusal. Omitted wherever `engine` is.
  - `complexity` string: Plan complexity hint inferred from query characteristics (e.g. simple/medium/complex). Surfaced from the planning engine so callers can apply complexity-aware UI hints.
  - `domain` string: Inferred query domain (e.g. travel, finance, healthcare). Echoes the request's `domain` field if provided, otherwise populated by the planning engine's domain classifier.
  - `parallel` boolean: True when the planning engine produced steps that can execute in parallel. Callers may choose to render parallel-step UIs differently from sequential ones.
  - `status` string: Plan lifecycle status (e.g. created, in_progress, completed, failed). Mirrors the WorkflowStatusResponse.status field for plans the orchestrator returns synchronously alongside the plan body.

## Other responses

- `400`: Invalid request body or missing context.plan_id
- `401`: Not routed through AxonFlow Agent (user.id missing)
- `403`: Refused. Policy blocked the plan at its request-level decision, or HITL confirm/step mode was requested in Community edition (a `PlanResponse` or flat error body). Or a step was refused before it ran (#4382), in `auto`, `sequential`, `parallel` or `balanced` mode: the body is a `MultiAgentStepRefusal` (`execution_blocked`; `approval_requires_durable_record` for a step that requires an approval, which only `confirm` and `step` mode can hold; or `route_refused` for an `llm-call` step whose call the organization's route rows refuse, naming the route's reason as `policy`, #4249). The plan is marked failed. A refusal is not a failure: `soft_failure_tolerance` does not absorb it. In `parallel` and `balanced` mode a group of steps is decided whole before any of them starts, so a step DECISION's refusal runs none of the group. A `route_refused` is made at the step's LLM call, not at the decision: a sibling in the same group whose call the route rows permit may already have reached its provider, and the steps after the group do not run. Before v11.1.0 a step was decided only when `AXONFLOW_HITL_ENABLED` was `true`, and a paused step answered `202`; that response no longer exists.
- `404`: Plan not found
- `409`: Plan already executed or cancelled
- `410`: Plan expired
- `413`: The request body is over 1 MiB (1,048,576 bytes), refused whole before it is decoded, in this route's `{success, error}` envelope: `error` begins `request_too_large: `. Nothing is gated, planned or run (#4249).
- `429`: Concurrent plan-execution limit reached
- `503`: Workflow engine or plan storage unavailable, or (outside confirm/step mode) the workflow engine decides no step because its step gate is not wired; the plan is marked failed.

---

[API](https://skmtc.dev/getaxonflow/apis/axonflow-orchestrator-api.md) - [All operations](https://skmtc.dev/getaxonflow/apis/axonflow-orchestrator-api/llms.txt) - [OpenAPI document](https://skmtc.dev/getaxonflow/apis/axonflow-orchestrator-api/revisions/f408ef3ec9dd?raw)
