Recipe: First approval flow end-to-end
Time: ~10 minutes Prereqs: Teams tier; one teammate with approver permissions; Python SDK 1.6.0+
This walkthrough sets up an approval-eligible deny on Bash:sudo *, triggers it from an agent, approves it from the dashboard, and verifies the audit lineage.
Step 1. Tag a rule as approval-eligible
Edit your project policy in the dashboard or as YAML:
version: '1'
rules:
- deny: 'Bash:sudo *'
escalate_on_deny: true
reason: 'sudo requires admin approval'
- allow: 'Bash:*'
Save the policy.
Step 2. Enable approvals for the project
Go to /settings/hitl. Flip the toggle ON for your project. Pick yourself as the requestor and a teammate as the approver. (Same person equals self-approval theater; the workflow refuses it.)
Click "Send test request" to verify routing. Your teammate should see a bell badge within 60 seconds.
Step 3. Install the SDK with your identity
pip install -U controlzero
controlzero install --api-key cz_live_xxx --email alice@acme.com
controlzero doctor
The doctor command should print:
IDENTITY: alice@acme.com -- resolved as user_id=<uuid> in org acme OK
Step 4. Trigger the deny + approve flow
from controlzero import Client, PolicyDeniedError
client = Client()
decision = client.guard("Bash:sudo apt-get install python3-foo")
if decision.denied and decision.hitl_eligible:
print(f"deny is approval-eligible. Requesting approval at https://app.controlzero.ai/my-requests/...")
request = client.request_approval(
decision,
message="installing test dep for FOO-1234",
timeout_s=300,
)
final = request.wait() # blocks
if final.denied:
raise PolicyDeniedError(final)
print("approved, proceeding")
Run it. Your teammate gets a bell + email. They click "Approve once" in the drawer. Within 1 second your terminal prints "approved, proceeding".
Step 5. Verify audit lineage
Open /audit. Filter by "Decision source = Approval". You should see one row:
- Action:
Bash:sudo apt-get install python3-foo - Decision:
allow (via approval) - Approval:
req_abc123(click to open the approval detail)
Expanding the row shows the full lineage: requested by alice@acme.com at T, approved by teammate@acme.com at T+12s, grant_id grnt_x7j2, decision_kind approved_once.
Step 6. Try the deny path
Run the same script again. Teammate clicks "Deny" this time. Your script raises PolicyDeniedError.
What you learned
escalate_on_deny: truetags a rule as approval-eligible.- The SDK requires
--emailat install for approval flows. client.request_approval()+request.wait()are the SDK API for the request/approve/resume cycle.- The audit log shows full lineage on every call approved via this flow.
Where to go next
- Recipe: Multi-developer project with shared keys
- Concept: Approval Workflow. Deeper architectural detail
- Concept: Secrets approvals. Approvals on credential reads
- SDK: Approval callback. The full API reference