Skip to main content

Quick Start

Supported modes: Hosted Hybrid Local Available in: Free Solo Teams

Add AI governance to your agent in under 5 minutes. There are two deployment paths. Pick the one that fits your situation:

PathBest forCode changes
Gateway (Option A)Existing agents, quick rolloutNone. Change one URL
SDK (Option B)New agents, tightest integrationInstall package, wrap tool calls

Both enforce the same policies defined in your dashboard.

Step 1: Sign Up

  1. Go to app.controlzero.ai and create an account.
  2. Want to explore first? Try the interactive demo at app.controlzero.ai/demo. No account required.

The free tier includes 10,000 governed actions per month with no credit card required.

Step 2: Create a Project

  1. After signing in, the 2-step onboarding walks you through creating your first project.
  2. Name your project (e.g., my-agent) and select your environment.
  3. Copy your API Key from the project settings page (e.g., cz_live_abc123...).

Set it as an environment variable:

export CONTROLZERO_API_KEY="cz_live_your_key_here"

Step 3: Define a Policy in the Dashboard

Policies live at the org level in the Policy Library and are attached to projects. This lets one policy govern many projects with per-project overrides.

3a. Create the library policy

Click Library in the sidebar, then New policy.

  • Name: db-read-only
  • Description: Agents may query the database but cannot modify data.
  • Rules (JSON array): paste the snippet below, or click Insert sample.
[
{ "effect": "allow", "actions": ["database:read"], "resources": ["*"] },
{ "effect": "deny", "actions": ["database:write"], "resources": ["*"] },
{ "effect": "deny", "actions": ["database:admin"], "resources": ["*"] }
]

Click Create & publish v1. The policy is saved and auto-published as version 1 so it is immediately attachable.

Canonical rule shape

actions, resources, and principals are arrays. The create dialog accepts the flat action / resource singular form from older docs and normalises it for you.

SQL semantic classes

database:read is a portable canonical class that covers SELECT, EXPLAIN, SHOW, DESCRIBE, and CTE statements regardless of the dialect's keyword spelling. database:write covers INSERT/UPDATE/DELETE/MERGE and other data-modifying statements. database:admin covers schema and permission changes. Multi-statement piggyback like SELECT 1; DROP TABLE x resolves to database:admin so a deny-admin rule catches it. You can still target a specific keyword (e.g. database:DROP) when you need finer control. See Canonical Tool Names for the full mapping.

3b. Attach the policy to your project

Open your project, click Attach policy, pick db-read-only, keep the state as Active, confirm. The project's policy bundle now enforces the rules and every SDK or gateway client will pick them up within 60 seconds.

Step 4: Integrate Your Agent

Option A: Gateway (Zero Code Changes)

The gateway is a transparent proxy. Point your LLM base URL at it and add two headers. Your existing agent code keeps working. The gateway enforces policies on every request and response automatically.

1. Change Your Base URL

Anthropic (Claude):

# Before
ANTHROPIC_BASE_URL=https://api.anthropic.com

# After
ANTHROPIC_BASE_URL=https://gateway.controlzero.ai

OpenAI:

# Before
OPENAI_BASE_URL=https://api.openai.com

# After
OPENAI_BASE_URL=https://gateway.controlzero.ai/v1

2. Add Control Zero Headers

Add these headers to every LLM request:

X-ControlZero-API-Key: cz_live_your_key_here
X-ControlZero-Agent-ID: my-first-agent
  • X-ControlZero-API-Key (required) is your project key from the dashboard.
  • X-ControlZero-Agent-ID (optional) labels the caller for audit attribution. Defaults to <provider>-direct if omitted.

3. Test It (Anthropic)

curl -X POST https://gateway.controlzero.ai/v1/messages \
-H "Content-Type: application/json" \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "X-ControlZero-API-Key: $CONTROLZERO_API_KEY" \
-H "X-ControlZero-Agent-ID: my-first-agent" \
-d '{
"model": "claude-sonnet-4-6",
"max_tokens": 100,
"messages": [{"role": "user", "content": "What is 2+2?"}]
}'

Your Anthropic API key (x-api-key) authenticates with Anthropic. Your Control Zero API key (X-ControlZero-API-Key) enables governance, audit logging, and policy enforcement. Both are required.

