> ## Documentation Index
> Fetch the complete documentation index at: https://velt.dev/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Resolve Step

Use this API to resolve a step that's currently `running` or `waiting`. The endpoint supports two families of actions:

* **Admin-grade overrides** (`force-approve`, `force-reject`, `force-complete`, `force-fail`) — break a stuck step out of `waiting` or terminate a running step with an admin-supplied output. Audit log records the actor as an operator override.
* **Reviewer-scoped decisions** (`reviewer-approve`, `reviewer-reject`) — record a reviewer's approve/reject decision through the API instead of through the comment-reply flow. Auth-gated to userIds in the step's declared reviewer list.

Every resolve writes a `step.overridden` audit event with `{ actorId, action, reason, decision }`.

# Endpoint

`POST https://api.velt.dev/v2/workflow/steps/resolve`

# Headers

<ParamField header="x-velt-api-key" type="string" required>
  Your API key.
</ParamField>

<ParamField header="x-velt-auth-token" type="string" required>
  Your [Auth Token](/security/auth-tokens).
</ParamField>

# Body

#### Params

<ParamField body="data" type="object" required>
  <Expandable title="properties">
    <ParamField body="executionId" type="string" required>
      The execution containing the step.
    </ParamField>

    <ParamField body="stepId" type="string" required>
      The step to resolve.
    </ParamField>

    <ParamField body="action" type="string" required>
      One of `force-approve` / `force-reject` / `force-complete` / `force-fail` / `reviewer-approve` / `reviewer-reject`. See the action matrix below.
    </ParamField>

    <ParamField body="actorId" type="string" required>
      1–256 chars. For `force-*` actions, the operator's identifier. For `reviewer-*` actions, **must equal a userId declared in the step's reviewer list** — otherwise the request is rejected with `PERMISSION_DENIED`.
    </ParamField>

    <ParamField body="output" type="object">
      Optional. Admin-supplied output for `force-complete` / `force-fail`. Ignored for approve/reject actions — `decision`, `approved`, and `approvalReply` are computed authoritatively by the engine.
    </ParamField>

    <ParamField body="reason" type="string">
      ≤ 2000 chars. Recorded on the audit event.
    </ParamField>
  </Expandable>
</ParamField>

#### Action matrix

| Action             | Auth gate                                 | Allowed step states          | Resulting step status | Notes                                                                                                                   |
| ------------------ | ----------------------------------------- | ---------------------------- | --------------------- | ----------------------------------------------------------------------------------------------------------------------- |
| `force-approve`    | auth token                                | `waiting` (human only)       | `completed`           | Synthesizes `output.decision = 'approve'`. Downstream edges that gate on `decision == 'approve'` fire.                  |
| `force-reject`     | auth token                                | `waiting` (human only)       | `completed`           | Synthesizes `output.decision = 'reject'`. Downstream edges that gate on `decision == 'reject'` fire.                    |
| `force-complete`   | auth token                                | `running` or `waiting` (any) | `completed`           | Caller-supplied `output` (or `{}`) is written through. Downstream edges fire as if the step completed normally.         |
| `force-fail`       | auth token                                | `running` or `waiting` (any) | `failed`              | Caller-supplied `output` is recorded. Edges that gate on the `failed` terminal status fire (e.g. escalation routing).   |
| `reviewer-approve` | auth token + `actorId ∈ step.reviewerIds` | `waiting` (human only)       | `completed`           | Mechanically identical to `force-approve` but distinguished in the audit log so "reviewer acted" ≠ "operator overrode". |
| `reviewer-reject`  | auth token + `actorId ∈ step.reviewerIds` | `waiting` (human only)       | `completed`           | Mechanically identical to `force-reject`. Same audit distinction.                                                       |

<Note>
  **Authority-of-record.** For all approve/reject actions (admin and reviewer), the engine computes `decision`, `approved`, and `approvalReply` itself. Caller-supplied `output` keys with the same names cannot override them. Other keys in `output` pass through. This prevents a reviewer from sending `action: 'reviewer-reject'` with `output: { decision: 'approve' }` and routing edges as an approval while the audit log records a rejection.
</Note>

<Warning>
  **`reviewer-reject` and `force-reject` do not populate `output.rejectedBy` / `output.rejectorMandatory`.** Those fields are only set on the natural [Record Reviewer Decision](/api-reference/rest-apis/v2/approval-engine/steps/record-reviewer-decision) aggregator path. If you drive [loop regions](/ai/approval-engine/customize-behavior#loop-regions) via `reviewer-reject`, the default loop predicate (`decision == 'reject' && rejectorMandatory == true`) won't fire — supply an explicit `onIterationReject.when` predicate that filters on `decision` alone.
</Warning>

## **Example Requests**

#### Reviewer approves through the API

```JSON theme={null} theme={null}
{
  "data": {
    "executionId": "exec_1777374504255_xzy43k9q",
    "stepId": "step_agent-draft_..._lwofay__to__human-legal",
    "action": "reviewer-approve",
    "actorId": "u_legal_01",
    "reason": "looks good"
  }
}
```

#### Admin force-completes a stuck blocking-agent step

```JSON theme={null} theme={null}
{
  "data": {
    "executionId": "exec_1777374504255_xzy43k9q",
    "stepId": "step_blocking-agent_...",
    "action": "force-complete",
    "actorId": "ops_jane",
    "output": { "summary": "Manually closed; agent hung past deadline" },
    "reason": "Agent stuck on retry; closing to unblock execution"
  }
}
```

#### Admin force-fails a step to trigger an escalation edge

```JSON theme={null} theme={null}
{
  "data": {
    "executionId": "exec_1777374504255_xzy43k9q",
    "stepId": "step_agent-draft_..._lwofay__to__human-brand",
    "action": "force-fail",
    "actorId": "ops_jane",
    "reason": "Reviewer on PTO indefinitely; routing to escalation edge"
  }
}
```

# Response

#### Success Response

```JSON theme={null} theme={null}
{
  "result": {
    "resolved": true,
    "executionId": "exec_1777374504255_xzy43k9q",
    "stepId": "step_agent-draft_..._lwofay__to__human-brand",
    "action": "force-complete"
  }
}
```

#### Failure Response

```JSON theme={null} theme={null}
{
  "error": {
    "message": "ERROR_MESSAGE",
    "status": "FAILED_PRECONDITION"
  }
}
```

**Errors:**

| Code                  | Cause                                                                                                                                                                      |
| --------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `INVALID_ARGUMENT`    | Schema validation (missing `action`, missing `actorId`, etc.).                                                                                                             |
| `PERMISSION_DENIED`   | `reviewer-approve` / `reviewer-reject` with `actorId` not in the step's declared reviewer list.                                                                            |
| `FAILED_PRECONDITION` | Step is already terminal, step is not in an allowed state for the action (e.g. `force-approve` on a non-human step), or the underlying transition CAS rejected the change. |
| `NOT_FOUND`           | `executionId` or `stepId` does not exist.                                                                                                                                  |

<Note>
  **Workspace-admin RBAC is post-GA.** Today the `force-*` actions gate on the standard auth token only. Until role-based access lands, restrict who can call this endpoint inside your own application.
</Note>

<ResponseExample>
  ```js theme={null} theme={null}
  {
    "result": {
      "resolved": true,
      "executionId": "exec_1777374504255_xzy43k9q",
      "stepId": "step_agent-draft_..._lwofay__to__human-brand",
      "action": "force-complete"
    }
  }
  ```
</ResponseExample>
