---
title: "Reject a MAP plan step (HITL parity with WCP)"
method: POST
path: "/api/v1/plans/{planId}/steps/{stepId}/reject"
tags: ["Multi-Agent Planning"]
---

# Reject a MAP plan step (HITL parity with WCP)

`POST /api/v1/plans/{planId}/steps/{stepId}/reject`

## Request

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

Plan-scoped rejection endpoint. Symmetric with the WCP endpoint
(`/api/v1/workflows/{id}/steps/{step_id}/reject`) — same response
shape (`ApprovalResponse`) plus `plan_id`. See ADR-046.

Rejection aborts the workflow / plan: the WCP service's `RejectStep`
handles the abort. A plan no workflow backs has no paused step, and
answers 404.

## Path parameters

- `planId` string, required
- `stepId` string, required

## Request body

- object
  - `rejected_by` string
  - `reason` string: Audit justification. Required on WCP-backed plans (min 10 chars); auto-filled otherwise.

## Response `200`

Step rejected successfully

- ApprovalResponse: Rich response returned by the WCP `/approve` and `/reject` endpoints and by the MAP plan-scoped equivalents (`/api/v1/plans/{id}/steps/{step_id}/approve|reject`). Both planes project through the same helper — see ADR-046 (HITL response parity) and ADR-045 (retry_context wire contract). A refused approval answers the same status on both planes: 409 when the approval has expired (`approval_expired`), 503 when its state cannot be read (`approval_state_unreadable`). `decision` resolves to `allow` on a successful approval (the step can now proceed) or `block` on rejection (workflow aborted). `plan_id` is populated only on MAP-plane responses; on WCP-plane responses it is omitted. `retry_context` is always present and mirrors the StepGate `retry_context` shape.
  - `workflow_id` string: Underlying WCP workflow identifier
  - `plan_id` string: MAP plan id — present on MAP-plane responses. Omitted on WCP-plane responses (WCP has no plan concept).
  - `step_id` string: Step that was approved or rejected
  - `status` 'pending' | 'approved' | 'rejected' | 'expired': Flat string alias of `approval_status`. Both fields always carry the same value — `status` is convenient for loggers, dashboards, and clients that prefer a simple string; `approval_status` is the typed source of truth. First-class on both the WCP and MAP response shapes so existing clients reading either field keep working without branching.
  - `decision` 'allow' | 'block' | 'require_approval': Post-approval decision. Approved `require_approval` steps resolve to `allow`; rejected to `block`. `require_approval` on an approve/reject response means the step is still pending.
  - `reason` string: Decision reason text. Approved / rejected responses prefix the original policy reason with `Approved:` or `Rejected:`.
  - `approval_status` 'pending' | 'approved' | 'rejected' | 'expired': Terminal approval status after the mutation landed. `expired` is an auto-timeout (Evaluation-tier) — a terminal not-approved state that blocks the step, kept distinct from a human `rejected`.
  - `approval_id` string, uuid: HITL queue entry UUID of the hold this decision acted on: the step's pending hold, else its newest (hold 1 is UUID v5 over `workflow_id + ":" + step_id`; hold n >= 2, a step held again after an earlier hold was decided, is UUID v5 over `workflow_id + ":" + step_id + "#" + n`). Matches the queue row written by the WCP HITL adapter. With no hold for the step (a step-gate row outside its hold ids is not a hold) it is the hold-1 id; empty on the legacy in-memory MAP flow, and omitted when the queue could not be read.
  - `approved_by` string: Identity (X-User-ID, typically email) that approved the step
  - `approved_at` string, date-time: Timestamp when the approval was persisted
  - `rejected_by` string: Identity that rejected the step (rejection path only)
  - `rejected_at` string, date-time: Timestamp when the rejection was persisted
  - `policies_matched` PolicyMatch[]: Policies that triggered the original `require_approval` decision
    - `policy_id` string: Unique identifier for the policy
    - `policy_name` string: Human-readable name of the policy
    - `action` 'allow' | 'block' | 'require_approval' | 'redact': Action taken by this policy
    - `reason` string: Reason for the policy match
  - `retry_context` RetryContext: First-class retry and execution state (Issue #1673 Phase 1). Always present on every `StepGateResponse`, including the first gate call. Replaces the ambiguous `cached: bool` signal with unambiguous state the agent and policy engine can reason about.
    - `gate_count` integer, required: Number of /gate calls for this (workflow_id, step_id), including the current call. First call returns 1.
    - `completion_count` integer, required: Number of /complete calls for this (workflow_id, step_id). Normally 0 on first gate, 1 after the step completes.
    - `prior_completion_status` 'none' | 'completed' | 'gated_not_completed', required: "none" on first gate call. "completed" when a prior /gate + /complete both landed. "gated_not_completed" when a prior /gate landed but no /complete followed — uncertain territory the agent needs to reconcile against the downstream system before re-executing.
    - `prior_output_available` boolean, required: True iff prior_completion_status == "completed". Mirrors whether prior_output *could* be returned if include_prior_output=true.
    - `prior_output` object, nullable, required: Always present in the schema. Populated only when the caller set ?include_prior_output=true AND prior_output_available is true. Otherwise null.
    - `prior_completion_at` string, date-time, nullable, required: Timestamp of the prior /complete call, if any.
    - `first_attempt_at` string, date-time, required: Timestamp of the first /gate call for this step. On the first call, equals last_attempt_at.
    - `last_attempt_at` string, date-time, required: Timestamp of this /gate call.
    - `last_decision` 'allow' | 'block' | 'require_approval', required: Decision of the immediately prior /gate call. On the first call (gate_count == 1), equals the current decision (first-call invariant).
    - `idempotency_key` string, required: The caller-supplied business-level key recorded on this step (Issue #1673 Phase 2). Always present in the schema — empty string `""` if the caller never supplied one.
  - `message` string: Human-readable status summary

## Other responses

- `403`: MAP step rejection requires Enterprise license, or (#4249) the caller's tenant cannot see the plan's workflow.
- `404`: No paused execution / plan step found
- `409`: (#4249) The rejection is refused, and nothing changes, when the plan's bound workflow has ended, when the plan's workflow is still being set up, or, for a plan that began executing before the binding existed, when a workflow named for the plan has ended or more than one is running. The same selection as the approve route.

---

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