---
title: "Execute multi-agent plan"
method: POST
path: "/api/v1/plan"
tags: ["Multi-Agent Planning"]
---

# Execute multi-agent plan

`POST /api/v1/plan`

## Request

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

Multi-Agent Planning (MAP) endpoint for complex, multi-step tasks.

## How MAP Works
1. **Decomposition**: LLM analyzes query and breaks into sub-tasks
2. **Planning**: Creates workflow with dependencies
3. **Execution**: Runs tasks (parallel when possible)
4. **Aggregation**: Synthesizes results into final response

## Provider routing
The organization's route rows (`allowed_providers`,
`preferred_provider`) bind every LLM call a plan makes, as they bind
`/api/v1/process` (#4249): the two calls that generate the plan and
each `llm-call` step when it runs, decided over the prompt the call
sends. When several applying rows name a `preferred_provider`, the last
applying row in evaluation order (priority descending, then newest
first) wins it; `allowed_providers` intersect across every applying row.
Rows that permit no provider refuse the call before any provider
is called (`no_compliant_provider`, or `segment_not_established`), and a
route that cannot be established refuses it
(`segment_resolution_failed`, `decision_enforcement_unavailable`).
Plan generation answers such a refusal 403; a refused step fails with
the reason.

## Execution Modes
- `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 (Enterprise only)
- `step`: First step auto-executes, subsequent require approval (Enterprise only)

## Domains
- `travel`: Flights, hotels, itineraries
- `healthcare`: Medical queries
- `finance`: Financial analysis
- `generic`: General-purpose tasks

Plan steps are automatically routed to matching connectors based on capabilities.
Community: Subject to connector limits (2 connectors).
Enterprise: Unlimited connectors with multi-connector fallback support.

**Requires authentication**: Requests must come through the Agent.

## 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

- 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

- `401`: Authentication required
- `403`: The organization's route rows refuse the planner's LLM call, or the route could not be established (#4249). No provider was called; the `error` names the reason.
- `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).
- `503`: Planning engine unavailable

---

[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)
