# Approvals

An **approval** is a blocking pending request that stops a cycle from being dispatched until a person decides. Projects are autonomous by default. Two things can open one: a project policy you turn on, and a [risk lane](/concepts/triage/) severe enough to demand one on its own. Neither is hidden — both name themselves on the request.

## Prerequisites

- A project you can administer (agency `owner`, `admin`, or `member`)
- A cycle that has not yet been dispatched

## Turning the gate on

Approval is the project-level `requiresApproval` flag, default **off**.

**In the dashboard:** it is the **approval gate** switch, in two places for the same flag — **Project settings → Agent → Behavior**, and inline on a cycle's **Request Queue** tab for the operator already looking at a run.

**Over the API:**

```bash
curl -sS -X PATCH "$HLIX_BASE_URL/v1/api/projects/$PROJECT_ID" \
  -H "x-api-key: $HLIX_API_KEY" \
  -H "X-Organization-Id: $HLIX_WORKSPACE_ID" \
  -H 'content-type: application/json' \
  -d '{"requiresApproval":true}'
```

With the flag set, the project orchestrator opens a blocking `deployment_approval` request **before** launching a gated cycle and parks the run. Approving resumes dispatch; denying fails that cycle. With the flag off, an autonomous run is byte-for-byte unchanged — the gate adds no step it does not need to.
**What the gate controls:** Approval governs **dispatch**. It is not a claim that no task branch was ever created, or that a repo-less project's internal working history has not advanced. It is the decision point before agents are launched on that cycle.

### A default for new projects

Set the flag once for the workspace and every project created afterwards starts with it:

```bash
curl -sS -X PUT "$HLIX_BASE_URL/v1/api/settings/org/project-defaults" \
  -H "x-api-key: $HLIX_API_KEY" \
  -H "X-Organization-Id: $HLIX_WORKSPACE_ID" \
  -H 'content-type: application/json' \
  -d '{"requiresApproval":true}'
```

Three things about it are worth being precise on:

- **It applies at creation only.** A project's own `requiresApproval` is concrete from the moment the row exists, so changing the workspace default never reaches back and rewrites projects that already exist. Change those individually.
- **An explicit choice wins.** Creating a project with `requiresApproval` in the body uses that value; the default fills in only when the request says nothing.
- **`null` clears it**, returning the workspace to "no default"; omitting a field leaves it as it was. `GET` on the same path reads the current values.

Writing this setting requires `owner` or `admin`. It is not in the published contract, so there is no generated SDK method for it.

## When the lane demands it instead

A cycle can park for approval on a project that never turned the gate on. [Triage](/concepts/triage/) classifies every cycle into a risk lane, and four of those lanes — `stop`, `design-review`, `architecture-review`, and `manual-lane` — open the same blocking request on their own.

The two conditions OR together: the project's flag, or the cycle's lane. Whichever says "gate" wins, and there is no way to opt a lane-gated cycle out short of re-triaging it.

The request's `context` names the cycle's lane whenever it has one:

```json
{"projectId":"…","gate":"cycle_dispatch","lane":"architecture-review"}
```

