# Policies API — Control Agent Tool Calls Programmatically

REST reference for ACP's four-layer policy system. Read and write workspace, role, agent-type, and user policy from code — the same policy the console writes.

# Policies

A policy decides what an agent's tool calls are allowed to do — allow, deny, rate-limit, or redact — and whether the workspace is enforcing those decisions or just watching. These endpoints read and write that policy from code. They write the same documents the console does; the console is just another client.

```
https://api.agenticcontrolplane.com/<workspace>/admin
```

Policy endpoints carry your workspace slug in the path (it must match the slug in your key). Reads work with any key that carries the scope. **Writes are human-only:** a signed-in owner or admin. An API key, which is an agent's credential, is rejected on every policy write, so an agent can never change the rules it runs under; it can [propose a rule](/docs/self-optimize) for a human to confirm.

## The four layers

Policy is evaluated across layers that **intersect**: for any tool and tier, the most restrictive permission any applicable layer sets is the one that holds. A layer can tighten what a broader layer allows; it cannot loosen what a broader layer denies.

| Layer | Endpoint | Scope |
|-------|----------|-------|
| Workspace | `/admin/workspacePolicy` | The baseline for everything in the workspace. |
| Role | `/admin/rolePolicies/{role}` | Per-role overrides — `owner`, `admin`, `member`. |
| Agent type | `/admin/agentTypePolicies/{key}` | Per-agent-identity overrides, keyed `client::tier::name`. |
| User | `/admin/userPolicies/{uid}` | Per-user overrides — including stricter rules a user sets on themselves. |

At request time ACP merges the layers that apply to the caller and the agent into one effective policy, taking the strictest permission per tool and tier, and evaluates the call against it. Within a layer, a more specific tool entry decides over a broader one. The two floors (the hardline catastrophe list and policy tamper) sit above every layer. This page documents the **workspace** layer in full; the other three take the same body shape at their own paths.

## The policy object

```json
{
  "mode": "enforce",
  "defaults": {
    "interactive": { "permission": "allow", "rateLimit": 100, "transform": "log" },
    "subagent":    { "permission": "allow", "rateLimit": 60,  "transform": "redact" },
    "background":  { "permission": "deny" },
    "api":         { "permission": "allow", "rateLimit": 30,  "transform": "redact" }
  },
  "tools": {
    "github.create_issue": {
      "interactive": { "permission": "allow", "rateLimit": 10, "transform": "log" }
    }
  }
}
```

| Field | Type | Description |
|-------|------|-------------|
| `mode` | `"enforce"` \| `"audit"` | `enforce` applies decisions; `audit` logs what *would* have happened without blocking anything. Start in `audit`, watch the logs, then flip to `enforce`. `audit` is shadow evaluation on live traffic — the same would-have-happened lens as the shadow replay in the console, which runs a proposed policy against your recorded calls before you apply it. |
| `defaults` | object | Per-tier rules applied to every tool unless a `tools` entry overrides them. Tiers: `interactive`, `subagent`, `background`, `api`. |
| `tools` | object | Per-tool overrides, each keyed by tool name, then by tier. |

Each rule is:

| Field | Type | Description |
|-------|------|-------------|
| `permission` | `"allow"` \| `"deny"` | Whether the call may proceed. |
| `rateLimit` | number | Optional. Max calls per window before the call is throttled. |
| `transform` | `"log"` \| `"redact"` | `log` records the call as-is; `redact` strips detected secrets/PII from what's logged and passed on. |

The **tiers** describe how the agent is running, not who wrote it: `interactive` (a human in the loop), `subagent` (a delegated sub-task), `background` (autonomous/scheduled), `api` (driven by an API trigger). Governing by tier lets you, say, allow a tool interactively but deny it to a background run.

## Read the workspace policy

```
GET /<workspace>/admin/workspacePolicy
```

Readable by any member.

```bash
curl -s "$ACP/acme/admin/workspacePolicy" \
  -H "Authorization: Bearer $ACP_KEY"
```

Returns the policy object, or `{}` if none is set (defaults apply).

## Write the workspace policy

```
PUT /<workspace>/admin/workspacePolicy
```

A **merge** update — send only what you want to change; unset fields are left intact. Owner/admin only.

```bash
curl -sX PUT "$ACP/acme/admin/workspacePolicy" \
  -H "Authorization: Bearer $ACP_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "mode": "enforce",
    "defaults": {
      "background": { "permission": "deny" },
      "interactive": { "permission": "allow", "transform": "log" }
    }
  }'
```

```json
{ "ok": true }
```

A common rollout: set `"mode": "audit"`, run your agents, read the [audit log](/docs/api/logs) to see which tools they actually touch, codify rules for those, then `PUT { "mode": "enforce" }`.

## Clear the workspace policy

```
DELETE /<workspace>/admin/workspacePolicy
```

Removes the workspace layer entirely; the built-in defaults apply at the next call. Useful for resetting a test workspace. Owner/admin only.

```bash
curl -sX DELETE "$ACP/acme/admin/workspacePolicy" \
  -H "Authorization: Bearer $ACP_KEY"
```

## The other three layers

`rolePolicies`, `agentTypePolicies`, and `userPolicies` take the **same body shape** at their own paths, with the layer's key as the last path segment:

```bash
# Per-role: tighten what members can do
curl -sX PUT "$ACP/acme/admin/rolePolicies/member" \
  -H "Authorization: Bearer $ACP_KEY" -H "Content-Type: application/json" \
  -d '{ "defaults": { "interactive": { "permission": "allow", "rateLimit": 50 } } }'

# Per-agent-type: rein in one agent identity (client::tier::name)
curl -sX PUT "$ACP/acme/admin/agentTypePolicies/Claude%20Code::background::" \
  -H "Authorization: Bearer $ACP_KEY" -H "Content-Type: application/json" \
  -d '{ "tools": { "shell.exec": { "background": { "permission": "deny" } } } }'

# Per-user: a user can set stricter rules on themselves
curl -sX PUT "$ACP/acme/admin/userPolicies/uid_123" \
  -H "Authorization: Bearer $ACP_KEY" -H "Content-Type: application/json" \
  -d '{ "mode": "enforce", "defaults": { "api": { "permission": "deny" } } }'
```

Each layer supports `GET`, `PUT`, and `DELETE` the same way the workspace layer does. `userPolicies` allows self-edits (a member tightening their own rules) in addition to admin edits.

## The conceptual model

This is the reference. For *why* the layers merge the way they do, the tier model, and the detection passes behind `redact`, read [Policies &amp; scopes](/docs/policies) and [how a call is decided](/docs/governance-model).

## Next

<div style="display:flex;gap:12px;flex-wrap:wrap;margin-top:8px;">
  <a href="/docs/api/logs" class="acp-btn acp-btn-primary" style="padding:12px 24px;">See what policy did &rarr;</a>
  <a href="/docs/policies" class="acp-btn acp-btn-ghost" style="padding:12px 24px;">The policy model &rarr;</a>
</div>