That is it. The gateway intercepts every LLM response, evaluates tool calls against your policies, and blocks unauthorized actions. Pre-flight checks (model blocking, cost caps, PII detection) run on every request before it reaches the provider.

See the Gateway guide for self-hosted deployment, supported providers, and configuration details.

Option B: SDK Integration

Install the SDK to govern tool calls at the application level. With an API key the SDK pulls your signed policy bundle from the dashboard automatically -- no local policy file required. Call guard() before executing any tool.

1. Install the SDK

Python:

pip install controlzero

Requirements: Python 3.9 or later.

Node.js:

npm install @controlzero/sdk

Go:

go get controlzero.ai/sdk/go

2. Add Governance to Your AI App (Hosted Mode)

Python:

from controlzero import Client

# SDK pulls your dashboard policy on first call. Signed bundle, verified
# locally. Audit streams to the dashboard trail.
cz = Client(api_key="cz_live_your_key_here")

# Policies are managed in app.controlzero.ai, not in code. The SDK
# derives the SQL semantic class (read|write|admin|exec) from the
# `sql` argument, so a `database:read` rule fires for SELECT,
# EXPLAIN, SHOW, and CTE statements regardless of dialect.
result = cz.guard("database", method="SELECT", args={"sql": "SELECT id FROM orders"})
print(result.decision) # "allow" or "deny" per dashboard rules

result = cz.guard("database", method="DROP", args={"sql": "DROP TABLE orders"})
print(result.decision) # typically "deny" (matches database:admin)
print(result.reason)

# Or raise on deny:
from controlzero import PolicyDeniedError
try:
cz.guard("database", method="DROP", args={"sql": "DROP TABLE orders"}, raise_on_deny=True)
except PolicyDeniedError as e:
print(f"Blocked: {e.decision.reason}")

cz.close()

Node.js:

import { Client } from '@controlzero/sdk';

// Hosted mode uses the async factory. The SDK pulls your dashboard
// policy on first call, verifies signature, decrypts, and enforces.
const cz = await Client.create({ apiKey: 'cz_live_your_key_here' });

const result = cz.guard('database', {
method: 'SELECT',
args: { sql: 'SELECT id FROM orders' },
});
console.log(result.decision); // "allow" or "deny"

await cz.close();

Go:

import controlzero "controlzero.ai/sdk/go"

client, err := controlzero.New(
controlzero.WithAPIKey("cz_live_your_key_here"),
)
if err != nil {
log.Fatal(err)
}
defer client.Close()

Two modes, one client:

  • Hosted mode (api_key=...): the SDK pulls the signed policy bundle from the dashboard, verifies and decrypts it locally, and ships audit to the remote trail. Keys and bundles are cached under ~/.controlzero/cache/ so restarts work offline. This is the recommended flow -- policies live where your team can manage them.
  • Local mode (policy=... or policy_file=...): zero network for policy eval. Audit stays in a rotating local file. Good for air-gapped deployments and controlzero.yaml checked into the repo.

The guard() method returns a PolicyDecision with decision ("allow" or "deny") and reason fields. Pass raise_on_deny=True to raise PolicyDeniedError instead of returning a deny decision.

What happens when a call is blocked

When a guard() call returns a deny decision, you can inspect the result or raise an exception:

from controlzero import Client, PolicyDeniedError

cz = Client(api_key="cz_live_your_key")

# Check the decision object. Note: `database:execute` is a legacy action
# name. The canonical name is `database:write`; both forms match the
# same calls.
result = cz.guard("database", method="execute", args={"sql": "DROP TABLE orders"})
if result.effect == "deny":
print(f"Blocked: {result.reason}")
# result.effect = "deny"
# result.reason = human-readable explanation
# result.policy_id = which policy matched

# Or raise on deny
try:
cz.guard("database", method="execute", args={"sql": "DROP TABLE orders"}, raise_on_deny=True)
except PolicyDeniedError as e:
print(f"Blocked by policy {e.decision.policy_id}: {e.decision.reason}")

Step 5: Scan Your Project for Governance Gaps

Use the CLI scanner to analyze your codebase and identify ungoverned AI tool calls:

npx @controlzero/scanner

The scanner produces a governance grade (A through F) and lists specific findings. You can integrate it into CI/CD pipelines. See the CLI Scanner guide for details.

Step 6: Use the Context Manager (Optional)

