Skip to main content

Human-in-the-Loop (HITL)

Human-in-the-Loop authorization means some access requests cannot be decided automatically — a human must explicitly approve or deny them before the requester is granted access.

When a request triggers HITL, the evaluation does not return a final allow or deny. Instead it returns a pending state that your application must handle.

The evaluation response

A HITL-triggered evaluation returns decision: false with a reason_code of authorization_pending:

{
"decision": false,
"context": {
"reason_code": "authorization_pending",
"approval_id": "apr-c3d4e5f6",
"auth_req_id": "apr-c3d4e5f6",
"expires_in": 300,
"interval": 5
}
}
FieldMeaning
reason_codeauthorization_pending — a human approval is required
approval_id / auth_req_idID of the approval request. Use this to track or display the pending request
expires_inSeconds until this pending request expires. If nobody decides in time, the next evaluation call starts a fresh approval cycle — see What happens on expiry
intervalSuggested polling interval in seconds. Poll no faster than this — see slow_down

If you're using the Go or Node.js Vengtoo SDK, use authorizeWithPolling() instead of calling evaluate directly. It handles the entire pending → poll → resolve loop for you:

// Node.js
const result = await vengtoo.authorizeWithPolling(params, {
timeoutMs: 5 * 60_000,
onPending: (authReqId, expiresIn) => {
// Called once, when the request first enters the pending state.
// Show a waiting UI here — not called again on subsequent polls.
console.log(`Waiting for approval: ${authReqId}`);
}
});

if (result.decision) {
// proceed
} else {
// denied, timed out, or a safety re-check blocked the reused approval
// — inspect result.context.reason_code
}
// Go
result, err := client.AuthorizeWithPolling(ctx, req, &vengtoo.PollingOptions{
Timeout: 5 * time.Minute,
OnPending: func(authReqID string, expiresIn int) {
log.Printf("waiting for approval: %s", authReqID)
},
})

authorizeWithPolling():

  • Calls onPending exactly once, when the request first enters the pending state — not on every poll.
  • Polls at the server's suggested interval, backing off automatically on slow_down without surfacing it to your code.
  • Returns (never throws) on timeout, with reason_code: "approval_timeout" — handle it the same way as any other denial.

For languages without SDK support yet, see Manual polling.

Manual polling

  1. Detect the pending state — check for decision: false and reason_code: authorization_pending.
  2. Show a waiting state to the user — the action is blocked until a human decides.
  3. Poll the evaluate endpoint at the interval cadence, passing the same request. Vengtoo returns the pending state on each poll until a human acts or the request expires.
  4. Handle slow_down — if you poll faster than the server expects, you receive reason_code: "slow_down". Back off and retry; don't surface this to the end user, it's a rate signal not a decision.
  5. Handle the outcome:
    • Approved → decision: true, reason_code: "approved_by_human".
    • Denied → decision: false, reason_code: "access_denied".
    • Expired with no decision → see What happens on expiry.
{
"decision": true,
"context": {
"reason_code": "approved_by_human",
"approval_id": "apr-c3d4e5f6",
"access_path": "direct"
}
}

What happens on expiry

If nobody approves or denies before expires_in runs out, the pending request expires — it does not become a permanent denial. The next evaluation call for the same subject, resource, and action creates a fresh approval cycle, returning a new authorization_pending response with a new approval_id.

This means an unanswered HITL request is never a dead end. The caller can retry, and the human will be prompted again.

Approvals and denials are time-bounded, not permanent

When a human approves or denies a request, that decision is reused for a configurable window (default: one hour from the moment of decision). Within that window:

  • A repeated call for the same subject, resource, and action gets the same outcome instantly — without prompting a human again.
  • After the window expires, the next call creates a new pending request and the human is asked to decide again.

This applies equally to approvals and denials — neither is permanent. The window is configured per policy via approval_ttl_hours in the policy's HITL modifier settings.

The safety re-check on reuse

Before honoring a previously approved decision, Vengtoo re-checks a narrow set of safety invariants to confirm the approval is still valid to act on:

  • Is the policy that triggered the approval still active?
  • Does the subject still exist?

If a re-check fails, the approval is not honored and you receive a distinct reason code instead of approved_by_human:

Reason codeMeaning
policy_inactiveThe policy that originally required approval has since been disabled
subject_deletedThe subject no longer exists

These appear distinctly in the audit log — "approved, then blocked by a safety re-check" is never confused with a standard policy denial.

Treating pending as deny

If your application cannot handle a waiting state, treat authorization_pending as a deny and surface a clear message. Never default to allowing access while a decision is pending.

When to use HITL

  • An action is high-risk or irreversible (e.g. deleting production data, transferring funds).
  • Compliance requires a human audit trail before sensitive access is granted.
  • An AI agent is requesting access on behalf of a user and the action scope exceeds a pre-approved threshold.
  • You want a hard boundary outside the model — a human gate that holds regardless of what an AI agent was told or convinced to attempt.
  • Access Paths — how evaluation works for standard requests.
  • Delegation — granting an agent pre-approved access without requiring per-request human approval.
  • Evaluation API — the full request and response shape.