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 severe enough to demand one on its own. Neither is hidden — both name themselves on the request.
Prerequisites
Section titled “Prerequisites”- A project you can administer (agency
owner,admin, ormember) - A cycle that has not yet been dispatched
Turning the gate on
Section titled “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:
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.
A default for new projects
Section titled “A default for new projects”Set the flag once for the workspace and every project created afterwards starts with it:
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
requiresApprovalis 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
requiresApprovalin the body uses that value; the default fills in only when the request says nothing. nullclears it, returning the workspace to “no default”; omitting a field leaves it as it was.GETon 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
Section titled “When the lane demands it instead”A cycle can park for approval on a project that never turned the gate on. 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:
{"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 explains what each lane is classifying.
Finding what is waiting
Section titled “Finding what is waiting”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
Section titled “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
Section titled “Deciding”There is no separate approve or deny endpoint. Both are the status field on one call.
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 row.
To withdraw a request instead of deciding it:
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
Section titled “Asking the client a question”POST /v1/api/pending-requests/client-question opens a non-blocking requirement_clarification addressed to the client collaborator:
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
Section titled “Contract coverage”If an approval will not resolve
Section titled “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_approvaland carried acycleId. Only that combination resumes a run. - Approving does nothing for a
plan_approvalorcode_reviewrow — expected. Those types record the decision without resuming a workflow. - A run parked and no request appears — check that
requiresApprovalis set on the project you are actually running. - A cycle parked although
requiresApprovalisfalse— read the request’scontext.lane. A forced lane gates on its own. See 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.