Client supports the context manager protocol, which calls close() on exit to flush audit logs:

from controlzero import Client, PolicyDeniedError

with Client(policy_file="./controlzero.yaml") as cz:
result = cz.guard(
"filesystem",
{"path": "/data/report.csv"},
method="read_file",
context={"resource": "/data/report.csv"},
)
if result.denied:
print(f"Access denied: {result.reason}")
else:
print(f"Allowed: {result.reason}")

Complete Governance Scenario

The following end-to-end example shows a realistic agent that reads from a database and attempts an unauthorized write. The write is blocked by policy before the tool is ever invoked.

from controlzero import Client

policy = {
"rules": [
{"allow": "database:read", "reason": "Reads are permitted"},
{"deny": "database:*", "reason": "All other database operations are blocked"},
]
}

def run_analyst_agent(cz: Client) -> None:
# Step 1: check if read is allowed. The SDK derives the SQL
# semantic class (read) from the `sql` argument, so the
# `database:read` rule fires.
read_decision = cz.guard(
"database",
{"sql": "SELECT * FROM orders WHERE region = 'APAC'"},
method="SELECT",
)
print(f"Read decision: {read_decision.decision} - {read_decision.reason}")

# Step 2: check if write is allowed (it will be denied; class is `write`)
write_decision = cz.guard(
"database",
{"sql": "UPDATE orders SET status = 'processed' WHERE region = 'APAC'"},
method="UPDATE",
)
print(f"Write decision: {write_decision.decision} - {write_decision.reason}")

with Client(policy=policy) as cz:
run_analyst_agent(cz)

Expected output:

Read decision: allow - Reads are permitted
Write decision: deny - All other database operations are blocked

View the Audit Log

Every guard() decision, allowed or denied, is automatically logged. In the dashboard, navigate to your project and click Audit Log to see:

TimestampToolMethodDecisionAgent
12:00:01databaseSELECTallowagent-analyst
12:00:02databaseUPDATEdenyagent-analyst

SDK Reference: Key Methods

Client.__init__()

def __init__(
self,
api_key: str = None, # optional, for hosted mode (cz_live_ or cz_test_)
policy: dict = None, # inline policy dict with "rules" key
policy_file: str = None, # path to controlzero.yaml policy file
strict_hosted: bool = False, # raise on hybrid (API key + local policy) instead of warn
log_path: str = "./controlzero.log", # local audit log path
log_rotation: str = "daily", # audit log rotation interval
log_retention: str = "30 days", # how long to keep rotated logs
log_compression: str = None, # compress rotated logs (e.g. "gz")
log_format: str = "json", # audit log format
): ...

Policy resolution order: policy= arg, policy_file= arg, CONTROLZERO_POLICY_FILE env var, ./controlzero.yaml in cwd, CONTROLZERO_API_KEY env var (hosted mode), then no-op pass-through.

Client.guard(tool, args, method, raise_on_deny, context)

Evaluates the tool call against the loaded policy. Returns a PolicyDecision with decision ("allow" or "deny"), reason, and policy_id fields. The action string evaluated is "{tool}:{method}".

Pass raise_on_deny=True to raise PolicyDeniedError on a deny decision instead of returning it.

Client.close()

Flushes buffered audit logs and closes any open connections.

PolicyDeniedError

Raised by guard() when raise_on_deny=True and the policy denies the action.

AttributeTypeDescription
e.decision.effectstrAlways "deny".
e.decision.reasonstrHuman-readable explanation.
e.decision.policy_idstrID of the policy that matched, or None.

Debugging

Set CZ_DEBUG=1 to enable verbose debug output on stderr. This prints every policy evaluation, cache hit/miss, and audit log flush to help diagnose governance issues:

CZ_DEBUG=1 python my_agent.py

Sample debug output:

[CZ DEBUG] initialized client agent=my-agent project=proj_abc123
[CZ DEBUG] policy_eval tool=database method=SELECT action=database:SELECT class=database:read effect=allow policy_id=db-read-only latency_ms=0.12
[CZ DEBUG] policy_eval tool=database method=UPDATE action=database:UPDATE class=database:write effect=deny policy_id=db-read-only latency_ms=0.08
[CZ DEBUG] audit_flush count=2 status=ok

The latency_ms field shows policy evaluation time. Policy evaluation is local and does not make network requests.

Next Steps