Read it as the classification, and infer from it. One of the four lanes above means this cycle would have parked whatever the project's setting says. Any other lane — or no `lane` key at all — means the project's own policy is what stopped it. [Review & QA gates](/concepts/review-gates/#lanes-that-demand-a-human) explains what each lane is classifying.

## Finding what is waiting

```bash
curl -sS "$HLIX_BASE_URL/v1/api/pending-requests?status=pending" \
  -H "x-api-key: $HLIX_API_KEY" \
  -H "X-Organization-Id: $HLIX_WORKSPACE_ID"
```

`status`, `cycleId`, and `taskId` are the supported query filters. A per-cycle read is also available at `GET /v1/api/cycles/:id/pending-requests`.

Each row carries `requestType`, `blocking`, `requiredActor`, `title`, `description`, `context`, `status`, `deadline`, and — once decided — `decision`, `decidedBy`, `decidedAt`, and `resultingAction`.

In the dashboard, **Approvals** lists every pending request across the workspace.

## Request types

Eight types share one queue. Only the first one resumes a parked run.

| Type | Raised when | Resolving it |
| --- | --- | --- |
| `deployment_approval` | A gated cycle is about to be dispatched | Approve resumes dispatch; deny fails the cycle |
| `tool_permission` | An agent asks to use a gated tool | The decision is replayed onto the live agent session |
| `plan_approval` | A plan wants sign-off | Recorded only |
| `code_review` | A change wants human review | Recorded only |
| `conflict_resolution` | A merge conflict needs a decision | Recorded only |
| `budget_exceeded` | A cost ceiling was hit | Recorded only |
| `requirement_clarification` | An agent needs an answer from you or the client | Recorded only |
| `cycle_proposal` | A cycle is proposed for adoption | Recorded only |

"Recorded only" is deliberate and honest: those types capture the decision and its audit trail, but do not themselves resume a workflow.

## Deciding

There is no separate approve or deny endpoint. Both are the `status` field on one call.

```bash
curl -sS -X POST "$HLIX_BASE_URL/v1/api/pending-requests/$REQUEST_ID/resolve" \
  -H "x-api-key: $HLIX_API_KEY" \
  -H "X-Organization-Id: $HLIX_WORKSPACE_ID" \
  -H 'content-type: application/json' \
  -d '{"status":"approved","decision":"Ship it — the migration is reversible."}'
```

`status` is `approved` or `denied` and is required; `decision` is optional free text up to 5000 characters. The response is the updated request row with `status`, `decision`, `decidedBy`, and `decidedAt` written. Every decision emits an event and writes a guaranteed [audit](/concepts/review-gates/) row.

To withdraw a request instead of deciding it:

```bash
curl -sS -X POST "$HLIX_BASE_URL/v1/api/pending-requests/$REQUEST_ID/cancel" \
  -H "x-api-key: $HLIX_API_KEY" \
  -H "X-Organization-Id: $HLIX_WORKSPACE_ID"
```

Cancelling a `deployment_approval` un-parks the cycle as a **deny**. A cancelled gate does not leave a run waiting forever.

## Asking the client a question

`POST /v1/api/pending-requests/client-question` opens a non-blocking `requirement_clarification` addressed to the client collaborator:

```bash
curl -sS -X POST "$HLIX_BASE_URL/v1/api/pending-requests/client-question" \
  -H "x-api-key: $HLIX_API_KEY" \
  -H "X-Organization-Id: $HLIX_WORKSPACE_ID" \
  -H 'content-type: application/json' \
  -d '{"cycleId":"'"$CYCLE_ID"'","title":"Invoice numbering","question":"Should invoice numbers restart each fiscal year?"}'
```

The body accepts exactly `cycleId`, `title`, and `question` — `requestType` and `requiredActor` are set by the server and rejected if you send them. Only the agency can ask; the named client can answer.

## Contract coverage
**Only the per-cycle list is in the published contract:** `GET /v1/api/cycles/:id/pending-requests` appears in `openapi.json`. The workspace-wide list, `resolve`, `cancel`, and `client-question` do **not**, so there is no generated SDK method for them today. Use the dashboard or a direct HTTP call.

## If an approval will not resolve

- `{"error":"Request already approved"}` — someone decided it first. Re-read the row before acting.
- `{"error":"Not found"}` — wrong id, another workspace, or you are a client collaborator and the request was not addressed to you. Existence is deliberately not revealed.
- The cycle stays parked after approval — confirm the request was `deployment_approval` and carried a `cycleId`. Only that combination resumes a run.
- Approving does nothing for a `plan_approval` or `code_review` row — expected. Those types record the decision without resuming a workflow.
- A run parked and no request appears — check that `requiresApproval` is set on the project you are actually running.
- A cycle parked although `requiresApproval` is `false` — read the request's `context.lane`. A forced lane gates on its own. See [Triage](/concepts/triage/).
- You set the workspace default and existing projects did not change — by design; it applies to projects created after it. Set those projects individually.

## Next steps

[Cycles](/running/cycles/)
  [Review & QA gates](/concepts/review-gates/)
  [Triage](/concepts/triage/)
  [Reviewing output](/running/review/)